github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/swarm/api/manifest.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 package api 13 14 import ( 15 "bytes" 16 "encoding/json" 17 "errors" 18 "fmt" 19 "io" 20 "net/http" 21 "strings" 22 "sync" 23 "time" 24 25 "github.com/Sberex/go-sberex/common" 26 "github.com/Sberex/go-sberex/log" 27 "github.com/Sberex/go-sberex/swarm/storage" 28 ) 29 30 const ( 31 ManifestType = "application/bzz-manifest+json" 32 ) 33 34 // Manifest represents a swarm manifest 35 type Manifest struct { 36 Entries []ManifestEntry `json:"entries,omitempty"` 37 } 38 39 // ManifestEntry represents an entry in a swarm manifest 40 type ManifestEntry struct { 41 Hash string `json:"hash,omitempty"` 42 Path string `json:"path,omitempty"` 43 ContentType string `json:"contentType,omitempty"` 44 Mode int64 `json:"mode,omitempty"` 45 Size int64 `json:"size,omitempty"` 46 ModTime time.Time `json:"mod_time,omitempty"` 47 Status int `json:"status,omitempty"` 48 } 49 50 // ManifestList represents the result of listing files in a manifest 51 type ManifestList struct { 52 CommonPrefixes []string `json:"common_prefixes,omitempty"` 53 Entries []*ManifestEntry `json:"entries,omitempty"` 54 } 55 56 // NewManifest creates and stores a new, empty manifest 57 func (a *Api) NewManifest() (storage.Key, error) { 58 var manifest Manifest 59 data, err := json.Marshal(&manifest) 60 if err != nil { 61 return nil, err 62 } 63 return a.Store(bytes.NewReader(data), int64(len(data)), &sync.WaitGroup{}) 64 } 65 66 // ManifestWriter is used to add and remove entries from an underlying manifest 67 type ManifestWriter struct { 68 api *Api 69 trie *manifestTrie 70 quitC chan bool 71 } 72 73 func (a *Api) NewManifestWriter(key storage.Key, quitC chan bool) (*ManifestWriter, error) { 74 trie, err := loadManifest(a.dpa, key, quitC) 75 if err != nil { 76 return nil, fmt.Errorf("error loading manifest %s: %s", key, err) 77 } 78 return &ManifestWriter{a, trie, quitC}, nil 79 } 80 81 // AddEntry stores the given data and adds the resulting key to the manifest 82 func (m *ManifestWriter) AddEntry(data io.Reader, e *ManifestEntry) (storage.Key, error) { 83 key, err := m.api.Store(data, e.Size, nil) 84 if err != nil { 85 return nil, err 86 } 87 entry := newManifestTrieEntry(e, nil) 88 entry.Hash = key.String() 89 m.trie.addEntry(entry, m.quitC) 90 return key, nil 91 } 92 93 // RemoveEntry removes the given path from the manifest 94 func (m *ManifestWriter) RemoveEntry(path string) error { 95 m.trie.deleteEntry(path, m.quitC) 96 return nil 97 } 98 99 // Store stores the manifest, returning the resulting storage key 100 func (m *ManifestWriter) Store() (storage.Key, error) { 101 return m.trie.hash, m.trie.recalcAndStore() 102 } 103 104 // ManifestWalker is used to recursively walk the entries in the manifest and 105 // all of its submanifests 106 type ManifestWalker struct { 107 api *Api 108 trie *manifestTrie 109 quitC chan bool 110 } 111 112 func (a *Api) NewManifestWalker(key storage.Key, quitC chan bool) (*ManifestWalker, error) { 113 trie, err := loadManifest(a.dpa, key, quitC) 114 if err != nil { 115 return nil, fmt.Errorf("error loading manifest %s: %s", key, err) 116 } 117 return &ManifestWalker{a, trie, quitC}, nil 118 } 119 120 // SkipManifest is used as a return value from WalkFn to indicate that the 121 // manifest should be skipped 122 var SkipManifest = errors.New("skip this manifest") 123 124 // WalkFn is the type of function called for each entry visited by a recursive 125 // manifest walk 126 type WalkFn func(entry *ManifestEntry) error 127 128 // Walk recursively walks the manifest calling walkFn for each entry in the 129 // manifest, including submanifests 130 func (m *ManifestWalker) Walk(walkFn WalkFn) error { 131 return m.walk(m.trie, "", walkFn) 132 } 133 134 func (m *ManifestWalker) walk(trie *manifestTrie, prefix string, walkFn WalkFn) error { 135 for _, entry := range trie.entries { 136 if entry == nil { 137 continue 138 } 139 entry.Path = prefix + entry.Path 140 err := walkFn(&entry.ManifestEntry) 141 if err != nil { 142 if entry.ContentType == ManifestType && err == SkipManifest { 143 continue 144 } 145 return err 146 } 147 if entry.ContentType != ManifestType { 148 continue 149 } 150 if err := trie.loadSubTrie(entry, nil); err != nil { 151 return err 152 } 153 if err := m.walk(entry.subtrie, entry.Path, walkFn); err != nil { 154 return err 155 } 156 } 157 return nil 158 } 159 160 type manifestTrie struct { 161 dpa *storage.DPA 162 entries [257]*manifestTrieEntry // indexed by first character of basePath, entries[256] is the empty basePath entry 163 hash storage.Key // if hash != nil, it is stored 164 } 165 166 func newManifestTrieEntry(entry *ManifestEntry, subtrie *manifestTrie) *manifestTrieEntry { 167 return &manifestTrieEntry{ 168 ManifestEntry: *entry, 169 subtrie: subtrie, 170 } 171 } 172 173 type manifestTrieEntry struct { 174 ManifestEntry 175 176 subtrie *manifestTrie 177 } 178 179 func loadManifest(dpa *storage.DPA, hash storage.Key, quitC chan bool) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand 180 181 log.Trace(fmt.Sprintf("manifest lookup key: '%v'.", hash.Log())) 182 // retrieve manifest via DPA 183 manifestReader := dpa.Retrieve(hash) 184 return readManifest(manifestReader, hash, dpa, quitC) 185 } 186 187 func readManifest(manifestReader storage.LazySectionReader, hash storage.Key, dpa *storage.DPA, quitC chan bool) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand 188 189 // TODO check size for oversized manifests 190 size, err := manifestReader.Size(quitC) 191 if err != nil { // size == 0 192 // can't determine size means we don't have the root chunk 193 err = fmt.Errorf("Manifest not Found") 194 return 195 } 196 manifestData := make([]byte, size) 197 read, err := manifestReader.Read(manifestData) 198 if int64(read) < size { 199 log.Trace(fmt.Sprintf("Manifest %v not found.", hash.Log())) 200 if err == nil { 201 err = fmt.Errorf("Manifest retrieval cut short: read %v, expect %v", read, size) 202 } 203 return 204 } 205 206 log.Trace(fmt.Sprintf("Manifest %v retrieved", hash.Log())) 207 var man struct { 208 Entries []*manifestTrieEntry `json:"entries"` 209 } 210 err = json.Unmarshal(manifestData, &man) 211 if err != nil { 212 err = fmt.Errorf("Manifest %v is malformed: %v", hash.Log(), err) 213 log.Trace(fmt.Sprintf("%v", err)) 214 return 215 } 216 217 log.Trace(fmt.Sprintf("Manifest %v has %d entries.", hash.Log(), len(man.Entries))) 218 219 trie = &manifestTrie{ 220 dpa: dpa, 221 } 222 for _, entry := range man.Entries { 223 trie.addEntry(entry, quitC) 224 } 225 return 226 } 227 228 func (self *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) { 229 self.hash = nil // trie modified, hash needs to be re-calculated on demand 230 231 if len(entry.Path) == 0 { 232 self.entries[256] = entry 233 return 234 } 235 236 b := entry.Path[0] 237 oldentry := self.entries[b] 238 if (oldentry == nil) || (oldentry.Path == entry.Path && oldentry.ContentType != ManifestType) { 239 self.entries[b] = entry 240 return 241 } 242 243 cpl := 0 244 for (len(entry.Path) > cpl) && (len(oldentry.Path) > cpl) && (entry.Path[cpl] == oldentry.Path[cpl]) { 245 cpl++ 246 } 247 248 if (oldentry.ContentType == ManifestType) && (cpl == len(oldentry.Path)) { 249 if self.loadSubTrie(oldentry, quitC) != nil { 250 return 251 } 252 entry.Path = entry.Path[cpl:] 253 oldentry.subtrie.addEntry(entry, quitC) 254 oldentry.Hash = "" 255 return 256 } 257 258 commonPrefix := entry.Path[:cpl] 259 260 subtrie := &manifestTrie{ 261 dpa: self.dpa, 262 } 263 entry.Path = entry.Path[cpl:] 264 oldentry.Path = oldentry.Path[cpl:] 265 subtrie.addEntry(entry, quitC) 266 subtrie.addEntry(oldentry, quitC) 267 268 self.entries[b] = newManifestTrieEntry(&ManifestEntry{ 269 Path: commonPrefix, 270 ContentType: ManifestType, 271 }, subtrie) 272 } 273 274 func (self *manifestTrie) getCountLast() (cnt int, entry *manifestTrieEntry) { 275 for _, e := range self.entries { 276 if e != nil { 277 cnt++ 278 entry = e 279 } 280 } 281 return 282 } 283 284 func (self *manifestTrie) deleteEntry(path string, quitC chan bool) { 285 self.hash = nil // trie modified, hash needs to be re-calculated on demand 286 287 if len(path) == 0 { 288 self.entries[256] = nil 289 return 290 } 291 292 b := path[0] 293 entry := self.entries[b] 294 if entry == nil { 295 return 296 } 297 if entry.Path == path { 298 self.entries[b] = nil 299 return 300 } 301 302 epl := len(entry.Path) 303 if (entry.ContentType == ManifestType) && (len(path) >= epl) && (path[:epl] == entry.Path) { 304 if self.loadSubTrie(entry, quitC) != nil { 305 return 306 } 307 entry.subtrie.deleteEntry(path[epl:], quitC) 308 entry.Hash = "" 309 // remove subtree if it has less than 2 elements 310 cnt, lastentry := entry.subtrie.getCountLast() 311 if cnt < 2 { 312 if lastentry != nil { 313 lastentry.Path = entry.Path + lastentry.Path 314 } 315 self.entries[b] = lastentry 316 } 317 } 318 } 319 320 func (self *manifestTrie) recalcAndStore() error { 321 if self.hash != nil { 322 return nil 323 } 324 325 var buffer bytes.Buffer 326 buffer.WriteString(`{"entries":[`) 327 328 list := &Manifest{} 329 for _, entry := range self.entries { 330 if entry != nil { 331 if entry.Hash == "" { // TODO: paralellize 332 err := entry.subtrie.recalcAndStore() 333 if err != nil { 334 return err 335 } 336 entry.Hash = entry.subtrie.hash.String() 337 } 338 list.Entries = append(list.Entries, entry.ManifestEntry) 339 } 340 341 } 342 343 manifest, err := json.Marshal(list) 344 if err != nil { 345 return err 346 } 347 348 sr := bytes.NewReader(manifest) 349 wg := &sync.WaitGroup{} 350 key, err2 := self.dpa.Store(sr, int64(len(manifest)), wg, nil) 351 wg.Wait() 352 self.hash = key 353 return err2 354 } 355 356 func (self *manifestTrie) loadSubTrie(entry *manifestTrieEntry, quitC chan bool) (err error) { 357 if entry.subtrie == nil { 358 hash := common.Hex2Bytes(entry.Hash) 359 entry.subtrie, err = loadManifest(self.dpa, hash, quitC) 360 entry.Hash = "" // might not match, should be recalculated 361 } 362 return 363 } 364 365 func (self *manifestTrie) listWithPrefixInt(prefix, rp string, quitC chan bool, cb func(entry *manifestTrieEntry, suffix string)) error { 366 plen := len(prefix) 367 var start, stop int 368 if plen == 0 { 369 start = 0 370 stop = 256 371 } else { 372 start = int(prefix[0]) 373 stop = start 374 } 375 376 for i := start; i <= stop; i++ { 377 select { 378 case <-quitC: 379 return fmt.Errorf("aborted") 380 default: 381 } 382 entry := self.entries[i] 383 if entry != nil { 384 epl := len(entry.Path) 385 if entry.ContentType == ManifestType { 386 l := plen 387 if epl < l { 388 l = epl 389 } 390 if prefix[:l] == entry.Path[:l] { 391 err := self.loadSubTrie(entry, quitC) 392 if err != nil { 393 return err 394 } 395 err = entry.subtrie.listWithPrefixInt(prefix[l:], rp+entry.Path[l:], quitC, cb) 396 if err != nil { 397 return err 398 } 399 } 400 } else { 401 if (epl >= plen) && (prefix == entry.Path[:plen]) { 402 cb(entry, rp+entry.Path[plen:]) 403 } 404 } 405 } 406 } 407 return nil 408 } 409 410 func (self *manifestTrie) listWithPrefix(prefix string, quitC chan bool, cb func(entry *manifestTrieEntry, suffix string)) (err error) { 411 return self.listWithPrefixInt(prefix, "", quitC, cb) 412 } 413 414 func (self *manifestTrie) findPrefixOf(path string, quitC chan bool) (entry *manifestTrieEntry, pos int) { 415 416 log.Trace(fmt.Sprintf("findPrefixOf(%s)", path)) 417 418 if len(path) == 0 { 419 return self.entries[256], 0 420 } 421 422 //see if first char is in manifest entries 423 b := path[0] 424 entry = self.entries[b] 425 if entry == nil { 426 return self.entries[256], 0 427 } 428 429 epl := len(entry.Path) 430 log.Trace(fmt.Sprintf("path = %v entry.Path = %v epl = %v", path, entry.Path, epl)) 431 if len(path) <= epl { 432 if entry.Path[:len(path)] == path { 433 if entry.ContentType == ManifestType { 434 err := self.loadSubTrie(entry, quitC) 435 if err == nil && entry.subtrie != nil { 436 subentries := entry.subtrie.entries 437 for i := 0; i < len(subentries); i++ { 438 sub := subentries[i] 439 if sub != nil && sub.Path == "" { 440 return sub, len(path) 441 } 442 } 443 } 444 entry.Status = http.StatusMultipleChoices 445 } 446 pos = len(path) 447 return 448 } 449 return nil, 0 450 } 451 if path[:epl] == entry.Path { 452 log.Trace(fmt.Sprintf("entry.ContentType = %v", entry.ContentType)) 453 //the subentry is a manifest, load subtrie 454 if entry.ContentType == ManifestType && (strings.Contains(entry.Path, path) || strings.Contains(path, entry.Path)) { 455 err := self.loadSubTrie(entry, quitC) 456 if err != nil { 457 return nil, 0 458 } 459 sub, pos := entry.subtrie.findPrefixOf(path[epl:], quitC) 460 if sub != nil { 461 entry = sub 462 pos += epl 463 return sub, pos 464 } else if path == entry.Path { 465 entry.Status = http.StatusMultipleChoices 466 } 467 468 } else { 469 //entry is not a manifest, return it 470 if path != entry.Path { 471 return nil, 0 472 } 473 pos = epl 474 } 475 } 476 return 477 } 478 479 // file system manifest always contains regularized paths 480 // no leading or trailing slashes, only single slashes inside 481 func RegularSlashes(path string) (res string) { 482 for i := 0; i < len(path); i++ { 483 if (path[i] != '/') || ((i > 0) && (path[i-1] != '/')) { 484 res = res + path[i:i+1] 485 } 486 } 487 if (len(res) > 0) && (res[len(res)-1] == '/') { 488 res = res[:len(res)-1] 489 } 490 return 491 } 492 493 func (self *manifestTrie) getEntry(spath string) (entry *manifestTrieEntry, fullpath string) { 494 path := RegularSlashes(spath) 495 var pos int 496 quitC := make(chan bool) 497 entry, pos = self.findPrefixOf(path, quitC) 498 return entry, path[:pos] 499 }