package storage import ( "context" "errors" "fmt" "io" ) type rangeOpener func(offset int64) (io.ReadCloser, context.CancelFunc, error) type s3Reader struct { size int64 position int64 bodyOffset int64 body io.ReadCloser cancel context.CancelFunc reopen rangeOpener } func newS3Reader(size int64, body io.ReadCloser, cancel context.CancelFunc, reopen rangeOpener) *s3Reader { return &s3Reader{size: size, body: body, cancel: cancel, reopen: reopen} } func (r *s3Reader) Read(p []byte) (int, error) { if r.position >= r.size { return 0, io.EOF } if r.body == nil || r.bodyOffset != r.position { if err := r.openAtPosition(); err != nil { return 0, err } } n, err := r.body.Read(p) r.position += int64(n) r.bodyOffset = r.position return n, err } func (r *s3Reader) openAtPosition() error { r.release() body, cancel, err := r.reopen(r.position) if err != nil { return err } r.body, r.cancel, r.bodyOffset = body, cancel, r.position return nil } func (r *s3Reader) Seek(offset int64, whence int) (int64, error) { var target int64 switch whence { case io.SeekStart: target = offset case io.SeekCurrent: target = r.position + offset case io.SeekEnd: target = r.size + offset default: return 0, errors.New("invalid seek whence") } if target < 0 { return 0, fmt.Errorf("negative seek position %d", target) } r.position = target return target, nil } func (r *s3Reader) Close() error { r.release() return nil } func (r *s3Reader) release() { if r.body != nil { _ = r.body.Close() r.body = nil } if r.cancel != nil { r.cancel() r.cancel = nil } }