github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/repo/refstore.go (about) 1 package repo 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/qri-io/qri/dsref" 8 reporef "github.com/qri-io/qri/repo/ref" 9 ) 10 11 // Refstore keeps a collection of dataset references, Refstores require complete 12 // references (with both alias and identifiers), and can carry only one of a 13 // given alias eg: putting peer/dataset@a/ipfs/b when a ref with alias peer/dataset 14 // is already in the store will overwrite the stored reference 15 type Refstore interface { 16 // PutRef adds a reference to the store. References must be complete with 17 // Peername, Name, and Path/FSIPath specified 18 PutRef(ref reporef.DatasetRef) error 19 // GetRef "completes" a passed in alias (reporef.DatasetRef with at least Peername 20 // and Name field specified), filling in missing fields with a stored ref 21 GetRef(ref reporef.DatasetRef) (reporef.DatasetRef, error) 22 // DeleteRef removes a reference from the store 23 DeleteRef(ref reporef.DatasetRef) error 24 // References returns a set of references from the store 25 References(offset, limit int) ([]reporef.DatasetRef, error) 26 // RefCount returns the number of references in the store 27 RefCount() (int, error) 28 } 29 30 // ListVersionInfoShim wraps a call to References, converting results to a 31 // slice of VersionInfos 32 func ListVersionInfoShim(r Repo, offset, limit int) ([]dsref.VersionInfo, error) { 33 refs, err := r.References(offset, limit) 34 if err != nil { 35 return nil, err 36 } 37 38 res := make([]dsref.VersionInfo, len(refs)) 39 for i, rref := range refs { 40 res[i] = reporef.ConvertToVersionInfo(&rref) 41 } 42 return res, nil 43 } 44 45 // GetVersionInfoShim is a shim for getting away from the old 46 // DatasetRef, CanonicalizeDatasetRef, RefStore stack 47 func GetVersionInfoShim(r Repo, ref dsref.Ref) (*dsref.VersionInfo, error) { 48 rref, err := r.GetRef(reporef.RefFromDsref(ref)) 49 if err != nil { 50 return nil, err 51 } 52 vi := reporef.ConvertToVersionInfo(&rref) 53 return &vi, nil 54 } 55 56 // PutVersionInfoShim is a shim for getting away from old stack of 57 // DatasetRef, CanonicalizeDatasetRef, and RefStores 58 // while still safely interacting with the repo.Refstore API 59 func PutVersionInfoShim(ctx context.Context, r Repo, vi *dsref.VersionInfo) error { 60 // attempt to look up peerIDs when not set 61 if vi.ProfileID == "" && vi.Username != "" { 62 rref := &reporef.DatasetRef{Peername: vi.Username} 63 if err := canonicalizeProfile(ctx, r, rref); err == nil { 64 vi.ProfileID = rref.ProfileID.Encode() 65 } 66 } 67 return r.PutRef(reporef.RefFromVersionInfo(vi)) 68 } 69 70 // DeleteVersionInfoShim is a shim for getting away from the old stack of 71 // DatasetRef, CanonicalizeDatasetRef, and RefStore 72 // while still safely interacting with the repo.Refstore API 73 func DeleteVersionInfoShim(ctx context.Context, r Repo, ref dsref.Ref) (*dsref.VersionInfo, error) { 74 rref := reporef.RefFromDsref(ref) 75 if err := canonicalizeDatasetRef(ctx, r, &rref); err != nil && err != ErrNoHistory { 76 return nil, err 77 } 78 if err := r.DeleteRef(rref); err != nil { 79 return nil, err 80 } 81 vi := reporef.ConvertToVersionInfo(&rref) 82 return &vi, nil 83 } 84 85 // ListDatasetsShim gets away from the old stack of DatasetRef, 86 // CanonicalizeDatasetRef and RefStore 87 func ListDatasetsShim(r Repo, offset, limit int) ([]dsref.VersionInfo, error) { 88 rrefs, err := r.References(offset, limit) 89 if err != nil { 90 return nil, err 91 } 92 93 vis := make([]dsref.VersionInfo, 0, limit) 94 for _, ref := range rrefs { 95 if offset > 0 { 96 offset-- 97 continue 98 } 99 vis = append(vis, reporef.ConvertToVersionInfo(&ref)) 100 if len(vis) == limit { 101 return vis, nil 102 } 103 } 104 105 return vis, nil 106 } 107 108 // canonicalizeDatasetRef uses the user's repo to turn any local aliases into full dataset 109 // references using known canonical peernames and paths. If the provided reference is not 110 // in the local repo, still do the work of handling aliases, but return a repo.ErrNotFound 111 // error, which callers can respond to by possibly contacting remote repos. 112 func canonicalizeDatasetRef(ctx context.Context, r Repo, ref *reporef.DatasetRef) error { 113 if ref.IsEmpty() { 114 return ErrEmptyRef 115 } 116 117 if err := canonicalizeProfile(ctx, r, ref); err != nil { 118 return err 119 } 120 121 got, err := r.GetRef(*ref) 122 if err != nil { 123 return err 124 } 125 126 // TODO (b5) - this is the assign pattern, refactor into a method on reporef.DatasetRef 127 if ref.Path == "" { 128 ref.Path = got.Path 129 } 130 if ref.ProfileID == "" { 131 ref.ProfileID = got.ProfileID 132 } 133 if ref.Name == "" { 134 ref.Name = got.Name 135 } 136 if ref.Peername == "" || ref.Peername != got.Peername { 137 ref.Peername = got.Peername 138 } 139 ref.Published = got.Published 140 if ref.FSIPath == "" { 141 ref.FSIPath = got.FSIPath 142 } 143 if ref.ProfileID != got.ProfileID || ref.Name != got.Name { 144 return fmt.Errorf("Given datasetRef %s does not match datasetRef on file: %s", ref.String(), got.String()) 145 } 146 147 if got.Path == "" { 148 return ErrNoHistory 149 } 150 151 return nil 152 } 153 154 // canonicalizeProfile populates dataset reporef.DatasetRef ProfileID and Peername properties, 155 // changing aliases to known names, and adding ProfileID from a peerstore 156 func canonicalizeProfile(ctx context.Context, r Repo, ref *reporef.DatasetRef) error { 157 if ref.Peername == "" && ref.ProfileID == "" { 158 return nil 159 } 160 161 p := r.Profiles().Owner(ctx) 162 163 // If this is a dataset ref that a peer of the user owns. 164 if ref.Peername == "me" || ref.Peername == p.Peername || ref.ProfileID == p.ID { 165 if ref.Peername == "me" { 166 ref.ProfileID = p.ID 167 ref.Peername = p.Peername 168 } 169 170 if ref.Peername != "" && ref.ProfileID != "" { 171 if ref.Peername == p.Peername && ref.ProfileID != p.ID { 172 return fmt.Errorf("Peername and ProfileID combination not valid: Peername = %s, ProfileID = %s, but was given ProfileID = %s", p.Peername, p.ID, ref.ProfileID) 173 } 174 if ref.ProfileID == p.ID && ref.Peername != p.Peername { 175 // The peer renamed itself at some point, but the profileID matches. Use the 176 // new peername. 177 ref.Peername = p.Peername 178 return nil 179 } 180 if ref.Peername == p.Peername && ref.ProfileID == p.ID { 181 return nil 182 } 183 } 184 185 if ref.Peername != "" { 186 if ref.Peername != p.Peername { 187 return nil 188 } 189 } 190 191 if ref.ProfileID != "" { 192 if ref.ProfileID != p.ID { 193 return nil 194 } 195 } 196 197 ref.Peername = p.Peername 198 ref.ProfileID = p.ID 199 return nil 200 } 201 202 if ref.ProfileID != "" { 203 if profile, err := r.Profiles().GetProfile(ctx, ref.ProfileID); err == nil { 204 205 if ref.Peername == "" { 206 ref.Peername = profile.Peername 207 return nil 208 } 209 if ref.Peername != profile.Peername { 210 return fmt.Errorf("Peername and ProfileID combination not valid: ProfileID = %s, Peername = %s, but was given Peername = %s", profile.ID, profile.Peername, ref.Peername) 211 } 212 } 213 } 214 215 if ref.Peername != "" { 216 if id, err := r.Profiles().PeernameID(ctx, ref.Peername); err == nil { 217 // if err != nil { 218 // return fmt.Errorf("error fetching peer from store: %s", err) 219 // } 220 if ref.ProfileID == "" { 221 ref.ProfileID = id 222 return nil 223 } 224 if ref.ProfileID != id { 225 return fmt.Errorf("Peername and ProfileID combination not valid: Peername = %s, ProfileID = %s, but was given ProfileID = %s", ref.Peername, id.Encode(), ref.ProfileID) 226 } 227 } 228 } 229 return nil 230 } 231 232 // CompareDatasetRef compares two Dataset References, returning an error 233 // describing any difference between the two references 234 func CompareDatasetRef(a, b reporef.DatasetRef) error { 235 if a.ProfileID != b.ProfileID { 236 return fmt.Errorf("PeerID mismatch. %s != %s", a.ProfileID.Encode(), b.ProfileID.Encode()) 237 } 238 if a.Peername != b.Peername { 239 return fmt.Errorf("Peername mismatch. %s != %s", a.Peername, b.Peername) 240 } 241 if a.Name != b.Name { 242 return fmt.Errorf("Name mismatch. %s != %s", a.Name, b.Name) 243 } 244 if a.Path != b.Path { 245 return fmt.Errorf("Path mismatch. %s != %s", a.Path, b.Path) 246 } 247 if a.Published != b.Published { 248 return fmt.Errorf("Published mismatch: %t != %t", a.Published, b.Published) 249 } 250 return nil 251 }