aboutsummaryrefslogtreecommitdiffstats
path: root/formats/packfile/seekable.go
blob: ea1c501dab843e27b232ccaf2942ebc1dee9ad66 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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()
}