package repository import ( "bytes" "encoding/json" "fmt" "io" "restic" "sync" "time" "github.com/pkg/errors" "restic/crypto" "restic/debug" ) // Index holds a lookup table for id -> pack. type Index struct { m sync.Mutex pack map[restic.BlobHandle][]indexEntry final bool // set to true for all indexes read from the backend ("finalized") id restic.ID // set to the ID of the index when it's finalized supersedes restic.IDs created time.Time } type indexEntry struct { packID restic.ID offset uint length uint } // NewIndex returns a new index. func NewIndex() *Index { return &Index{ pack: make(map[restic.BlobHandle][]indexEntry), created: time.Now(), } } func (idx *Index) store(blob PackedBlob) { newEntry := indexEntry{ packID: blob.PackID, offset: blob.Offset, length: blob.Length, } h := restic.BlobHandle{ID: blob.ID, Type: blob.Type} idx.pack[h] = append(idx.pack[h], newEntry) } // Final returns true iff the index is already written to the repository, it is // finalized. func (idx *Index) Final() bool { idx.m.Lock() defer idx.m.Unlock() return idx.final } const ( indexMinBlobs = 20 indexMaxBlobs = 2000 indexMinAge = 2 * time.Minute indexMaxAge = 15 * time.Minute ) // IndexFull returns true iff the index is "full enough" to be saved as a preliminary index. var IndexFull = func(idx *Index) bool { idx.m.Lock() defer idx.m.Unlock() debug.Log("Index.Full", "checking whether index %p is full", idx) packs := len(idx.pack) age := time.Now().Sub(idx.created) if age > indexMaxAge { debug.Log("Index.Full", "index %p is old enough", idx, age) return true } if packs < indexMinBlobs || age < indexMinAge { debug.Log("Index.Full", "index %p only has %d packs or is too young (%v)", idx, packs, age) return false } if packs > indexMaxBlobs { debug.Log("Index.Full", "index %p has %d packs", idx, packs) return true } debug.Log("Index.Full", "index %p is not full", idx) return false } // Store remembers the id and pack in the index. An existing entry will be // silently overwritten. func (idx *Index) Store(blob PackedBlob) { idx.m.Lock() defer idx.m.Unlock() if idx.final { panic("store new item in finalized index") } debug.Log("Index.Store", "%v", blob) idx.store(blob) } // Lookup queries the index for the blob ID and returns a PackedBlob. func (idx *Index) Lookup(id restic.ID, tpe restic.BlobType) (blobs []PackedBlob, err error) { idx.m.Lock() defer idx.m.Unlock() h := restic.BlobHandle{ID: id, Type: tpe} if packs, ok := idx.pack[h]; ok { blobs = make([]PackedBlob, 0, len(packs)) for _, p := range packs { debug.Log("Index.Lookup", "id %v found in pack %v at %d, length %d", id.Str(), p.packID.Str(), p.offset, p.length) blob := PackedBlob{ Type: tpe, Length: p.length, ID: id, Offset: p.offset, PackID: p.packID, } blobs = append(blobs, blob) } return blobs, nil } debug.Log("Index.Lookup", "id %v not found", id.Str()) return nil, errors.Errorf("id %v not found in index", id) } // ListPack returns a list of blobs contained in a pack. func (idx *Index) ListPack(id restic.ID) (list []PackedBlob) { idx.m.Lock() defer idx.m.Unlock() for h, packList := range idx.pack { for _, entry := range packList { if entry.packID == id { list = append(list, PackedBlob{ ID: h.ID, Type: h.Type, Length: entry.length, Offset: entry.offset, PackID: entry.packID, }) } } } return list } // Has returns true iff the id is listed in the index. func (idx *Index) Has(id restic.ID, tpe restic.BlobType) bool { _, err := idx.Lookup(id, tpe) if err == nil { return true } return false } // LookupSize returns the length of the cleartext content behind the // given id func (idx *Index) LookupSize(id restic.ID, tpe restic.BlobType) (cleartextLength uint, err error) { blobs, err := idx.Lookup(id, tpe) if err != nil { return 0, err } return blobs[0].PlaintextLength(), nil } // Supersedes returns the list of indexes this index supersedes, if any. func (idx *Index) Supersedes() restic.IDs { return idx.supersedes } // AddToSupersedes adds the ids to the list of indexes superseded by this // index. If the index has already been finalized, an error is returned. func (idx *Index) AddToSupersedes(ids ...restic.ID) error { idx.m.Lock() defer idx.m.Unlock() if idx.final { return errors.New("index already finalized") } idx.supersedes = append(idx.supersedes, ids...) return nil } // PackedBlob is a blob already saved within a pack. type PackedBlob struct { Type restic.BlobType Length uint ID restic.ID Offset uint PackID restic.ID } func (pb PackedBlob) String() string { return fmt.Sprintf("