github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/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 "github.com/pkg/errors" 20 "github.com/rclone/rclone/fs" 21 "github.com/rclone/rclone/fs/walk" 22 bolt "go.etcd.io/bbolt" 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 // addPendingUpload adds a new file to the pending queue of uploads 771 func (b *Persistent) addPendingUpload(destPath string, started bool) error { 772 return b.db.Update(func(tx *bolt.Tx) error { 773 bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket)) 774 if err != nil { 775 return errors.Errorf("couldn't bucket for %v", tempBucket) 776 } 777 tempObj := &tempUploadInfo{ 778 DestPath: destPath, 779 AddedOn: time.Now(), 780 Started: started, 781 } 782 783 // cache Object Info 784 encoded, err := json.Marshal(tempObj) 785 if err != nil { 786 return errors.Errorf("couldn't marshal object (%v) info: %v", destPath, err) 787 } 788 err = bucket.Put([]byte(destPath), encoded) 789 if err != nil { 790 return errors.Errorf("couldn't cache object (%v) info: %v", destPath, err) 791 } 792 793 return nil 794 }) 795 } 796 797 // getPendingUpload returns the next file from the pending queue of uploads 798 func (b *Persistent) getPendingUpload(inRoot string, waitTime time.Duration) (destPath string, err error) { 799 b.tempQueueMux.Lock() 800 defer b.tempQueueMux.Unlock() 801 802 err = b.db.Update(func(tx *bolt.Tx) error { 803 bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket)) 804 if err != nil { 805 return errors.Errorf("couldn't bucket for %v", tempBucket) 806 } 807 808 c := bucket.Cursor() 809 for k, v := c.Seek([]byte(inRoot)); k != nil && bytes.HasPrefix(k, []byte(inRoot)); k, v = c.Next() { 810 //for k, v := c.First(); k != nil; k, v = c.Next() { 811 var tempObj = &tempUploadInfo{} 812 err = json.Unmarshal(v, tempObj) 813 if err != nil { 814 fs.Errorf(b, "failed to read pending upload: %v", err) 815 continue 816 } 817 // skip over started uploads 818 if tempObj.Started || time.Now().Before(tempObj.AddedOn.Add(waitTime)) { 819 continue 820 } 821 822 tempObj.Started = true 823 v2, err := json.Marshal(tempObj) 824 if err != nil { 825 fs.Errorf(b, "failed to update pending upload: %v", err) 826 continue 827 } 828 err = bucket.Put(k, v2) 829 if err != nil { 830 fs.Errorf(b, "failed to update pending upload: %v", err) 831 continue 832 } 833 834 destPath = tempObj.DestPath 835 return nil 836 } 837 838 return errors.Errorf("no pending upload found") 839 }) 840 841 return destPath, err 842 } 843 844 // SearchPendingUpload returns the file info from the pending queue of uploads 845 func (b *Persistent) SearchPendingUpload(remote string) (started bool, err error) { 846 err = b.db.View(func(tx *bolt.Tx) error { 847 bucket := tx.Bucket([]byte(tempBucket)) 848 if bucket == nil { 849 return errors.Errorf("couldn't bucket for %v", tempBucket) 850 } 851 852 var tempObj = &tempUploadInfo{} 853 v := bucket.Get([]byte(remote)) 854 err = json.Unmarshal(v, tempObj) 855 if err != nil { 856 return errors.Errorf("pending upload (%v) not found %v", remote, err) 857 } 858 859 started = tempObj.Started 860 return nil 861 }) 862 863 return started, err 864 } 865 866 // searchPendingUploadFromDir files currently pending upload from a single dir 867 func (b *Persistent) searchPendingUploadFromDir(dir string) (remotes []string, err error) { 868 err = b.db.View(func(tx *bolt.Tx) error { 869 bucket := tx.Bucket([]byte(tempBucket)) 870 if bucket == nil { 871 return errors.Errorf("couldn't bucket for %v", tempBucket) 872 } 873 874 c := bucket.Cursor() 875 for k, v := c.First(); k != nil; k, v = c.Next() { 876 var tempObj = &tempUploadInfo{} 877 err = json.Unmarshal(v, tempObj) 878 if err != nil { 879 fs.Errorf(b, "failed to read pending upload: %v", err) 880 continue 881 } 882 parentDir := cleanPath(path.Dir(tempObj.DestPath)) 883 if dir == parentDir { 884 remotes = append(remotes, tempObj.DestPath) 885 } 886 } 887 888 return nil 889 }) 890 891 return remotes, err 892 } 893 894 func (b *Persistent) rollbackPendingUpload(remote string) error { 895 b.tempQueueMux.Lock() 896 defer b.tempQueueMux.Unlock() 897 898 return b.db.Update(func(tx *bolt.Tx) error { 899 bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket)) 900 if err != nil { 901 return errors.Errorf("couldn't bucket for %v", tempBucket) 902 } 903 var tempObj = &tempUploadInfo{} 904 v := bucket.Get([]byte(remote)) 905 err = json.Unmarshal(v, tempObj) 906 if err != nil { 907 return errors.Errorf("pending upload (%v) not found %v", remote, err) 908 } 909 tempObj.Started = false 910 v2, err := json.Marshal(tempObj) 911 if err != nil { 912 return errors.Errorf("pending upload not updated %v", err) 913 } 914 err = bucket.Put([]byte(tempObj.DestPath), v2) 915 if err != nil { 916 return errors.Errorf("pending upload not updated %v", err) 917 } 918 return nil 919 }) 920 } 921 922 func (b *Persistent) removePendingUpload(remote string) error { 923 b.tempQueueMux.Lock() 924 defer b.tempQueueMux.Unlock() 925 926 return b.db.Update(func(tx *bolt.Tx) error { 927 bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket)) 928 if err != nil { 929 return errors.Errorf("couldn't bucket for %v", tempBucket) 930 } 931 return bucket.Delete([]byte(remote)) 932 }) 933 } 934 935 // updatePendingUpload allows to update an existing item in the queue while checking if it's not started in the same 936 // transaction. If it is started, it will not allow the update 937 func (b *Persistent) updatePendingUpload(remote string, fn func(item *tempUploadInfo) error) error { 938 b.tempQueueMux.Lock() 939 defer b.tempQueueMux.Unlock() 940 941 return b.db.Update(func(tx *bolt.Tx) error { 942 bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket)) 943 if err != nil { 944 return errors.Errorf("couldn't bucket for %v", tempBucket) 945 } 946 947 var tempObj = &tempUploadInfo{} 948 v := bucket.Get([]byte(remote)) 949 err = json.Unmarshal(v, tempObj) 950 if err != nil { 951 return errors.Errorf("pending upload (%v) not found %v", remote, err) 952 } 953 if tempObj.Started { 954 return errors.Errorf("pending upload already started %v", remote) 955 } 956 err = fn(tempObj) 957 if err != nil { 958 return err 959 } 960 if remote != tempObj.DestPath { 961 err := bucket.Delete([]byte(remote)) 962 if err != nil { 963 return err 964 } 965 // if this is removed then the entry can be removed too 966 if tempObj.DestPath == "" { 967 return nil 968 } 969 } 970 v2, err := json.Marshal(tempObj) 971 if err != nil { 972 return errors.Errorf("pending upload not updated %v", err) 973 } 974 err = bucket.Put([]byte(tempObj.DestPath), v2) 975 if err != nil { 976 return errors.Errorf("pending upload not updated %v", err) 977 } 978 979 return nil 980 }) 981 } 982 983 // ReconcileTempUploads will recursively look for all the files in the temp directory and add them to the queue 984 func (b *Persistent) ReconcileTempUploads(ctx context.Context, cacheFs *Fs) error { 985 return b.db.Update(func(tx *bolt.Tx) error { 986 _ = tx.DeleteBucket([]byte(tempBucket)) 987 bucket, err := tx.CreateBucketIfNotExists([]byte(tempBucket)) 988 if err != nil { 989 return err 990 } 991 992 var queuedEntries []fs.Object 993 err = walk.ListR(ctx, cacheFs.tempFs, "", true, -1, walk.ListObjects, func(entries fs.DirEntries) error { 994 for _, o := range entries { 995 if oo, ok := o.(fs.Object); ok { 996 queuedEntries = append(queuedEntries, oo) 997 } 998 } 999 return nil 1000 }) 1001 if err != nil { 1002 return err 1003 } 1004 1005 fs.Debugf(cacheFs, "reconciling temporary uploads") 1006 for _, queuedEntry := range queuedEntries { 1007 destPath := path.Join(cacheFs.Root(), queuedEntry.Remote()) 1008 tempObj := &tempUploadInfo{ 1009 DestPath: destPath, 1010 AddedOn: time.Now(), 1011 Started: false, 1012 } 1013 1014 // cache Object Info 1015 encoded, err := json.Marshal(tempObj) 1016 if err != nil { 1017 return errors.Errorf("couldn't marshal object (%v) info: %v", queuedEntry, err) 1018 } 1019 err = bucket.Put([]byte(destPath), encoded) 1020 if err != nil { 1021 return errors.Errorf("couldn't cache object (%v) info: %v", destPath, err) 1022 } 1023 fs.Debugf(cacheFs, "reconciled temporary upload: %v", destPath) 1024 } 1025 1026 return nil 1027 }) 1028 } 1029 1030 // Close should be called when the program ends gracefully 1031 func (b *Persistent) Close() { 1032 b.cleanupMux.Lock() 1033 defer b.cleanupMux.Unlock() 1034 1035 err := b.db.Close() 1036 if err != nil { 1037 fs.Errorf(b, "closing handle: %v", err) 1038 } 1039 b.open = false 1040 } 1041 1042 // itob returns an 8-byte big endian representation of v. 1043 func itob(v int64) []byte { 1044 b := make([]byte, 8) 1045 binary.BigEndian.PutUint64(b, uint64(v)) 1046 return b 1047 } 1048 1049 func btoi(d []byte) int64 { 1050 return int64(binary.BigEndian.Uint64(d)) 1051 }