github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/vfs/vfscache/vfscache.go (about) 1 // Package vfscache deals with caching of files locally for the VFS layer 2 package vfscache 3 4 import ( 5 "context" 6 "os" 7 "path" 8 "path/filepath" 9 "runtime" 10 "sort" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/djherbis/times" 16 "github.com/pkg/errors" 17 "github.com/rclone/rclone/fs" 18 fscache "github.com/rclone/rclone/fs/cache" 19 "github.com/rclone/rclone/fs/config" 20 "github.com/rclone/rclone/fs/operations" 21 "github.com/rclone/rclone/vfs/vfscommon" 22 ) 23 24 // Cache opened files 25 type Cache struct { 26 fremote fs.Fs // fs for the remote we are caching 27 fcache fs.Fs // fs for the cache directory 28 opt *vfscommon.Options // vfs Options 29 root string // root of the cache directory 30 itemMu sync.Mutex // protects the following variables 31 item map[string]*cacheItem // files/directories in the cache 32 used int64 // total size of files in the cache 33 } 34 35 // cacheItem is stored in the item map 36 type cacheItem struct { 37 opens int // number of times file is open 38 atime time.Time // last time file was accessed 39 isFile bool // if this is a file or a directory 40 size int64 // size of the cached item 41 } 42 43 // newCacheItem returns an item for the cache 44 func newCacheItem(isFile bool) *cacheItem { 45 return &cacheItem{atime: time.Now(), isFile: isFile} 46 } 47 48 // New creates a new cache heirachy for fremote 49 // 50 // This starts background goroutines which can be cancelled with the 51 // context passed in. 52 func New(ctx context.Context, fremote fs.Fs, opt *vfscommon.Options) (*Cache, error) { 53 fRoot := filepath.FromSlash(fremote.Root()) 54 if runtime.GOOS == "windows" { 55 if strings.HasPrefix(fRoot, `\\?`) { 56 fRoot = fRoot[3:] 57 } 58 fRoot = strings.Replace(fRoot, ":", "", -1) 59 } 60 root := filepath.Join(config.CacheDir, "vfs", fremote.Name(), fRoot) 61 fs.Debugf(nil, "vfs cache root is %q", root) 62 63 fcache, err := fscache.Get(root) 64 if err != nil { 65 return nil, errors.Wrap(err, "failed to create cache remote") 66 } 67 68 c := &Cache{ 69 fremote: fremote, 70 fcache: fcache, 71 opt: opt, 72 root: root, 73 item: make(map[string]*cacheItem), 74 } 75 76 go c.cleaner(ctx) 77 78 return c, nil 79 } 80 81 // clean returns the cleaned version of name for use in the index map 82 // 83 // name should be a remote path not an osPath 84 func clean(name string) string { 85 name = strings.Trim(name, "/") 86 name = path.Clean(name) 87 if name == "." || name == "/" { 88 name = "" 89 } 90 return name 91 } 92 93 // ToOSPath turns a remote relative name into an OS path in the cache 94 func (c *Cache) ToOSPath(name string) string { 95 return filepath.Join(c.root, filepath.FromSlash(name)) 96 } 97 98 // Mkdir makes the directory for name in the cache and returns an os 99 // path for the file 100 // 101 // name should be a remote path not an osPath 102 func (c *Cache) Mkdir(name string) (string, error) { 103 parent := vfscommon.FindParent(name) 104 leaf := path.Base(name) 105 parentPath := c.ToOSPath(parent) 106 err := os.MkdirAll(parentPath, 0700) 107 if err != nil { 108 return "", errors.Wrap(err, "make cache directory failed") 109 } 110 c.cacheDir(parent) 111 return filepath.Join(parentPath, leaf), nil 112 } 113 114 // _get gets name from the cache or creates a new one 115 // 116 // It returns the item and found as to whether this item was found in 117 // the cache (or just created). 118 // 119 // name should be a remote path not an osPath 120 // 121 // must be called with itemMu held 122 func (c *Cache) _get(isFile bool, name string) (item *cacheItem, found bool) { 123 item = c.item[name] 124 found = item != nil 125 if !found { 126 item = newCacheItem(isFile) 127 c.item[name] = item 128 } 129 return item, found 130 } 131 132 // Opens returns the number of opens that are on the file 133 // 134 // name should be a remote path not an osPath 135 func (c *Cache) Opens(name string) int { 136 name = clean(name) 137 c.itemMu.Lock() 138 defer c.itemMu.Unlock() 139 item := c.item[name] 140 if item == nil { 141 return 0 142 } 143 return item.opens 144 } 145 146 // get gets name from the cache or creates a new one 147 // 148 // name should be a remote path not an osPath 149 func (c *Cache) get(name string) *cacheItem { 150 name = clean(name) 151 c.itemMu.Lock() 152 item, _ := c._get(true, name) 153 c.itemMu.Unlock() 154 return item 155 } 156 157 // updateStat sets the atime of the name to that passed in if it is 158 // newer than the existing or there isn't an existing time. 159 // 160 // it also sets the size 161 // 162 // name should be a remote path not an osPath 163 func (c *Cache) updateStat(name string, when time.Time, size int64) { 164 name = clean(name) 165 c.itemMu.Lock() 166 item, found := c._get(true, name) 167 if !found || when.Sub(item.atime) > 0 { 168 fs.Debugf(name, "updateTime: setting atime to %v", when) 169 item.atime = when 170 } 171 item.size = size 172 c.itemMu.Unlock() 173 } 174 175 // _open marks name as open, must be called with the lock held 176 // 177 // name should be a remote path not an osPath 178 func (c *Cache) _open(isFile bool, name string) { 179 for { 180 item, _ := c._get(isFile, name) 181 item.opens++ 182 item.atime = time.Now() 183 if name == "" { 184 break 185 } 186 isFile = false 187 name = vfscommon.FindParent(name) 188 } 189 } 190 191 // Open marks name as open 192 // 193 // name should be a remote path not an osPath 194 func (c *Cache) Open(name string) { 195 name = clean(name) 196 c.itemMu.Lock() 197 c._open(true, name) 198 c.itemMu.Unlock() 199 } 200 201 // cacheDir marks a directory and its parents as being in the cache 202 // 203 // name should be a remote path not an osPath 204 func (c *Cache) cacheDir(name string) { 205 name = clean(name) 206 c.itemMu.Lock() 207 defer c.itemMu.Unlock() 208 for { 209 item := c.item[name] 210 if item != nil { 211 break 212 } 213 c.item[name] = newCacheItem(false) 214 if name == "" { 215 break 216 } 217 name = vfscommon.FindParent(name) 218 } 219 } 220 221 // Exists checks to see if the file exists in the cache or not 222 func (c *Cache) Exists(name string) bool { 223 osPath := c.ToOSPath(name) 224 fi, err := os.Stat(osPath) 225 if err != nil { 226 return false 227 } 228 // checks for non-regular files (e.g. directories, symlinks, devices, etc.) 229 if !fi.Mode().IsRegular() { 230 return false 231 } 232 return true 233 } 234 235 // Rename the file in cache 236 func (c *Cache) Rename(name string, newName string) (err error) { 237 osOldPath := c.ToOSPath(name) 238 osNewPath := c.ToOSPath(newName) 239 sfi, err := os.Stat(osOldPath) 240 if err != nil { 241 return errors.Wrapf(err, "Failed to stat source: %s", osOldPath) 242 } 243 if !sfi.Mode().IsRegular() { 244 // cannot copy non-regular files (e.g., directories, symlinks, devices, etc.) 245 return errors.Errorf("Non-regular source file: %s (%q)", sfi.Name(), sfi.Mode().String()) 246 } 247 dfi, err := os.Stat(osNewPath) 248 if err != nil { 249 if !os.IsNotExist(err) { 250 return errors.Wrapf(err, "Failed to stat destination: %s", osNewPath) 251 } 252 parent := vfscommon.OsFindParent(osNewPath) 253 err = os.MkdirAll(parent, 0700) 254 if err != nil { 255 return errors.Wrapf(err, "Failed to create parent dir: %s", parent) 256 } 257 } else { 258 if !(dfi.Mode().IsRegular()) { 259 return errors.Errorf("Non-regular destination file: %s (%q)", dfi.Name(), dfi.Mode().String()) 260 } 261 if os.SameFile(sfi, dfi) { 262 return nil 263 } 264 } 265 if err = os.Rename(osOldPath, osNewPath); err != nil { 266 return errors.Wrapf(err, "Failed to rename in cache: %s to %s", osOldPath, osNewPath) 267 } 268 // Rename the cache item 269 c.itemMu.Lock() 270 if oldItem, ok := c.item[name]; ok { 271 c.item[newName] = oldItem 272 delete(c.item, name) 273 } 274 c.itemMu.Unlock() 275 fs.Infof(name, "Renamed in cache") 276 return nil 277 } 278 279 // _close marks name as closed - must be called with the lock held 280 func (c *Cache) _close(isFile bool, name string) { 281 for { 282 item, _ := c._get(isFile, name) 283 item.opens-- 284 item.atime = time.Now() 285 if item.opens < 0 { 286 fs.Errorf(name, "cache: double close") 287 } 288 osPath := c.ToOSPath(name) 289 fi, err := os.Stat(osPath) 290 // Update the size on close 291 if err == nil && !fi.IsDir() { 292 item.size = fi.Size() 293 } 294 if name == "" { 295 break 296 } 297 isFile = false 298 name = vfscommon.FindParent(name) 299 } 300 } 301 302 // Close marks name as closed 303 // 304 // name should be a remote path not an osPath 305 func (c *Cache) Close(name string) { 306 name = clean(name) 307 c.itemMu.Lock() 308 c._close(true, name) 309 c.itemMu.Unlock() 310 } 311 312 // Remove should be called if name is deleted 313 func (c *Cache) Remove(name string) { 314 osPath := c.ToOSPath(name) 315 err := os.Remove(osPath) 316 if err != nil && !os.IsNotExist(err) { 317 fs.Errorf(name, "Failed to remove from cache: %v", err) 318 } else { 319 fs.Infof(name, "Removed from cache") 320 } 321 } 322 323 // removeDir should be called if dir is deleted and returns true if 324 // the directory is gone. 325 func (c *Cache) removeDir(dir string) bool { 326 osPath := c.ToOSPath(dir) 327 err := os.Remove(osPath) 328 if err == nil || os.IsNotExist(err) { 329 if err == nil { 330 fs.Debugf(dir, "Removed empty directory") 331 } 332 return true 333 } 334 if !os.IsExist(err) { 335 fs.Errorf(dir, "Failed to remove cached dir: %v", err) 336 } 337 return false 338 } 339 340 // SetModTime should be called to set the modification time of the cache file 341 func (c *Cache) SetModTime(name string, modTime time.Time) { 342 osPath := c.ToOSPath(name) 343 err := os.Chtimes(osPath, modTime, modTime) 344 if err != nil { 345 fs.Errorf(name, "Failed to set modification time of cached file: %v", err) 346 } 347 } 348 349 // CleanUp empties the cache of everything 350 func (c *Cache) CleanUp() error { 351 return os.RemoveAll(c.root) 352 } 353 354 // walk walks the cache calling the function 355 func (c *Cache) walk(fn func(osPath string, fi os.FileInfo, name string) error) error { 356 return filepath.Walk(c.root, func(osPath string, fi os.FileInfo, err error) error { 357 if err != nil { 358 return err 359 } 360 // Find path relative to the cache root 361 name, err := filepath.Rel(c.root, osPath) 362 if err != nil { 363 return errors.Wrap(err, "filepath.Rel failed in walk") 364 } 365 if name == "." { 366 name = "" 367 } 368 // And convert into slashes 369 name = filepath.ToSlash(name) 370 371 return fn(osPath, fi, name) 372 }) 373 } 374 375 // updateStats walks the cache updating any atimes and sizes it finds 376 // 377 // it also updates used 378 func (c *Cache) updateStats() error { 379 var newUsed int64 380 err := c.walk(func(osPath string, fi os.FileInfo, name string) error { 381 if !fi.IsDir() { 382 // Update the atime with that of the file 383 atime := times.Get(fi).AccessTime() 384 c.updateStat(name, atime, fi.Size()) 385 newUsed += fi.Size() 386 } else { 387 c.cacheDir(name) 388 } 389 return nil 390 }) 391 c.itemMu.Lock() 392 c.used = newUsed 393 c.itemMu.Unlock() 394 return err 395 } 396 397 // purgeOld gets rid of any files that are over age 398 func (c *Cache) purgeOld(maxAge time.Duration) { 399 c._purgeOld(maxAge, c.Remove) 400 } 401 402 func (c *Cache) _purgeOld(maxAge time.Duration, remove func(name string)) { 403 c.itemMu.Lock() 404 defer c.itemMu.Unlock() 405 cutoff := time.Now().Add(-maxAge) 406 for name, item := range c.item { 407 if item.isFile && item.opens == 0 { 408 // If not locked and access time too long ago - delete the file 409 dt := item.atime.Sub(cutoff) 410 // fs.Debugf(name, "atime=%v cutoff=%v, dt=%v", item.atime, cutoff, dt) 411 if dt < 0 { 412 remove(name) 413 // Remove the entry 414 delete(c.item, name) 415 } 416 } 417 } 418 } 419 420 // Purge any empty directories 421 func (c *Cache) purgeEmptyDirs() { 422 c._purgeEmptyDirs(c.removeDir) 423 } 424 425 func (c *Cache) _purgeEmptyDirs(removeDir func(name string) bool) { 426 c.itemMu.Lock() 427 defer c.itemMu.Unlock() 428 var dirs []string 429 for name, item := range c.item { 430 if !item.isFile && item.opens == 0 { 431 dirs = append(dirs, name) 432 } 433 } 434 // remove empty directories in reverse alphabetical order 435 sort.Strings(dirs) 436 for i := len(dirs) - 1; i >= 0; i-- { 437 dir := dirs[i] 438 // Remove the entry 439 if removeDir(dir) { 440 delete(c.item, dir) 441 } 442 } 443 } 444 445 // This is a cacheItem with a name for sorting 446 type cacheNamedItem struct { 447 name string 448 item *cacheItem 449 } 450 type cacheNamedItems []cacheNamedItem 451 452 func (v cacheNamedItems) Len() int { return len(v) } 453 func (v cacheNamedItems) Swap(i, j int) { v[i], v[j] = v[j], v[i] } 454 func (v cacheNamedItems) Less(i, j int) bool { return v[i].item.atime.Before(v[j].item.atime) } 455 456 // Remove any files that are over quota starting from the 457 // oldest first 458 func (c *Cache) purgeOverQuota(quota int64) { 459 c._purgeOverQuota(quota, c.Remove) 460 } 461 462 func (c *Cache) _purgeOverQuota(quota int64, remove func(name string)) { 463 c.itemMu.Lock() 464 defer c.itemMu.Unlock() 465 466 if quota <= 0 || c.used < quota { 467 return 468 } 469 470 var items cacheNamedItems 471 472 // Make a slice of unused files 473 for name, item := range c.item { 474 if item.isFile && item.opens == 0 { 475 items = append(items, cacheNamedItem{ 476 name: name, 477 item: item, 478 }) 479 } 480 } 481 sort.Sort(items) 482 483 // Remove items until the quota is OK 484 for _, item := range items { 485 if c.used < quota { 486 break 487 } 488 remove(item.name) 489 // Remove the entry 490 delete(c.item, item.name) 491 c.used -= item.item.size 492 } 493 } 494 495 // clean empties the cache of stuff if it can 496 func (c *Cache) clean() { 497 // Cache may be empty so end 498 _, err := os.Stat(c.root) 499 if os.IsNotExist(err) { 500 return 501 } 502 503 c.itemMu.Lock() 504 oldItems, oldUsed := len(c.item), fs.SizeSuffix(c.used) 505 c.itemMu.Unlock() 506 507 // first walk the FS to update the atimes and sizes 508 err = c.updateStats() 509 if err != nil { 510 fs.Errorf(nil, "Error traversing cache %q: %v", c.root, err) 511 } 512 513 // Remove any files that are over age 514 c.purgeOld(c.opt.CacheMaxAge) 515 516 // Now remove any files that are over quota starting from the 517 // oldest first 518 c.purgeOverQuota(int64(c.opt.CacheMaxSize)) 519 520 // Remove any empty directories 521 c.purgeEmptyDirs() 522 523 // Stats 524 c.itemMu.Lock() 525 newItems, newUsed := len(c.item), fs.SizeSuffix(c.used) 526 c.itemMu.Unlock() 527 528 fs.Infof(nil, "Cleaned the cache: objects %d (was %d), total size %v (was %v)", newItems, oldItems, newUsed, oldUsed) 529 } 530 531 // cleaner calls clean at regular intervals 532 // 533 // doesn't return until context is cancelled 534 func (c *Cache) cleaner(ctx context.Context) { 535 if c.opt.CachePollInterval <= 0 { 536 fs.Debugf(nil, "Cache cleaning thread disabled because poll interval <= 0") 537 return 538 } 539 // Start cleaning the cache immediately 540 c.clean() 541 // Then every interval specified 542 timer := time.NewTicker(c.opt.CachePollInterval) 543 defer timer.Stop() 544 for { 545 select { 546 case <-timer.C: 547 c.clean() 548 case <-ctx.Done(): 549 fs.Debugf(nil, "cache cleaner exiting") 550 return 551 } 552 } 553 } 554 555 // copy an object to or from the remote while accounting for it 556 func copyObj(f fs.Fs, dst fs.Object, remote string, src fs.Object) (newDst fs.Object, err error) { 557 if operations.NeedTransfer(context.TODO(), dst, src) { 558 newDst, err = operations.Copy(context.TODO(), f, dst, remote, src) 559 } else { 560 newDst = dst 561 } 562 return newDst, err 563 } 564 565 // Check the local file is up to date in the cache 566 func (c *Cache) Check(ctx context.Context, o fs.Object, remote string) error { 567 cacheObj, err := c.fcache.NewObject(ctx, remote) 568 if err == nil && cacheObj != nil { 569 _, err = copyObj(c.fcache, cacheObj, remote, o) 570 if err != nil { 571 return errors.Wrap(err, "failed to update cached file") 572 } 573 } 574 return nil 575 } 576 577 // Fetch fetches the object to the cache file 578 func (c *Cache) Fetch(ctx context.Context, o fs.Object, remote string) error { 579 _, err := copyObj(c.fcache, nil, remote, o) 580 return err 581 } 582 583 // Store stores the local cache file to the remote object, returning 584 // the new remote object. objOld is the old object if known. 585 func (c *Cache) Store(ctx context.Context, objOld fs.Object, remote string) (fs.Object, error) { 586 // Transfer the temp file to the remote 587 cacheObj, err := c.fcache.NewObject(ctx, remote) 588 if err != nil { 589 return nil, errors.Wrap(err, "failed to find cache file") 590 } 591 592 if objOld != nil { 593 remote = objOld.Remote() // use the path of the actual object if available 594 } 595 o, err := copyObj(c.fremote, objOld, remote, cacheObj) 596 if err != nil { 597 return nil, errors.Wrap(err, "failed to transfer file from cache to remote") 598 } 599 return o, nil 600 }