Skip to content

Commit

Permalink
Implement ReadSeeker over Reader
Browse files Browse the repository at this point in the history
  • Loading branch information
nlepage committed Aug 8, 2023
1 parent ff23ce9 commit 30ec627
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 6 deletions.
65 changes: 59 additions & 6 deletions fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tarfs
import (
"archive/tar"
"bytes"
"errors"
"io"
"io/fs"
"path"
Expand Down Expand Up @@ -262,15 +263,14 @@ func (e *regEntry) readdir(path string) ([]fs.DirEntry, error) {
}

func (e *regEntry) readfile(path string) ([]byte, error) {
tr := tar.NewReader(io.NewSectionReader(e.ra, e.offset, 1<<63-1))

if _, err := tr.Next(); err != nil {
r, err := e.reader()
if err != nil {
return nil, err
}

b := bytes.NewBuffer(make([]byte, 0, e.size()))

if _, err := io.Copy(b, tr); err != nil {
if _, err := io.Copy(b, r); err != nil {
return nil, err
}

Expand All @@ -282,12 +282,22 @@ func (e *regEntry) entries(op, path string) ([]fs.DirEntry, error) {
}

func (e *regEntry) open() (fs.File, error) {
b, err := e.readfile("")
r, err := e.reader()
if err != nil {
return nil, err
}

return &file{e, bytes.NewReader(b), -1, false}, nil
return &file{e, &readSeeker{&readCounter{r, 0}, e}, -1, false}, nil
}

func (e *regEntry) reader() (io.Reader, error) {
tr := tar.NewReader(io.NewSectionReader(e.ra, e.offset, 1<<63-1))

if _, err := tr.Next(); err != nil {
return nil, err
}

return tr, nil
}

type dirEntry struct {
Expand Down Expand Up @@ -381,3 +391,46 @@ func (entries entriesByName) Len() int {
func (entries entriesByName) Swap(i, j int) {
entries[i], entries[j] = entries[j], entries[i]
}

type readSeeker struct {
*readCounter
e *regEntry
}

var _ io.ReadSeeker = &readSeeker{}

func (rs *readSeeker) Seek(offset int64, whence int) (int64, error) {
const op = "seek"

var abs int64
switch whence {
case io.SeekStart:
abs = offset
case io.SeekCurrent:
abs = rs.off + offset
case io.SeekEnd:
abs = rs.e.size() + offset
default:
return 0, newErr(op, rs.e.name, errors.New("invalid whence"))
}
if abs < 0 {
return 0, newErr(op, rs.e.name, errors.New("negative position"))
}

if abs < rs.off {
r, err := rs.e.reader()
if err != nil {
return 0, err
}

rs.readCounter = &readCounter{r, 0}
}

if abs > rs.off {
if _, err := io.CopyN(io.Discard, rs.readCounter, abs-rs.off); err != nil && err != io.EOF {
return 0, err
}
}

return abs, nil
}
24 changes: 24 additions & 0 deletions fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,30 @@ func TestOpenThenReadAll(t *testing.T) {
}
}

func TestOpenThenSeekAfterEnd(t *testing.T) {
require := require.New(t)

f, err := os.Open("test.tar")
require.NoError(err)
defer f.Close()

tfs, err := New(f)
require.NoError(err)

r, err := tfs.Open("foo")
require.NoError(err, "when tarfs.Open(foo)")

rs := r.(io.ReadSeeker)

abs, err := rs.Seek(10, io.SeekStart)
require.NoError(err, "when ReadSeeker.Seek(10, io.SeekStart)")
require.Equal(int64(10), abs, "when ReadSeeker.Seek(10, io.SeekStart)")

b := make([]byte, 0, 1)
_, err = rs.Read(b)
require.ErrorIs(err, io.EOF, "when ReadSeeker.Read([]byte)")
}

func TestReadDir(t *testing.T) {
require, assert := require.New(t), assert.New(t)

Expand Down

0 comments on commit 30ec627

Please sign in to comment.