diff options
Diffstat (limited to 'formats/packfile/seekable.go')
-rw-r--r-- | formats/packfile/seekable.go | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/formats/packfile/seekable.go b/formats/packfile/seekable.go new file mode 100644 index 0000000..ea1c501 --- /dev/null +++ b/formats/packfile/seekable.go @@ -0,0 +1,108 @@ +package packfile + +import ( + "io" + "os" + + "gopkg.in/src-d/go-git.v3/core" +) + +// Seekable implements ReadRecaller for the io.ReadSeeker of a packfile. +// Remembering does not actually stores any reference to the remembered +// objects; the object offset is remebered instead and the packfile is +// read again everytime a recall operation is requested. This saves +// memory buy can be very slow if the associated io.ReadSeeker is slow +// (like a hard disk). +type Seekable struct { + io.ReadSeeker + HashToOffset map[core.Hash]int64 +} + +// NewSeekable returns a new Seekable that reads form r. +func NewSeekable(r io.ReadSeeker) *Seekable { + return &Seekable{ + r, + make(map[core.Hash]int64), + } +} + +// Read reads up to len(p) bytes into p. +func (r *Seekable) Read(p []byte) (int, error) { + return r.ReadSeeker.Read(p) +} + +// ReadByte reads a byte. +func (r *Seekable) ReadByte() (byte, error) { + var p [1]byte + _, err := r.ReadSeeker.Read(p[:]) + if err != nil { + return 0, err + } + + return p[0], nil +} + +// Offset returns the offset for the next Read or ReadByte. +func (r *Seekable) Offset() (int64, error) { + return r.Seek(0, os.SEEK_CUR) +} + +// Remember stores the offset of the object and its hash, but not the +// object itself. This implementation does not check for already stored +// offsets, as it is too expensive to build this information from an +// index every time a get operation is performed on the SeekableReadRecaller. +func (r *Seekable) Remember(o int64, obj core.Object) error { + h := obj.Hash() + if _, ok := r.HashToOffset[h]; ok { + return ErrDuplicatedObject.AddDetails("with hash %s", h) + } + + r.HashToOffset[h] = o + + return nil +} + +// ForgetAll forgets all previously remembered objects. For efficiency +// reasons RecallByOffset always find objects, even if they have been +// forgetted or were never remembered. +func (r *Seekable) ForgetAll() { + r.HashToOffset = make(map[core.Hash]int64) +} + +// RecallByHash returns the object for a given hash by looking for it again in +// the io.ReadeSeerker. +func (r *Seekable) RecallByHash(h core.Hash) (core.Object, error) { + o, ok := r.HashToOffset[h] + if !ok { + return nil, ErrCannotRecall.AddDetails("hash not found: %s", h) + } + + return r.RecallByOffset(o) +} + +// RecallByOffset returns the object for a given offset by looking for it again in +// the io.ReadeSeerker. For efficiency reasons, this method always find objects by +// offset, even if they have not been remembered or if they have been forgetted. +func (r *Seekable) RecallByOffset(o int64) (obj core.Object, err error) { + // remember current offset + beforeJump, err := r.Offset() + if err != nil { + return nil, err + } + + defer func() { + // jump back + _, seekErr := r.Seek(beforeJump, os.SEEK_SET) + if err == nil { + err = seekErr + } + }() + + // jump to requested offset + _, err = r.Seek(o, os.SEEK_SET) + if err != nil { + return nil, err + } + + return NewParser(r).ReadObject() +} |