ServeContent seeks to the end for the size before it seeks to the range, which was throwing away the stream Check had just opened. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
90 lines
2.0 KiB
Go
90 lines
2.0 KiB
Go
package files
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
// Seeker is an FS file as http.ServeContent wants one — something to seek in — for a file
|
|
// whose size is already known from a stat. ServeContent seeks to the end for the size, to
|
|
// the start of the range asked for, then reads: so the backend is opened once, at that
|
|
// position, and only ever read forward. The Sudo backend's helper is started at the offset
|
|
// rather than streamed from the top and skipped.
|
|
type Seeker struct {
|
|
ctx context.Context
|
|
fsys FS
|
|
path string
|
|
size int64
|
|
pos int64 // where the next Read reads from
|
|
at int64 // where cur is positioned, when open
|
|
cur io.ReadCloser
|
|
}
|
|
|
|
func NewSeeker(ctx context.Context, fsys FS, path string, size int64) *Seeker {
|
|
return &Seeker{ctx: ctx, fsys: fsys, path: path, size: size}
|
|
}
|
|
|
|
// Check opens the file now, from the start, so a file that cannot be read is known before
|
|
// any headers go out. Seeking costs nothing; a Read from somewhere else reopens there.
|
|
func (s *Seeker) Check() error {
|
|
if s.cur != nil {
|
|
return nil
|
|
}
|
|
f, err := s.fsys.OpenAt(s.ctx, s.path, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.cur, s.at = f, 0
|
|
return nil
|
|
}
|
|
|
|
func (s *Seeker) Seek(offset int64, whence int) (int64, error) {
|
|
var pos int64
|
|
switch whence {
|
|
case io.SeekStart:
|
|
pos = offset
|
|
case io.SeekCurrent:
|
|
pos = s.pos + offset
|
|
case io.SeekEnd:
|
|
pos = s.size + offset
|
|
default:
|
|
return 0, errors.New("bad whence")
|
|
}
|
|
if pos < 0 {
|
|
return 0, errors.New("seek before start")
|
|
}
|
|
s.pos = pos // the stream, if open, stays where it is until a Read wants somewhere else
|
|
return pos, nil
|
|
}
|
|
|
|
func (s *Seeker) Read(p []byte) (int, error) {
|
|
if s.pos >= s.size {
|
|
return 0, io.EOF
|
|
}
|
|
if s.cur != nil && s.pos != s.at {
|
|
s.cur.Close()
|
|
s.cur = nil
|
|
}
|
|
if s.cur == nil {
|
|
f, err := s.fsys.OpenAt(s.ctx, s.path, s.pos)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
s.cur, s.at = f, s.pos
|
|
}
|
|
n, err := s.cur.Read(p)
|
|
s.pos += int64(n)
|
|
s.at += int64(n)
|
|
return n, err
|
|
}
|
|
|
|
func (s *Seeker) Close() error {
|
|
if s.cur == nil {
|
|
return nil
|
|
}
|
|
err := s.cur.Close()
|
|
s.cur = nil
|
|
return err
|
|
}
|