github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libgit/on_demand_delta_object.go (about) 1 // Copyright 2017 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libgit 6 7 import ( 8 "io" 9 10 lru "github.com/hashicorp/golang-lru" 11 "github.com/pkg/errors" 12 "gopkg.in/src-d/go-git.v4/plumbing" 13 "gopkg.in/src-d/go-git.v4/plumbing/storer" 14 ) 15 16 type onDemandDeltaObject struct { 17 s storer.DeltaObjectStorer 18 hash plumbing.Hash 19 typeSet bool 20 objType plumbing.ObjectType 21 sizeSet bool 22 size int64 23 baseHash plumbing.Hash 24 actualHash plumbing.Hash 25 actualSize int64 26 recentCache *lru.Cache 27 } 28 29 var _ plumbing.DeltaObject = (*onDemandDeltaObject)(nil) 30 31 type notDeltaError struct { 32 } 33 34 func (nde notDeltaError) Error() string { 35 return "Not a delta object" 36 } 37 38 func (oddo *onDemandDeltaObject) cache() (o plumbing.DeltaObject, err error) { 39 if tmp, ok := oddo.recentCache.Get(oddo.hash); ok { 40 o, ok = tmp.(plumbing.DeltaObject) 41 if !ok { 42 return nil, notDeltaError{} 43 } 44 } else { 45 eo, err := oddo.s.DeltaObject(oddo.objType, oddo.hash) 46 if err != nil { 47 return nil, err 48 } 49 oddo.recentCache.Add(oddo.hash, eo) 50 o, ok = eo.(plumbing.DeltaObject) 51 if !ok { 52 return nil, notDeltaError{} 53 } 54 } 55 56 if !oddo.sizeSet { 57 oddo.size = o.Size() 58 } 59 if !oddo.typeSet { 60 oddo.objType = o.Type() 61 } 62 oddo.baseHash = o.BaseHash() 63 oddo.actualHash = o.ActualHash() 64 oddo.actualSize = o.ActualSize() 65 return o, nil 66 } 67 68 func (oddo *onDemandDeltaObject) cacheIfNeeded() error { 69 if oddo.size >= 0 { 70 return nil 71 } 72 // TODDO: We should be able to read the type and size from the 73 // object's header, without loading the entire object itself. 74 _, err := oddo.cache() 75 return err 76 } 77 78 func (oddo *onDemandDeltaObject) Hash() plumbing.Hash { 79 return oddo.hash 80 } 81 82 func (oddo *onDemandDeltaObject) Type() plumbing.ObjectType { 83 _ = oddo.cacheIfNeeded() 84 return oddo.objType 85 } 86 87 func (oddo *onDemandDeltaObject) SetType(ot plumbing.ObjectType) { 88 oddo.typeSet = true 89 oddo.objType = ot 90 } 91 92 func (oddo *onDemandDeltaObject) Size() int64 { 93 _ = oddo.cacheIfNeeded() 94 return oddo.size 95 } 96 97 func (oddo *onDemandDeltaObject) SetSize(s int64) { 98 oddo.sizeSet = true 99 oddo.size = s 100 } 101 102 func (oddo *onDemandDeltaObject) Reader() (io.ReadCloser, error) { 103 // Create a new object to stream data from the storer on-demand, 104 // without caching the bytes to long-lived memory. Let the block 105 // cache do its job to keep recent data in memory and control 106 // total memory usage. 107 o, err := oddo.cache() 108 if err != nil { 109 return nil, err 110 } 111 return o.Reader() 112 } 113 114 func (oddo *onDemandDeltaObject) Writer() (io.WriteCloser, error) { 115 return nil, errors.New("onDemandDeltaObject shouldn't be used for writes") 116 } 117 118 func (oddo *onDemandDeltaObject) BaseHash() plumbing.Hash { 119 _ = oddo.cacheIfNeeded() 120 return oddo.baseHash 121 } 122 123 func (oddo *onDemandDeltaObject) ActualHash() plumbing.Hash { 124 _ = oddo.cacheIfNeeded() 125 return oddo.actualHash 126 } 127 128 func (oddo *onDemandDeltaObject) ActualSize() int64 { 129 _ = oddo.cacheIfNeeded() 130 return oddo.actualSize 131 }