github.com/xhghs/rclone@v1.51.1-0.20200430155106-e186a28cced8/backend/cache/storage_persistent.go (about) 1 // +build !plan9 2 3 package cache 4 5 import ( 6 "bytes" 7 "context" 8 "encoding/binary" 9 "encoding/json" 10 "fmt" 11 "io/ioutil" 12 "os" 13 "path" 14 "strconv" 15 "strings" 16 "sync" 17 "time" 18 19 bolt "github.com/etcd-io/bbolt" 20 "github.com/pkg/errors" 21 "github.com/rclone/rclone/fs" 22 "github.com/rclone/rclone/fs/walk" 23 ) 24 25 // Constants 26 const ( 27 RootBucket = "root" 28 RootTsBucket = "rootTs" 29 DataTsBucket = "dataTs" 30 tempBucket = "pending" 31 ) 32 33 // Features flags for this storage type 34 type Features struct { 35 PurgeDb bool // purge the db before starting 36 DbWaitTime time.Duration // time to wait for DB to be available 37 } 38 39 var boltMap = make(map[string]*Persistent) 40 var boltMapMx sync.Mutex 41 42 // GetPersistent returns a single instance for the specific store 43 func GetPersistent(dbPath, chunkPath string, f *Features) (*Persistent, error) { 44 // write lock to create one 45 boltMapMx.Lock() 46 defer boltMapMx.Unlock() 47 if b, ok := boltMap[dbPath]; ok { 48 if !b.open { 49 err := b.connect() 50 if err != nil { 51 return nil, err 52 } 53 } 54 return b, nil 55 } 56 57 bb, err := newPersistent(dbPath, chunkPath, f) 58 if err != nil { 59 return nil, err 60 } 61 boltMap[dbPath] = bb 62 return boltMap[dbPath], nil 63 } 64 65 type chunkInfo struct { 66 Path string 67 Offset int64 68 Size int64 69 } 70 71 type tempUploadInfo struct { 72 DestPath string 73 AddedOn time.Time 74 Started bool 75 } 76 77 // String representation of a tempUploadInfo 78 func (t *tempUploadInfo) String() string { 79 return fmt.Sprintf("%v - %v (%v)", t.DestPath, t.Started, t.AddedOn) 80 } 81 82 // Persistent is a wrapper of persistent storage for a bolt.DB file 83 type Persistent struct { 84 dbPath string 85 dataPath string 86 open bool 87 db *bolt.DB 88 cleanupMux sync.Mutex 89 tempQueueMux sync.Mutex 90 features *Features 91 } 92 93 // newPersistent builds a new wrapper and connects to the bolt.DB file 94 func newPersistent(dbPath, chunkPath string, f *Features) (*Persistent, error) { 95 b := &Persistent{ 96 dbPath: dbPath, 97 dataPath: chunkPath, 98 features: f, 99 } 100 101 err := b.connect() 102 if err != nil { 103 fs.Errorf(dbPath, "Error opening storage cache. Is there another rclone running on the same remote? %v", err) 104 return nil, err 105 } 106 107 return b, nil 108 } 109 110 // String will return a human friendly string for this DB (currently the dbPath) 111 func (b *Persistent) String() string { 112 return "<Cache DB> " + b.dbPath 113 } 114 115 // connect creates a connection to the configured file 116 // refreshDb will delete the file before to create an empty DB if it's set to true 117 func (b *Persistent) connect() error { 118 var err error 119 120 err = os.MkdirAll(b.dataPath, os.ModePerm) 121 if err != nil { 122 return errors.Wrapf(err, "failed to create a data directory %q", b.dataPath) 123 } 124 b.db, err = bolt.Open(b.dbPath, 0644, &bolt.Options{Timeout: b.features.DbWaitTime}) 125 if err != nil { 126 return errors.Wrapf(err, "failed to open a cache connection to %q", b.dbPath) 127 } 128 if b.features.PurgeDb { 129 b.Purge() 130 } 131 _ = b.db.Update(func(tx *bolt.Tx) error { 132 _, _ = tx.CreateBucketIfNotExists([]byte(RootBucket)) 133 _, _ = tx.CreateBucketIfNotExists([]byte(RootTsBucket)) 134 _, _ = tx.CreateBucketIfNotExists([]byte(DataTsBucket)) 135 _, _ = tx.CreateBucketIfNotExists([]byte(tempBucket)) 136 137 return nil 138 }) 139 140 b.open = true 141 return nil 142 } 143 144 // getBucket prepares and cleans a specific path of the form: /var/tmp and will iterate through each path component 145 // to get to the nested bucket of the final part (in this example: tmp) 146 func (b *Persistent) getBucket(dir string, createIfMissing bool, tx *bolt.Tx) *bolt.Bucket { 147 cleanPath(dir) 148 149 entries := strings.FieldsFunc(dir, func(c rune) bool { 150 // cover Windows where rclone still uses '/' as path separator 151 // this should be safe as '/' is not a valid Windows character 152 return (os.PathSeparator == c || c == rune('/')) 153 }) 154 bucket := tx.Bucket([]byte(RootBucket)) 155 156 for _, entry := range entries { 157 if createIfMissing { 158 bucket, _ = bucket.CreateBucketIfNotExists([]byte(entry)) 159 } else { 160 bucket = bucket.Bucket([]byte(entry)) 161 } 162 163 if bucket == nil { 164 return nil 165 } 166 } 167 168 return bucket 169 } 170 171 // GetDir will retrieve data of a cached directory 172 func (b *Persistent) GetDir(remote string) (*Directory, error) { 173 cd := &Directory{} 174 175 err := b.db.View(func(tx *bolt.Tx) error { 176 bucket := b.getBucket(remote, false, tx) 177 if bucket == nil { 178 return errors.Errorf("couldn't open bucket (%v)", remote) 179 } 180 181 data := bucket.Get([]byte(".")) 182 if data != nil { 183 return json.Unmarshal(data, cd) 184 } 185 186 return errors.Errorf("%v not found", remote) 187 }) 188 189 return cd, err 190 } 191 192 // AddDir will update a CachedDirectory metadata and all its entries 193 func (b *Persistent) AddDir(cachedDir *Directory) error { 194 return b.AddBatchDir([]*Directory{cachedDir}) 195 } 196 197 // AddBatchDir will update a list of CachedDirectory metadata and all their entries 198 func (b *Persistent) AddBatchDir(cachedDirs []*Directory) error { 199 if len(cachedDirs) == 0 { 200 return nil 201 } 202 203 return b.db.Update(func(tx *bolt.Tx) error { 204 var bucket *bolt.Bucket 205 if cachedDirs[0].Dir == "" { 206 bucket = tx.Bucket([]byte(RootBucket)) 207 } else { 208 bucket = b.getBucket(cachedDirs[0].Dir, true, tx) 209 } 210 if bucket == nil { 211 return errors.Errorf("couldn't open bucket (%v)", cachedDirs[0].Dir) 212 } 213 214 for _, cachedDir := range cachedDirs { 215 var b *bolt.Bucket 216 var err error 217 if cachedDir.Name == "" { 218 b = bucket 219 } else { 220 b, err = bucket.CreateBucketIfNotExists([]byte(cachedDir.Name)) 221 } 222 if err != nil { 223 return err 224 } 225 226 encoded, err := json.Marshal(cachedDir) 227 if err != nil { 228 return errors.Errorf("couldn't marshal object (%v): %v", cachedDir, err) 229 } 230 err = b.Put([]byte("."), encoded) 231 if err != nil { 232 return err 233 } 234 } 235 return nil 236 }) 237 } 238 239 // GetDirEntries will return a CachedDirectory, its list of dir entries and/or an error if it encountered issues 240 func (b *Persistent) GetDirEntries(cachedDir *Directory) (fs.DirEntries, error) { 241 var dirEntries fs.DirEntries 242 243 err := b.db.View(func(tx *bolt.Tx) error { 244 bucket := b.getBucket(cachedDir.abs(), false, tx) 245 if bucket == nil { 246 return errors.Errorf("couldn't open bucket (%v)", cachedDir.abs()) 247 } 248 249 val := bucket.Get([]byte(".")) 250 if val != nil { 251 err := json.Unmarshal(val, cachedDir) 252 if err != nil { 253 return errors.Errorf("error during unmarshalling obj: %v", err) 254 } 255 } else { 256 return errors.Errorf("missing cached dir: %v", cachedDir) 257 } 258 259 c := bucket.Cursor() 260 for k, v := c.First(); k != nil; k, v = c.Next() { 261 // ignore metadata key: . 262 if bytes.Equal(k, []byte(".")) { 263 continue 264 } 265 entryPath := path.Join(cachedDir.Remote(), string(k)) 266 267 if v == nil { // directory 268 // we try to find a cached meta for the dir 269 currentBucket := c.Bucket().Bucket(k) 270 if currentBucket == nil { 271 return errors.Errorf("couldn't open bucket (%v)", string(k)) 272 } 273 274 metaKey := currentBucket.Get([]byte(".")) 275 d := NewDirectory(cachedDir.CacheFs, entryPath) 276 if metaKey != nil { //if we don't find it, we create an empty dir 277 err := json.Unmarshal(metaKey, d) 278 if err != nil { // if even this fails, we fallback to an empty dir 279 fs.Debugf(string(k), "error during unmarshalling obj: %v", err) 280 } 281 } 282 283 dirEntries = append(dirEntries, d) 284 } else { // object 285 o := NewObject(cachedDir.CacheFs, entryPath) 286 err := json.Unmarshal(v, o) 287 if err != nil { 288 fs.Debugf(string(k), "error during unmarshalling obj: %v", err) 289 continue 290 } 291 292 dirEntries = append(dirEntries, o) 293 } 294 } 295 296 return nil 297 }) 298 299 return dirEntries, err 300 } 301 302 // RemoveDir will delete a CachedDirectory, all its objects and all the chunks stored for it 303 func (b *Persistent) RemoveDir(fp string) error { 304 var err error 305 parentDir, dirName := path.Split(fp) 306 if fp == "" { 307 err = b.db.Update(func(tx *bolt.Tx) error { 308 err := tx.DeleteBucket([]byte(RootBucket)) 309 if err != nil { 310 fs.Debugf(fp, "couldn't delete from cache: %v", err) 311 return err 312 } 313 _, _ = tx.CreateBucketIfNotExists([]byte(RootBucket)) 314 return nil 315 }) 316 } else { 317 err = b.db.Update(func(tx *bolt.Tx) error { 318 bucket := b.getBucket(cleanPath(parentDir), false, tx) 319 if bucket == nil { 320 return errors.Errorf("couldn't open bucket (%v)", fp) 321 } 322 // delete the cached dir 323 err := bucket.DeleteBucket([]byte(cleanPath(dirName))) 324 if err != nil { 325 fs.Debugf(fp, "couldn't delete from cache: %v", err) 326 } 327 return nil 328 }) 329 } 330 331 // delete chunks on disk 332 // safe to ignore as the files might not have been open 333 if err == nil { 334 _ = os.RemoveAll(path.Join(b.dataPath, fp)) 335 _ = os.MkdirAll(b.dataPath, os.ModePerm) 336 } 337 338 return err 339 } 340 341 // ExpireDir will flush a CachedDirectory and all its objects from the objects 342 // chunks will remain as they are 343 func (b *Persistent) ExpireDir(cd *Directory) error { 344 t := time.Now().Add(time.Duration(-cd.CacheFs.opt.InfoAge)) 345 cd.CacheTs = &t 346 347 // expire all parents 348 return b.db.Update(func(tx *bolt.Tx) error { 349 // expire all the parents 350 currentDir := cd.abs() 351 for { // until we get to the root 352 bucket := b.getBucket(currentDir, false, tx) 353 if bucket != nil { 354 val := bucket.Get([]byte(".")) 355 if val != nil { 356 cd2 := &Directory{CacheFs: cd.CacheFs} 357 err := json.Unmarshal(val, cd2) 358 if err == nil { 359 fs.Debugf(cd, "cache: expired %v", currentDir) 360 cd2.CacheTs = &t 361 enc2, _ := json.Marshal(cd2) 362 _ = bucket.Put([]byte("."), enc2) 363 } 364 } 365 } 366 if currentDir == "" { 367 break 368 } 369 currentDir = cleanPath(path.Dir(currentDir)) 370 } 371 return nil 372 }) 373 } 374 375 // GetObject will return a CachedObject from its parent directory or an error if it doesn't find it 376 func (b *Persistent) GetObject(cachedObject *Object) (err error) { 377 return b.db.View(func(tx *bolt.Tx) error { 378 bucket := b.getBucket(cachedObject.Dir, false, tx) 379 if bucket == nil { 380 return errors.Errorf("couldn't open parent bucket for %v", cachedObject.Dir) 381 } 382 val := bucket.Get([]byte(cachedObject.Name)) 383 if val != nil { 384 return json.Unmarshal(val, cachedObject) 385 } 386 return errors.Errorf("couldn't find object (%v)", cachedObject.Name) 387 }) 388 } 389 390 // AddObject will create a cached object in its parent directory 391 func (b *Persistent) AddObject(cachedObject *Object) error { 392 return b.db.Update(func(tx *bolt.Tx) error { 393 bucket := b.getBucket(cachedObject.Dir, true, tx) 394 if bucket == nil { 395 return errors.Errorf("couldn't open parent bucket for %v", cachedObject) 396 } 397 // cache Object Info 398 encoded, err := json.Marshal(cachedObject) 399 if err != nil { 400 return errors.Errorf("couldn't marshal object (%v) info: %v", cachedObject, err) 401 } 402 err = bucket.Put([]byte(cachedObject.Name), encoded) 403 if err != nil { 404 return errors.Errorf("couldn't cache object (%v) info: %v", cachedObject, err) 405 } 406 return nil 407 }) 408 } 409 410 // RemoveObject will delete a single cached object and all the chunks which belong to it 411 func (b *Persistent) RemoveObject(fp string) error { 412 parentDir, objName := path.Split(fp) 413 return b.db.Update(func(tx *bolt.Tx) error { 414 bucket := b.getBucket(cleanPath(parentDir), false, tx) 415 if bucket == nil { 416 return errors.Errorf("couldn't open parent bucket for %v", cleanPath(parentDir)) 417 } 418 err := bucket.Delete([]byte(cleanPath(objName))) 419 if err != nil { 420 fs.Debugf(fp, "couldn't delete obj from storage: %v", err) 421 } 422 // delete chunks on disk 423 // safe to ignore as the file might not have been open 424 _ = os.RemoveAll(path.Join(b.dataPath, fp)) 425 return nil 426 }) 427 } 428 429 // ExpireObject will flush an Object and all its data if desired 430 func (b *Persistent) ExpireObject(co *Object, withData bool) error { 431 co.CacheTs = time.Now().Add(time.Duration(-co.CacheFs.opt.InfoAge)) 432 err := b.AddObject(co) 433 if withData { 434 _ = os.RemoveAll(path.Join(b.dataPath, co.abs())) 435 } 436 return err 437 } 438 439 // HasEntry confirms the existence of a single entry (dir or object) 440 func (b *Persistent) HasEntry(remote string) bool { 441 dir, name := path.Split(remote) 442 dir = cleanPath(dir) 443 name = cleanPath(name) 444 445 err := b.db.View(func(tx *bolt.Tx) error { 446 bucket := b.getBucket(dir, false, tx) 447 if bucket == nil { 448 return errors.Errorf("couldn't open parent bucket for %v", remote) 449 } 450 if f := bucket.Bucket([]byte(name)); f != nil { 451 return nil 452 } 453 if f := bucket.Get([]byte(name)); f != nil { 454 return nil 455 } 456 457 return errors.Errorf("couldn't find object (%v)", remote) 458 }) 459 if err == nil { 460 return true 461 } 462 return false 463 } 464 465 // HasChunk confirms the existence of a single chunk of an object 466 func (b *Persistent) HasChunk(cachedObject *Object, offset int64) bool { 467 fp := path.Join(b.dataPath, cachedObject.abs(), strconv.FormatInt(offset, 10)) 468 if _, err := os.Stat(fp); !os.IsNotExist(err) { 469 return true 470 } 471 return false 472 } 473 474 // GetChunk will retrieve a single chunk which belongs to a cached object or an error if it doesn't find it 475 func (b *Persistent) GetChunk(cachedObject *Object, offset int64) ([]byte, error) { 476 var data []byte 477 478 fp := path.Join(b.dataPath, cachedObject.abs(), strconv.FormatInt(offset, 10)) 479 data, err := ioutil.ReadFile(fp) 480 if err != nil { 481 return nil, err 482 } 483 484 return data, err 485 } 486 487 // AddChunk adds a new chunk of a cached object 488 func (b *Persistent) AddChunk(fp string, data []byte, offset int64) error { 489 _ = os.MkdirAll(path.Join(b.dataPath, fp), os.ModePerm) 490 491 filePath := path.Join(b.dataPath, fp, strconv.FormatInt(offset, 10)) 492 err := ioutil.WriteFile(filePath, data, os.ModePerm) 493 if err != nil { 494 return err 495 } 496 497 return b.db.Update(func(tx *bolt.Tx) error { 498 tsBucket := tx.Bucket([]byte(DataTsBucket)) 499 ts := time.Now() 500 found := false 501 502 // delete (older) timestamps for the same object 503 c := tsBucket.Cursor() 504 for k, v := c.First(); k != nil; k, v = c.Next() { 505 var ci chunkInfo 506 err = json.Unmarshal(v, &ci) 507 if err != nil { 508 continue 509 } 510 if ci.Path == fp && ci.Offset == offset { 511 if tsInCache := time.Unix(0, btoi(k)); tsInCache.After(ts) && !found { 512 found = true 513 continue 514 } 515 err := c.Delete() 516 if err != nil { 517 fs.Debugf(fp, "failed to clean chunk: %v", err) 518 } 519 } 520 } 521 // don't overwrite if a newer one is already there 522 if found { 523 return nil 524 } 525 enc, err := json.Marshal(chunkInfo{Path: fp, Offset: offset, Size: int64(len(data))}) 526 if err != nil { 527 fs.Debugf(fp, "failed to timestamp chunk: %v", err) 528 } 529 err = tsBucket.Put(itob(ts.UnixNano()), enc) 530 if err != nil { 531 fs.Debugf(fp, "failed to timestamp chunk: %v", err) 532 } 533 return nil 534 }) 535 } 536 537 // CleanChunksByAge will cleanup on a cron basis 538 func (b *Persistent) CleanChunksByAge(chunkAge time.Duration) { 539 // NOOP 540 } 541 542 // CleanChunksByNeed is a noop for this implementation 543 func (b *Persistent) CleanChunksByNeed(offset int64) { 544 // noop: we want to clean a Bolt DB by time only 545 } 546 547 // CleanChunksBySize will cleanup chunks after the total size passes a certain point 548 func (b *Persistent) CleanChunksBySize(maxSize int64) { 549 b.cleanupMux.Lock() 550 defer b.cleanupMux.Unlock() 551 var cntChunks int 552 var roughlyCleaned fs.SizeSuffix 553 554 err := b.db.Update(func(tx *bolt.Tx) error { 555 dataTsBucket := tx.Bucket([]byte(DataTsBucket)) 556 if dataTsBucket == nil { 557 return errors.Errorf("Couldn't open (%v) bucket", DataTsBucket) 558 } 559 // iterate through ts 560 c := dataTsBucket.Cursor() 561 totalSize := int64(0) 562 for k, v := c.First(); k != nil; k, v = c.Next() { 563 var ci chunkInfo 564 err := json.Unmarshal(v, &ci) 565 if err != nil { 566 continue 567 } 568 569 totalSize += ci.Size 570 } 571 572 if totalSize > maxSize { 573 needToClean := totalSize - maxSize 574 roughlyCleaned = fs.SizeSuffix(needToClean) 575 for k, v := c.First(); k != nil; k, v = c.Next() { 576 var ci chunkInfo 577 err := json.Unmarshal(v, &ci) 578 if err != nil { 579 continue 580 } 581 // delete this ts entry 582 err = c.Delete() 583 if err != nil { 584 fs.Errorf(ci.Path, "failed deleting chunk ts during cleanup (%v): %v", ci.Offset, err) 585 continue 586 } 587 err = os.Remove(path.Join(b.dataPath, ci.Path, strconv.FormatInt(ci.Offset, 10))) 588 if err == nil { 589 cntChunks++ 590 needToClean -= ci.Size 591 if needToClean <= 0 { 592 break 593 } 594 } 595 } 596 } 597 if cntChunks > 0 { 598 fs.Infof("cache-cleanup", "chunks %v, est. size: %v", cntChunks, roughlyCleaned.String()) 599 600 } 601 return nil 602 }) 603 604 if err != nil { 605 if err == bolt.ErrDatabaseNotOpen { 606 // we're likely a late janitor and we need to end quietly as there's no guarantee of what exists anymore 607 return 608 } 609 fs.Errorf("cache", "cleanup failed: %v", err) 610 } 611 } 612 613 // Stats returns a go map with the stats key values 614 func (b *Persistent) Stats() (map[string]map[string]interface{}, error) { 615 r := make(map[string]map[string]interface{}) 616 r["data"] = make(map[string]interface{}) 617 r["data"]["oldest-ts"] = time.Now() 618 r["data"]["oldest-file"] = "" 619 r["data"]["newest-ts"] = time.Now() 620 r["data"]["newest-file"] = "" 621 r["data"]["total-chunks"] = 0 622 r["data"]["total-size"] = int64(0) 623 r["files"] = make(map[string]interface{}) 624 r["files"]["oldest-ts"] = time.Now() 625 r["files"]["oldest-name"] = "" 626 r["files"]["newest-ts"] = time.Now() 627 r["files"]["newest-name"] = "" 628 r["files"]["total-files"] = 0 629 630 _ = b.db.View(func(tx *bolt.Tx) error { 631 dataTsBucket := tx.Bucket([]byte(DataTsBucket)) 632 rootTsBucket := tx.Bucket([]byte(RootTsBucket)) 633 634 var totalDirs int 635 var totalFiles int 636 _ = b.iterateBuckets(tx.Bucket([]byte(RootBucket)), func(name string) { 637 totalDirs++ 638 }, func(key string, val []byte) { 639 totalFiles++ 640 }) 641 r["files"]["total-dir"] = totalDirs 642 r["files"]["total-files"] = totalFiles 643 644 c := dataTsBucket.Cursor() 645 646 totalChunks := 0 647 totalSize := int64(0) 648 for k, v := c.First(); k != nil; k, v = c.Next() { 649 var ci chunkInfo 650 err := json.Unmarshal(v, &ci) 651 if err != nil { 652 continue 653 } 654 totalChunks++ 655 totalSize += ci.Size 656 } 657 r["data"]["total-chunks"] = totalChunks 658 r["data"]["total-size"] = totalSize 659 660 if k, v := c.First(); k != nil { 661 var ci chunkInfo 662 _ = json.Unmarshal(v, &ci) 663 r["data"]["oldest-ts"] = time.Unix(0, btoi(k)) 664 r["data"]["oldest-file"] = ci.Path 665 } 666 if k, v := c.Last(); k != nil { 667 var ci chunkInfo 668 _ = json.Unmarshal(v, &ci) 669 r["data"]["newest-ts"] = time.Unix(0, btoi(k)) 670 r["data"]["newest-file"] = ci.Path 671 } 672 673 c = rootTsBucket.Cursor() 674 if k, v := c.First(); k != nil { 675 // split to get (abs path - offset) 676 r["files"]["oldest-ts"] = time.Unix(0, btoi(k)) 677 r["files"]["oldest-name"] = string(v) 678 } 679 if k, v := c.Last(); k != nil { 680 r["files"]["newest-ts"] = time.Unix(0, btoi(k)) 681 r["files"]["newest-name"] = string(v) 682 } 683 684 return nil 685 }) 686 687 return r, nil 688 } 689 690 // Purge will flush the entire cache 691 func (b *Persistent) Purge() { 692 b.cleanupMux.Lock() 693 defer b.cleanupMux.Unlock() 694 695 _ = b.db.Update(func(tx *bolt.Tx) error { 696 _ = tx.DeleteBucket([]byte(RootBucket)) 697 _ = tx.DeleteBucket([]byte(RootTsBucket)) 698 _ = tx.DeleteBucket([]byte(DataTsBucket)) 699 700 _, _ = tx.CreateBucketIfNotExists([]byte(RootBucket)) 701 _, _ = tx.CreateBucketIfNotExists([]byte(RootTsBucket)) 702 _, _ = tx.CreateBucketIfNotExists([]byte(DataTsBucket)) 703 704 return nil 705 }) 706 707 err := os.RemoveAll(b.dataPath) 708 if err != nil { 709 fs.Errorf(b, "issue removing data folder: %v", err) 710 } 711 err = os.MkdirAll(b.dataPath, os.ModePerm) 712 if err != nil { 713 fs.Errorf(b, "issue removing data folder: %v", err) 714 } 715 } 716 717 // GetChunkTs retrieves the current timestamp of this chunk 718 func (b *Persistent) GetChunkTs(path string, offset int64) (time.Time, error) { 719 var t time.Time 720 721 err := b.db.View(func(tx *bolt.Tx) error { 722 tsBucket := tx.Bucket([]byte(DataTsBucket)) 723 c := tsBucket.Cursor() 724 for k, v := c.First(); k != nil; k, v = c.Next() { 725 var ci chunkInfo 726 err := json.Unmarshal(v, &ci) 727 if err != nil { 728 continue 729 } 730 if ci.Path == path && ci.Offset == offset { 731 t = time.Unix(0, btoi(k)) 732 return nil 733 } 734 } 735 return errors.Errorf("not found %v-%v", path, offset) 736 }) 737 738 return t, err 739 } 740 741 func (b *Persistent) iterateBuckets(buk *bolt.Bucket, bucketFn func(name string), kvFn func(key string, val []byte)) error { 742 err := b.db.View(func(tx *bolt.Tx) error { 743 var c *bolt.Cursor 744 if buk == nil { 745 c = tx.Cursor() 746 } else { 747 c = buk.Cursor() 748 } 749 for k, v := c.First(); k != nil; k, v = c.Next() { 750 if v == nil { 751 var buk2 *bolt.Bucket 752 if buk == nil { 753 buk2 = tx.Bucket(k) 754 } else { 755 buk2 = buk.Bucket(k) 756 } 757 758 bucketFn(string(k)) 759 _ = b.iterateBuckets(buk2, bucketFn, kvFn) 760 } else { 761 kvFn(string(k), v) 762 } 763 } 764 return nil 765 }) 766 767 return err 768 } 769 770 func (b *Persistent) dumpRoot() string { 771 var itBuckets func(buk *bolt.Bucket) map[string]interface{} 772 773 itBuckets = func(buk *bolt.Bucket) map[string]interface{} { 774 m := make(map[string]interface{}) 775 c := buk.Cursor() 776 for k, v := c.First(); k != nil; k, v = c.Next() { 777 if v == nil { 778 buk2 := buk.Bucket(k) 779 m[string(k)] = itBuckets(buk2) 780 } else { 781 m[string(k)] = "-" 782 } 783 } 784 return m 785 } 786 var mm map[string]interface{} 787 _ = b.db.View(func(tx *bolt.Tx) error { 788 mm = itBuckets(tx.Bucket([]byte(RootBucket))) 789 return nil 790 }) 791 raw, _ := json.MarshalIndent(mm, "", " ") 792 return string(raw) 793 } 794 795 // addPendingUpload adds a new file to the pending queue of uploads 796 func (b *Persistent) addPendingUpload(destPath string, started bool) error { 797 return b.db.Update(func(tx *bolt.Tx) error { 798 bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket)) 799 if err != nil { 800 return errors.Errorf("couldn't bucket for %v", tempBucket) 801 } 802 tempObj := &tempUploadInfo{ 803 DestPath: destPath, 804 AddedOn: time.Now(), 805 Started: started, 806 } 807 808 // cache Object Info 809 encoded, err := json.Marshal(tempObj) 810 if err != nil { 811 return errors.Errorf("couldn't marshal object (%v) info: %v", destPath, err) 812 } 813 err = bucket.Put([]byte(destPath), encoded) 814 if err != nil { 815 return errors.Errorf("couldn't cache object (%v) info: %v", destPath, err) 816 } 817 818 return nil 819 }) 820 } 821 822 // getPendingUpload returns the next file from the pending queue of uploads 823 func (b *Persistent) getPendingUpload(inRoot string, waitTime time.Duration) (destPath string, err error) { 824 b.tempQueueMux.Lock() 825 defer b.tempQueueMux.Unlock() 826 827 err = b.db.Update(func(tx *bolt.Tx) error { 828 bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket)) 829 if err != nil { 830 return errors.Errorf("couldn't bucket for %v", tempBucket) 831 } 832 833 c := bucket.Cursor() 834 for k, v := c.Seek([]byte(inRoot)); k != nil && bytes.HasPrefix(k, []byte(inRoot)); k, v = c.Next() { 835 //for k, v := c.First(); k != nil; k, v = c.Next() { 836 var tempObj = &tempUploadInfo{} 837 err = json.Unmarshal(v, tempObj) 838 if err != nil { 839 fs.Errorf(b, "failed to read pending upload: %v", err) 840 continue 841 } 842 // skip over started uploads 843 if tempObj.Started || time.Now().Before(tempObj.AddedOn.Add(waitTime)) { 844 continue 845 } 846 847 tempObj.Started = true 848 v2, err := json.Marshal(tempObj) 849 if err != nil { 850 fs.Errorf(b, "failed to update pending upload: %v", err) 851 continue 852 } 853 err = bucket.Put(k, v2) 854 if err != nil { 855 fs.Errorf(b, "failed to update pending upload: %v", err) 856 continue 857 } 858 859 destPath = tempObj.DestPath 860 return nil 861 } 862 863 return errors.Errorf("no pending upload found") 864 }) 865 866 return destPath, err 867 } 868 869 // SearchPendingUpload returns the file info from the pending queue of uploads 870 func (b *Persistent) SearchPendingUpload(remote string) (started bool, err error) { 871 err = b.db.View(func(tx *bolt.Tx) error { 872 bucket := tx.Bucket([]byte(tempBucket)) 873 if bucket == nil { 874 return errors.Errorf("couldn't bucket for %v", tempBucket) 875 } 876 877 var tempObj = &tempUploadInfo{} 878 v := bucket.Get([]byte(remote)) 879 err = json.Unmarshal(v, tempObj) 880 if err != nil { 881 return errors.Errorf("pending upload (%v) not found %v", remote, err) 882 } 883 884 started = tempObj.Started 885 return nil 886 }) 887 888 return started, err 889 } 890 891 // searchPendingUploadFromDir files currently pending upload from a single dir 892 func (b *Persistent) searchPendingUploadFromDir(dir string) (remotes []string, err error) { 893 err = b.db.View(func(tx *bolt.Tx) error { 894 bucket := tx.Bucket([]byte(tempBucket)) 895 if bucket == nil { 896 return errors.Errorf("couldn't bucket for %v", tempBucket) 897 } 898 899 c := bucket.Cursor() 900 for k, v := c.First(); k != nil; k, v = c.Next() { 901 var tempObj = &tempUploadInfo{} 902 err = json.Unmarshal(v, tempObj) 903 if err != nil { 904 fs.Errorf(b, "failed to read pending upload: %v", err) 905 continue 906 } 907 parentDir := cleanPath(path.Dir(tempObj.DestPath)) 908 if dir == parentDir { 909 remotes = append(remotes, tempObj.DestPath) 910 } 911 } 912 913 return nil 914 }) 915 916 return remotes, err 917 } 918 919 func (b *Persistent) rollbackPendingUpload(remote string) error { 920 b.tempQueueMux.Lock() 921 defer b.tempQueueMux.Unlock() 922 923 return b.db.Update(func(tx *bolt.Tx) error { 924 bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket)) 925 if err != nil { 926 return errors.Errorf("couldn't bucket for %v", tempBucket) 927 } 928 var tempObj = &tempUploadInfo{} 929 v := bucket.Get([]byte(remote)) 930 err = json.Unmarshal(v, tempObj) 931 if err != nil { 932 return errors.Errorf("pending upload (%v) not found %v", remote, err) 933 } 934 tempObj.Started = false 935 v2, err := json.Marshal(tempObj) 936 if err != nil { 937 return errors.Errorf("pending upload not updated %v", err) 938 } 939 err = bucket.Put([]byte(tempObj.DestPath), v2) 940 if err != nil { 941 return errors.Errorf("pending upload not updated %v", err) 942 } 943 return nil 944 }) 945 } 946 947 func (b *Persistent) removePendingUpload(remote string) error { 948 b.tempQueueMux.Lock() 949 defer b.tempQueueMux.Unlock() 950 951 return b.db.Update(func(tx *bolt.Tx) error { 952 bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket)) 953 if err != nil { 954 return errors.Errorf("couldn't bucket for %v", tempBucket) 955 } 956 return bucket.Delete([]byte(remote)) 957 }) 958 } 959 960 // updatePendingUpload allows to update an existing item in the queue while checking if it's not started in the same 961 // transaction. If it is started, it will not allow the update 962 func (b *Persistent) updatePendingUpload(remote string, fn func(item *tempUploadInfo) error) error { 963 b.tempQueueMux.Lock() 964 defer b.tempQueueMux.Unlock() 965 966 return b.db.Update(func(tx *bolt.Tx) error { 967 bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket)) 968 if err != nil { 969 return errors.Errorf("couldn't bucket for %v", tempBucket) 970 } 971 972 var tempObj = &tempUploadInfo{} 973 v := bucket.Get([]byte(remote)) 974 err = json.Unmarshal(v, tempObj) 975 if err != nil { 976 return errors.Errorf("pending upload (%v) not found %v", remote, err) 977 } 978 if tempObj.Started { 979 return errors.Errorf("pending upload already started %v", remote) 980 } 981 err = fn(tempObj) 982 if err != nil { 983 return err 984 } 985 if remote != tempObj.DestPath { 986 err := bucket.Delete([]byte(remote)) 987 if err != nil { 988 return err 989 } 990 // if this is removed then the entry can be removed too 991 if tempObj.DestPath == "" { 992 return nil 993 } 994 } 995 v2, err := json.Marshal(tempObj) 996 if err != nil { 997 return errors.Errorf("pending upload not updated %v", err) 998 } 999 err = bucket.Put([]byte(tempObj.DestPath), v2) 1000 if err != nil { 1001 return errors.Errorf("pending upload not updated %v", err) 1002 } 1003 1004 return nil 1005 }) 1006 } 1007 1008 // SetPendingUploadToStarted is a way to mark an entry as started (even if it's not already) 1009 // TO BE USED IN TESTING ONLY 1010 func (b *Persistent) SetPendingUploadToStarted(remote string) error { 1011 return b.updatePendingUpload(remote, func(item *tempUploadInfo) error { 1012 item.Started = true 1013 return nil 1014 }) 1015 } 1016 1017 // ReconcileTempUploads will recursively look for all the files in the temp directory and add them to the queue 1018 func (b *Persistent) ReconcileTempUploads(ctx context.Context, cacheFs *Fs) error { 1019 return b.db.Update(func(tx *bolt.Tx) error { 1020 _ = tx.DeleteBucket([]byte(tempBucket)) 1021 bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket)) 1022 if err != nil { 1023 return err 1024 } 1025 1026 var queuedEntries []fs.Object 1027 err = walk.ListR(ctx, cacheFs.tempFs, "", true, -1, walk.ListObjects, func(entries fs.DirEntries) error { 1028 for _, o := range entries { 1029 if oo, ok := o.(fs.Object); ok { 1030 queuedEntries = append(queuedEntries, oo) 1031 } 1032 } 1033 return nil 1034 }) 1035 if err != nil { 1036 return err 1037 } 1038 1039 fs.Debugf(cacheFs, "reconciling temporary uploads") 1040 for _, queuedEntry := range queuedEntries { 1041 destPath := path.Join(cacheFs.Root(), queuedEntry.Remote()) 1042 tempObj := &tempUploadInfo{ 1043 DestPath: destPath, 1044 AddedOn: time.Now(), 1045 Started: false, 1046 } 1047 1048 // cache Object Info 1049 encoded, err := json.Marshal(tempObj) 1050 if err != nil { 1051 return errors.Errorf("couldn't marshal object (%v) info: %v", queuedEntry, err) 1052 } 1053 err = bucket.Put([]byte(destPath), encoded) 1054 if err != nil { 1055 return errors.Errorf("couldn't cache object (%v) info: %v", destPath, err) 1056 } 1057 fs.Debugf(cacheFs, "reconciled temporary upload: %v", destPath) 1058 } 1059 1060 return nil 1061 }) 1062 } 1063 1064 // PurgeTempUploads will remove all the pending uploads from the queue 1065 // TO BE USED IN TESTING ONLY 1066 func (b *Persistent) PurgeTempUploads() { 1067 b.tempQueueMux.Lock() 1068 defer b.tempQueueMux.Unlock() 1069 1070 _ = b.db.Update(func(tx *bolt.Tx) error { 1071 _ = tx.DeleteBucket([]byte(tempBucket)) 1072 _, _ = tx.CreateBucketIfNotExists([]byte(tempBucket)) 1073 return nil 1074 }) 1075 } 1076 1077 // Close should be called when the program ends gracefully 1078 func (b *Persistent) Close() { 1079 b.cleanupMux.Lock() 1080 defer b.cleanupMux.Unlock() 1081 1082 err := b.db.Close() 1083 if err != nil { 1084 fs.Errorf(b, "closing handle: %v", err) 1085 } 1086 b.open = false 1087 } 1088 1089 // itob returns an 8-byte big endian representation of v. 1090 func itob(v int64) []byte { 1091 b := make([]byte, 8) 1092 binary.BigEndian.PutUint64(b, uint64(v)) 1093 return b 1094 } 1095 1096 func btoi(d []byte) int64 { 1097 return int64(binary.BigEndian.Uint64(d)) 1098 }