github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/fs/operations/operations.go (about) 1 // Package operations does generic operations on filesystems and objects 2 package operations 3 4 import ( 5 "bytes" 6 "context" 7 "encoding/csv" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "math/rand" 12 "path" 13 "path/filepath" 14 "sort" 15 "strconv" 16 "strings" 17 "sync" 18 "sync/atomic" 19 "time" 20 21 "github.com/ncw/rclone/fs" 22 "github.com/ncw/rclone/fs/accounting" 23 "github.com/ncw/rclone/fs/cache" 24 "github.com/ncw/rclone/fs/fserrors" 25 "github.com/ncw/rclone/fs/fshttp" 26 "github.com/ncw/rclone/fs/hash" 27 "github.com/ncw/rclone/fs/march" 28 "github.com/ncw/rclone/fs/object" 29 "github.com/ncw/rclone/fs/walk" 30 "github.com/ncw/rclone/lib/readers" 31 "github.com/pkg/errors" 32 "golang.org/x/sync/errgroup" 33 ) 34 35 // CheckHashes checks the two files to see if they have common 36 // known hash types and compares them 37 // 38 // Returns 39 // 40 // equal - which is equality of the hashes 41 // 42 // hash - the HashType. This is HashNone if either of the hashes were 43 // unset or a compatible hash couldn't be found. 44 // 45 // err - may return an error which will already have been logged 46 // 47 // If an error is returned it will return equal as false 48 func CheckHashes(ctx context.Context, src fs.ObjectInfo, dst fs.Object) (equal bool, ht hash.Type, err error) { 49 common := src.Fs().Hashes().Overlap(dst.Fs().Hashes()) 50 // fs.Debugf(nil, "Shared hashes: %v", common) 51 if common.Count() == 0 { 52 return true, hash.None, nil 53 } 54 ht = common.GetOne() 55 srcHash, err := src.Hash(ctx, ht) 56 if err != nil { 57 fs.CountError(err) 58 fs.Errorf(src, "Failed to calculate src hash: %v", err) 59 return false, ht, err 60 } 61 if srcHash == "" { 62 return true, hash.None, nil 63 } 64 dstHash, err := dst.Hash(ctx, ht) 65 if err != nil { 66 fs.CountError(err) 67 fs.Errorf(dst, "Failed to calculate dst hash: %v", err) 68 return false, ht, err 69 } 70 if dstHash == "" { 71 return true, hash.None, nil 72 } 73 if srcHash != dstHash { 74 fs.Debugf(src, "%v = %s (%v)", ht, srcHash, src.Fs()) 75 fs.Debugf(dst, "%v = %s (%v)", ht, dstHash, dst.Fs()) 76 } 77 return srcHash == dstHash, ht, nil 78 } 79 80 // Equal checks to see if the src and dst objects are equal by looking at 81 // size, mtime and hash 82 // 83 // If the src and dst size are different then it is considered to be 84 // not equal. If --size-only is in effect then this is the only check 85 // that is done. If --ignore-size is in effect then this check is 86 // skipped and the files are considered the same size. 87 // 88 // If the size is the same and the mtime is the same then it is 89 // considered to be equal. This check is skipped if using --checksum. 90 // 91 // If the size is the same and mtime is different, unreadable or 92 // --checksum is set and the hash is the same then the file is 93 // considered to be equal. In this case the mtime on the dst is 94 // updated if --checksum is not set. 95 // 96 // Otherwise the file is considered to be not equal including if there 97 // were errors reading info. 98 func Equal(ctx context.Context, src fs.ObjectInfo, dst fs.Object) bool { 99 return equal(ctx, src, dst, fs.Config.SizeOnly, fs.Config.CheckSum, !fs.Config.NoUpdateModTime) 100 } 101 102 // sizeDiffers compare the size of src and dst taking into account the 103 // various ways of ignoring sizes 104 func sizeDiffers(src, dst fs.ObjectInfo) bool { 105 if fs.Config.IgnoreSize || src.Size() < 0 || dst.Size() < 0 { 106 return false 107 } 108 return src.Size() != dst.Size() 109 } 110 111 var checksumWarning sync.Once 112 113 func equal(ctx context.Context, src fs.ObjectInfo, dst fs.Object, sizeOnly, checkSum, UpdateModTime bool) bool { 114 if sizeDiffers(src, dst) { 115 fs.Debugf(src, "Sizes differ (src %d vs dst %d)", src.Size(), dst.Size()) 116 return false 117 } 118 if sizeOnly { 119 fs.Debugf(src, "Sizes identical") 120 return true 121 } 122 123 // Assert: Size is equal or being ignored 124 125 // If checking checksum and not modtime 126 if checkSum { 127 // Check the hash 128 same, ht, _ := CheckHashes(ctx, src, dst) 129 if !same { 130 fs.Debugf(src, "%v differ", ht) 131 return false 132 } 133 if ht == hash.None { 134 checksumWarning.Do(func() { 135 fs.Logf(dst.Fs(), "--checksum is in use but the source and destination have no hashes in common; falling back to --size-only") 136 }) 137 fs.Debugf(src, "Size of src and dst objects identical") 138 } else { 139 fs.Debugf(src, "Size and %v of src and dst objects identical", ht) 140 } 141 return true 142 } 143 144 // Sizes the same so check the mtime 145 modifyWindow := fs.GetModifyWindow(src.Fs(), dst.Fs()) 146 if modifyWindow == fs.ModTimeNotSupported { 147 fs.Debugf(src, "Sizes identical") 148 return true 149 } 150 srcModTime := src.ModTime(ctx) 151 dstModTime := dst.ModTime(ctx) 152 dt := dstModTime.Sub(srcModTime) 153 if dt < modifyWindow && dt > -modifyWindow { 154 fs.Debugf(src, "Size and modification time the same (differ by %s, within tolerance %s)", dt, modifyWindow) 155 return true 156 } 157 158 fs.Debugf(src, "Modification times differ by %s: %v, %v", dt, srcModTime, dstModTime) 159 160 // Check if the hashes are the same 161 same, ht, _ := CheckHashes(ctx, src, dst) 162 if !same { 163 fs.Debugf(src, "%v differ", ht) 164 return false 165 } 166 if ht == hash.None { 167 // if couldn't check hash, return that they differ 168 return false 169 } 170 171 // mod time differs but hash is the same to reset mod time if required 172 if UpdateModTime { 173 if fs.Config.DryRun { 174 fs.Logf(src, "Not updating modification time as --dry-run") 175 } else { 176 // Size and hash the same but mtime different 177 // Error if objects are treated as immutable 178 if fs.Config.Immutable { 179 fs.Errorf(dst, "Timestamp mismatch between immutable objects") 180 return false 181 } 182 // Update the mtime of the dst object here 183 err := dst.SetModTime(ctx, srcModTime) 184 if err == fs.ErrorCantSetModTime { 185 fs.Debugf(dst, "src and dst identical but can't set mod time without re-uploading") 186 return false 187 } else if err == fs.ErrorCantSetModTimeWithoutDelete { 188 fs.Debugf(dst, "src and dst identical but can't set mod time without deleting and re-uploading") 189 // Remove the file if BackupDir isn't set. If BackupDir is set we would rather have the old file 190 // put in the BackupDir than deleted which is what will happen if we don't delete it. 191 if fs.Config.BackupDir == "" { 192 err = dst.Remove(ctx) 193 if err != nil { 194 fs.Errorf(dst, "failed to delete before re-upload: %v", err) 195 } 196 } 197 return false 198 } else if err != nil { 199 fs.CountError(err) 200 fs.Errorf(dst, "Failed to set modification time: %v", err) 201 } else { 202 fs.Infof(src, "Updated modification time in destination") 203 } 204 } 205 } 206 return true 207 } 208 209 // Used to remove a failed copy 210 // 211 // Returns whether the file was successfully removed or not 212 func removeFailedCopy(ctx context.Context, dst fs.Object) bool { 213 if dst == nil { 214 return false 215 } 216 fs.Infof(dst, "Removing failed copy") 217 removeErr := dst.Remove(ctx) 218 if removeErr != nil { 219 fs.Infof(dst, "Failed to remove failed copy: %s", removeErr) 220 return false 221 } 222 return true 223 } 224 225 // Wrapper to override the remote for an object 226 type overrideRemoteObject struct { 227 fs.Object 228 remote string 229 } 230 231 // Remote returns the overridden remote name 232 func (o *overrideRemoteObject) Remote() string { 233 return o.remote 234 } 235 236 // MimeType returns the mime type of the underlying object or "" if it 237 // can't be worked out 238 func (o *overrideRemoteObject) MimeType(ctx context.Context) string { 239 if do, ok := o.Object.(fs.MimeTyper); ok { 240 return do.MimeType(ctx) 241 } 242 return "" 243 } 244 245 // Check interface is satisfied 246 var _ fs.MimeTyper = (*overrideRemoteObject)(nil) 247 248 // Copy src object to dst or f if nil. If dst is nil then it uses 249 // remote as the name of the new object. 250 // 251 // It returns the destination object if possible. Note that this may 252 // be nil. 253 func Copy(ctx context.Context, f fs.Fs, dst fs.Object, remote string, src fs.Object) (newDst fs.Object, err error) { 254 accounting.Stats.Transferring(src.Remote()) 255 defer func() { 256 accounting.Stats.DoneTransferring(src.Remote(), err == nil) 257 }() 258 newDst = dst 259 if fs.Config.DryRun { 260 fs.Logf(src, "Not copying as --dry-run") 261 return newDst, nil 262 } 263 maxTries := fs.Config.LowLevelRetries 264 tries := 0 265 doUpdate := dst != nil 266 // work out which hash to use - limit to 1 hash in common 267 var common hash.Set 268 hashType := hash.None 269 if !fs.Config.SizeOnly { 270 common = src.Fs().Hashes().Overlap(f.Hashes()) 271 if common.Count() > 0 { 272 hashType = common.GetOne() 273 common = hash.Set(hashType) 274 } 275 } 276 hashOption := &fs.HashesOption{Hashes: common} 277 var actionTaken string 278 for { 279 // Try server side copy first - if has optional interface and 280 // is same underlying remote 281 actionTaken = "Copied (server side copy)" 282 if doCopy := f.Features().Copy; doCopy != nil && (SameConfig(src.Fs(), f) || (SameRemoteType(src.Fs(), f) && f.Features().ServerSideAcrossConfigs)) { 283 // Check transfer limit for server side copies 284 if fs.Config.MaxTransfer >= 0 && accounting.Stats.GetBytes() >= int64(fs.Config.MaxTransfer) { 285 return nil, accounting.ErrorMaxTransferLimitReached 286 } 287 newDst, err = doCopy(ctx, src, remote) 288 if err == nil { 289 dst = newDst 290 accounting.Stats.Bytes(dst.Size()) // account the bytes for the server side transfer 291 } 292 } else { 293 err = fs.ErrorCantCopy 294 } 295 // If can't server side copy, do it manually 296 if err == fs.ErrorCantCopy { 297 if doOpenWriterAt := f.Features().OpenWriterAt; doOpenWriterAt != nil && src.Size() >= int64(fs.Config.MultiThreadCutoff) && fs.Config.MultiThreadStreams > 1 { 298 // Number of streams proportional to size 299 streams := src.Size() / int64(fs.Config.MultiThreadCutoff) 300 // With maximum 301 if streams > int64(fs.Config.MultiThreadStreams) { 302 streams = int64(fs.Config.MultiThreadStreams) 303 } 304 if streams < 2 { 305 streams = 2 306 } 307 dst, err = multiThreadCopy(ctx, f, remote, src, int(streams)) 308 if doUpdate { 309 actionTaken = "Multi-thread Copied (replaced existing)" 310 } else { 311 actionTaken = "Multi-thread Copied (new)" 312 } 313 } else { 314 var in0 io.ReadCloser 315 in0, err = newReOpen(ctx, src, hashOption, nil, fs.Config.LowLevelRetries) 316 if err != nil { 317 err = errors.Wrap(err, "failed to open source object") 318 } else { 319 if src.Size() == -1 { 320 // -1 indicates unknown size. Use Rcat to handle both remotes supporting and not supporting PutStream. 321 if doUpdate { 322 actionTaken = "Copied (Rcat, replaced existing)" 323 } else { 324 actionTaken = "Copied (Rcat, new)" 325 } 326 dst, err = Rcat(ctx, f, remote, in0, src.ModTime(ctx)) 327 newDst = dst 328 } else { 329 in := accounting.NewAccount(in0, src).WithBuffer() // account and buffer the transfer 330 var wrappedSrc fs.ObjectInfo = src 331 // We try to pass the original object if possible 332 if src.Remote() != remote { 333 wrappedSrc = &overrideRemoteObject{Object: src, remote: remote} 334 } 335 if doUpdate { 336 actionTaken = "Copied (replaced existing)" 337 err = dst.Update(ctx, in, wrappedSrc, hashOption) 338 } else { 339 actionTaken = "Copied (new)" 340 dst, err = f.Put(ctx, in, wrappedSrc, hashOption) 341 } 342 closeErr := in.Close() 343 if err == nil { 344 newDst = dst 345 err = closeErr 346 } 347 } 348 } 349 } 350 } 351 tries++ 352 if tries >= maxTries { 353 break 354 } 355 // Retry if err returned a retry error 356 if fserrors.IsRetryError(err) || fserrors.ShouldRetry(err) { 357 fs.Debugf(src, "Received error: %v - low level retry %d/%d", err, tries, maxTries) 358 continue 359 } 360 // otherwise finish 361 break 362 } 363 if err != nil { 364 fs.CountError(err) 365 fs.Errorf(src, "Failed to copy: %v", err) 366 return newDst, err 367 } 368 369 // Verify sizes are the same after transfer 370 if sizeDiffers(src, dst) { 371 err = errors.Errorf("corrupted on transfer: sizes differ %d vs %d", src.Size(), dst.Size()) 372 fs.Errorf(dst, "%v", err) 373 fs.CountError(err) 374 removeFailedCopy(ctx, dst) 375 return newDst, err 376 } 377 378 // Verify hashes are the same after transfer - ignoring blank hashes 379 if !fs.Config.IgnoreChecksum && hashType != hash.None { 380 var srcSum string 381 srcSum, err = src.Hash(ctx, hashType) 382 if err != nil { 383 fs.CountError(err) 384 fs.Errorf(src, "Failed to read src hash: %v", err) 385 } else if srcSum != "" { 386 var dstSum string 387 dstSum, err = dst.Hash(ctx, hashType) 388 if err != nil { 389 fs.CountError(err) 390 fs.Errorf(dst, "Failed to read hash: %v", err) 391 } else if !hash.Equals(srcSum, dstSum) { 392 err = errors.Errorf("corrupted on transfer: %v hash differ %q vs %q", hashType, srcSum, dstSum) 393 fs.Errorf(dst, "%v", err) 394 fs.CountError(err) 395 removeFailedCopy(ctx, dst) 396 return newDst, err 397 } 398 } 399 } 400 401 fs.Infof(src, actionTaken) 402 return newDst, err 403 } 404 405 // SameObject returns true if src and dst could be pointing to the 406 // same object. 407 func SameObject(src, dst fs.Object) bool { 408 if !SameConfig(src.Fs(), dst.Fs()) { 409 return false 410 } 411 srcPath := path.Join(src.Fs().Root(), src.Remote()) 412 dstPath := path.Join(dst.Fs().Root(), dst.Remote()) 413 if dst.Fs().Features().CaseInsensitive { 414 srcPath = strings.ToLower(srcPath) 415 dstPath = strings.ToLower(dstPath) 416 } 417 return srcPath == dstPath 418 } 419 420 // Move src object to dst or fdst if nil. If dst is nil then it uses 421 // remote as the name of the new object. 422 // 423 // Note that you must check the destination does not exist before 424 // calling this and pass it as dst. If you pass dst=nil and the 425 // destination does exist then this may create duplicates or return 426 // errors. 427 // 428 // It returns the destination object if possible. Note that this may 429 // be nil. 430 func Move(ctx context.Context, fdst fs.Fs, dst fs.Object, remote string, src fs.Object) (newDst fs.Object, err error) { 431 accounting.Stats.Checking(src.Remote()) 432 defer func() { 433 accounting.Stats.DoneChecking(src.Remote()) 434 }() 435 newDst = dst 436 if fs.Config.DryRun { 437 fs.Logf(src, "Not moving as --dry-run") 438 return newDst, nil 439 } 440 // See if we have Move available 441 if doMove := fdst.Features().Move; doMove != nil && (SameConfig(src.Fs(), fdst) || (SameRemoteType(src.Fs(), fdst) && fdst.Features().ServerSideAcrossConfigs)) { 442 // Delete destination if it exists and is not the same file as src (could be same file while seemingly different if the remote is case insensitive) 443 if dst != nil && !SameObject(src, dst) { 444 err = DeleteFile(ctx, dst) 445 if err != nil { 446 return newDst, err 447 } 448 } 449 // Move dst <- src 450 newDst, err = doMove(ctx, src, remote) 451 switch err { 452 case nil: 453 fs.Infof(src, "Moved (server side)") 454 return newDst, nil 455 case fs.ErrorCantMove: 456 fs.Debugf(src, "Can't move, switching to copy") 457 default: 458 fs.CountError(err) 459 fs.Errorf(src, "Couldn't move: %v", err) 460 return newDst, err 461 } 462 } 463 // Move not found or didn't work so copy dst <- src 464 newDst, err = Copy(ctx, fdst, dst, remote, src) 465 if err != nil { 466 fs.Errorf(src, "Not deleting source as copy failed: %v", err) 467 return newDst, err 468 } 469 // Delete src if no error on copy 470 return newDst, DeleteFile(ctx, src) 471 } 472 473 // CanServerSideMove returns true if fdst support server side moves or 474 // server side copies 475 // 476 // Some remotes simulate rename by server-side copy and delete, so include 477 // remotes that implements either Mover or Copier. 478 func CanServerSideMove(fdst fs.Fs) bool { 479 canMove := fdst.Features().Move != nil 480 canCopy := fdst.Features().Copy != nil 481 return canMove || canCopy 482 } 483 484 // SuffixName adds the current --suffix to the remote, obeying 485 // --suffix-keep-extension if set 486 func SuffixName(remote string) string { 487 if fs.Config.Suffix == "" { 488 return remote 489 } 490 if fs.Config.SuffixKeepExtension { 491 ext := path.Ext(remote) 492 base := remote[:len(remote)-len(ext)] 493 return base + fs.Config.Suffix + ext 494 } 495 return remote + fs.Config.Suffix 496 } 497 498 // DeleteFileWithBackupDir deletes a single file respecting --dry-run 499 // and accumulating stats and errors. 500 // 501 // If backupDir is set then it moves the file to there instead of 502 // deleting 503 func DeleteFileWithBackupDir(ctx context.Context, dst fs.Object, backupDir fs.Fs) (err error) { 504 accounting.Stats.Checking(dst.Remote()) 505 numDeletes := accounting.Stats.Deletes(1) 506 if fs.Config.MaxDelete != -1 && numDeletes > fs.Config.MaxDelete { 507 return fserrors.FatalError(errors.New("--max-delete threshold reached")) 508 } 509 action, actioned, actioning := "delete", "Deleted", "deleting" 510 if backupDir != nil { 511 action, actioned, actioning = "move into backup dir", "Moved into backup dir", "moving into backup dir" 512 } 513 if fs.Config.DryRun { 514 fs.Logf(dst, "Not %s as --dry-run", actioning) 515 } else if backupDir != nil { 516 err = MoveBackupDir(ctx, backupDir, dst) 517 } else { 518 err = dst.Remove(ctx) 519 } 520 if err != nil { 521 fs.CountError(err) 522 fs.Errorf(dst, "Couldn't %s: %v", action, err) 523 } else if !fs.Config.DryRun { 524 fs.Infof(dst, actioned) 525 } 526 accounting.Stats.DoneChecking(dst.Remote()) 527 return err 528 } 529 530 // DeleteFile deletes a single file respecting --dry-run and accumulating stats and errors. 531 // 532 // If useBackupDir is set and --backup-dir is in effect then it moves 533 // the file to there instead of deleting 534 func DeleteFile(ctx context.Context, dst fs.Object) (err error) { 535 return DeleteFileWithBackupDir(ctx, dst, nil) 536 } 537 538 // DeleteFilesWithBackupDir removes all the files passed in the 539 // channel 540 // 541 // If backupDir is set the files will be placed into that directory 542 // instead of being deleted. 543 func DeleteFilesWithBackupDir(ctx context.Context, toBeDeleted fs.ObjectsChan, backupDir fs.Fs) error { 544 var wg sync.WaitGroup 545 wg.Add(fs.Config.Transfers) 546 var errorCount int32 547 var fatalErrorCount int32 548 549 for i := 0; i < fs.Config.Transfers; i++ { 550 go func() { 551 defer wg.Done() 552 for dst := range toBeDeleted { 553 err := DeleteFileWithBackupDir(ctx, dst, backupDir) 554 if err != nil { 555 atomic.AddInt32(&errorCount, 1) 556 if fserrors.IsFatalError(err) { 557 fs.Errorf(nil, "Got fatal error on delete: %s", err) 558 atomic.AddInt32(&fatalErrorCount, 1) 559 return 560 } 561 } 562 } 563 }() 564 } 565 fs.Infof(nil, "Waiting for deletions to finish") 566 wg.Wait() 567 if errorCount > 0 { 568 err := errors.Errorf("failed to delete %d files", errorCount) 569 if fatalErrorCount > 0 { 570 return fserrors.FatalError(err) 571 } 572 return err 573 } 574 return nil 575 } 576 577 // DeleteFiles removes all the files passed in the channel 578 func DeleteFiles(ctx context.Context, toBeDeleted fs.ObjectsChan) error { 579 return DeleteFilesWithBackupDir(ctx, toBeDeleted, nil) 580 } 581 582 // SameRemoteType returns true if fdst and fsrc are the same type 583 func SameRemoteType(fdst, fsrc fs.Info) bool { 584 return fmt.Sprintf("%T", fdst) == fmt.Sprintf("%T", fsrc) 585 } 586 587 // SameConfig returns true if fdst and fsrc are using the same config 588 // file entry 589 func SameConfig(fdst, fsrc fs.Info) bool { 590 return fdst.Name() == fsrc.Name() 591 } 592 593 // Same returns true if fdst and fsrc point to the same underlying Fs 594 func Same(fdst, fsrc fs.Info) bool { 595 return SameConfig(fdst, fsrc) && strings.Trim(fdst.Root(), "/") == strings.Trim(fsrc.Root(), "/") 596 } 597 598 // fixRoot returns the Root with a trailing / if not empty. It is 599 // aware of case insensitive filesystems. 600 func fixRoot(f fs.Info) string { 601 s := strings.Trim(filepath.ToSlash(f.Root()), "/") 602 if s != "" { 603 s += "/" 604 } 605 if f.Features().CaseInsensitive { 606 s = strings.ToLower(s) 607 } 608 return s 609 } 610 611 // Overlapping returns true if fdst and fsrc point to the same 612 // underlying Fs and they overlap. 613 func Overlapping(fdst, fsrc fs.Info) bool { 614 if !SameConfig(fdst, fsrc) { 615 return false 616 } 617 fdstRoot := fixRoot(fdst) 618 fsrcRoot := fixRoot(fsrc) 619 return strings.HasPrefix(fdstRoot, fsrcRoot) || strings.HasPrefix(fsrcRoot, fdstRoot) 620 } 621 622 // SameDir returns true if fdst and fsrc point to the same 623 // underlying Fs and they are the same directory. 624 func SameDir(fdst, fsrc fs.Info) bool { 625 if !SameConfig(fdst, fsrc) { 626 return false 627 } 628 fdstRoot := fixRoot(fdst) 629 fsrcRoot := fixRoot(fsrc) 630 return fdstRoot == fsrcRoot 631 } 632 633 // checkIdentical checks to see if dst and src are identical 634 // 635 // it returns true if differences were found 636 // it also returns whether it couldn't be hashed 637 func checkIdentical(ctx context.Context, dst, src fs.Object) (differ bool, noHash bool) { 638 same, ht, err := CheckHashes(ctx, src, dst) 639 if err != nil { 640 // CheckHashes will log and count errors 641 return true, false 642 } 643 if ht == hash.None { 644 return false, true 645 } 646 if !same { 647 err = errors.Errorf("%v differ", ht) 648 fs.Errorf(src, "%v", err) 649 fs.CountError(err) 650 return true, false 651 } 652 return false, false 653 } 654 655 // checkFn is the the type of the checking function used in CheckFn() 656 type checkFn func(ctx context.Context, a, b fs.Object) (differ bool, noHash bool) 657 658 // checkMarch is used to march over two Fses in the same way as 659 // sync/copy 660 type checkMarch struct { 661 fdst, fsrc fs.Fs 662 check checkFn 663 oneway bool 664 differences int32 665 noHashes int32 666 srcFilesMissing int32 667 dstFilesMissing int32 668 matches int32 669 } 670 671 // DstOnly have an object which is in the destination only 672 func (c *checkMarch) DstOnly(dst fs.DirEntry) (recurse bool) { 673 switch dst.(type) { 674 case fs.Object: 675 if c.oneway { 676 return false 677 } 678 err := errors.Errorf("File not in %v", c.fsrc) 679 fs.Errorf(dst, "%v", err) 680 fs.CountError(err) 681 atomic.AddInt32(&c.differences, 1) 682 atomic.AddInt32(&c.srcFilesMissing, 1) 683 case fs.Directory: 684 // Do the same thing to the entire contents of the directory 685 return true 686 default: 687 panic("Bad object in DirEntries") 688 } 689 return false 690 } 691 692 // SrcOnly have an object which is in the source only 693 func (c *checkMarch) SrcOnly(src fs.DirEntry) (recurse bool) { 694 switch src.(type) { 695 case fs.Object: 696 err := errors.Errorf("File not in %v", c.fdst) 697 fs.Errorf(src, "%v", err) 698 fs.CountError(err) 699 atomic.AddInt32(&c.differences, 1) 700 atomic.AddInt32(&c.dstFilesMissing, 1) 701 case fs.Directory: 702 // Do the same thing to the entire contents of the directory 703 return true 704 default: 705 panic("Bad object in DirEntries") 706 } 707 return false 708 } 709 710 // check to see if two objects are identical using the check function 711 func (c *checkMarch) checkIdentical(ctx context.Context, dst, src fs.Object) (differ bool, noHash bool) { 712 accounting.Stats.Checking(src.Remote()) 713 defer accounting.Stats.DoneChecking(src.Remote()) 714 if sizeDiffers(src, dst) { 715 err := errors.Errorf("Sizes differ") 716 fs.Errorf(src, "%v", err) 717 fs.CountError(err) 718 return true, false 719 } 720 if fs.Config.SizeOnly { 721 return false, false 722 } 723 return c.check(ctx, dst, src) 724 } 725 726 // Match is called when src and dst are present, so sync src to dst 727 func (c *checkMarch) Match(ctx context.Context, dst, src fs.DirEntry) (recurse bool) { 728 switch srcX := src.(type) { 729 case fs.Object: 730 dstX, ok := dst.(fs.Object) 731 if ok { 732 differ, noHash := c.checkIdentical(ctx, dstX, srcX) 733 if differ { 734 atomic.AddInt32(&c.differences, 1) 735 } else { 736 atomic.AddInt32(&c.matches, 1) 737 fs.Debugf(dstX, "OK") 738 } 739 if noHash { 740 atomic.AddInt32(&c.noHashes, 1) 741 } 742 } else { 743 err := errors.Errorf("is file on %v but directory on %v", c.fsrc, c.fdst) 744 fs.Errorf(src, "%v", err) 745 fs.CountError(err) 746 atomic.AddInt32(&c.differences, 1) 747 atomic.AddInt32(&c.dstFilesMissing, 1) 748 } 749 case fs.Directory: 750 // Do the same thing to the entire contents of the directory 751 _, ok := dst.(fs.Directory) 752 if ok { 753 return true 754 } 755 err := errors.Errorf("is file on %v but directory on %v", c.fdst, c.fsrc) 756 fs.Errorf(dst, "%v", err) 757 fs.CountError(err) 758 atomic.AddInt32(&c.differences, 1) 759 atomic.AddInt32(&c.srcFilesMissing, 1) 760 761 default: 762 panic("Bad object in DirEntries") 763 } 764 return false 765 } 766 767 // CheckFn checks the files in fsrc and fdst according to Size and 768 // hash using checkFunction on each file to check the hashes. 769 // 770 // checkFunction sees if dst and src are identical 771 // 772 // it returns true if differences were found 773 // it also returns whether it couldn't be hashed 774 func CheckFn(ctx context.Context, fdst, fsrc fs.Fs, check checkFn, oneway bool) error { 775 c := &checkMarch{ 776 fdst: fdst, 777 fsrc: fsrc, 778 check: check, 779 oneway: oneway, 780 } 781 782 // set up a march over fdst and fsrc 783 m := &march.March{ 784 Ctx: ctx, 785 Fdst: fdst, 786 Fsrc: fsrc, 787 Dir: "", 788 Callback: c, 789 } 790 fs.Infof(fdst, "Waiting for checks to finish") 791 err := m.Run() 792 793 if c.dstFilesMissing > 0 { 794 fs.Logf(fdst, "%d files missing", c.dstFilesMissing) 795 } 796 if c.srcFilesMissing > 0 { 797 fs.Logf(fsrc, "%d files missing", c.srcFilesMissing) 798 } 799 800 fs.Logf(fdst, "%d differences found", accounting.Stats.GetErrors()) 801 if c.noHashes > 0 { 802 fs.Logf(fdst, "%d hashes could not be checked", c.noHashes) 803 } 804 if c.matches > 0 { 805 fs.Logf(fdst, "%d matching files", c.matches) 806 } 807 if c.differences > 0 { 808 return errors.Errorf("%d differences found", c.differences) 809 } 810 return err 811 } 812 813 // Check the files in fsrc and fdst according to Size and hash 814 func Check(ctx context.Context, fdst, fsrc fs.Fs, oneway bool) error { 815 return CheckFn(ctx, fdst, fsrc, checkIdentical, oneway) 816 } 817 818 // CheckEqualReaders checks to see if in1 and in2 have the same 819 // content when read. 820 // 821 // it returns true if differences were found 822 func CheckEqualReaders(in1, in2 io.Reader) (differ bool, err error) { 823 const bufSize = 64 * 1024 824 buf1 := make([]byte, bufSize) 825 buf2 := make([]byte, bufSize) 826 for { 827 n1, err1 := readers.ReadFill(in1, buf1) 828 n2, err2 := readers.ReadFill(in2, buf2) 829 // check errors 830 if err1 != nil && err1 != io.EOF { 831 return true, err1 832 } else if err2 != nil && err2 != io.EOF { 833 return true, err2 834 } 835 // err1 && err2 are nil or io.EOF here 836 // process the data 837 if n1 != n2 || !bytes.Equal(buf1[:n1], buf2[:n2]) { 838 return true, nil 839 } 840 // if both streams finished the we have finished 841 if err1 == io.EOF && err2 == io.EOF { 842 break 843 } 844 } 845 return false, nil 846 } 847 848 // CheckIdentical checks to see if dst and src are identical by 849 // reading all their bytes if necessary. 850 // 851 // it returns true if differences were found 852 func CheckIdentical(ctx context.Context, dst, src fs.Object) (differ bool, err error) { 853 in1, err := dst.Open(ctx) 854 if err != nil { 855 return true, errors.Wrapf(err, "failed to open %q", dst) 856 } 857 in1 = accounting.NewAccount(in1, dst).WithBuffer() // account and buffer the transfer 858 defer fs.CheckClose(in1, &err) 859 860 in2, err := src.Open(ctx) 861 if err != nil { 862 return true, errors.Wrapf(err, "failed to open %q", src) 863 } 864 in2 = accounting.NewAccount(in2, src).WithBuffer() // account and buffer the transfer 865 defer fs.CheckClose(in2, &err) 866 867 return CheckEqualReaders(in1, in2) 868 } 869 870 // CheckDownload checks the files in fsrc and fdst according to Size 871 // and the actual contents of the files. 872 func CheckDownload(ctx context.Context, fdst, fsrc fs.Fs, oneway bool) error { 873 check := func(ctx context.Context, a, b fs.Object) (differ bool, noHash bool) { 874 differ, err := CheckIdentical(ctx, a, b) 875 if err != nil { 876 fs.CountError(err) 877 fs.Errorf(a, "Failed to download: %v", err) 878 return true, true 879 } 880 return differ, false 881 } 882 return CheckFn(ctx, fdst, fsrc, check, oneway) 883 } 884 885 // ListFn lists the Fs to the supplied function 886 // 887 // Lists in parallel which may get them out of order 888 func ListFn(ctx context.Context, f fs.Fs, fn func(fs.Object)) error { 889 return walk.ListR(ctx, f, "", false, fs.Config.MaxDepth, walk.ListObjects, func(entries fs.DirEntries) error { 890 entries.ForObject(fn) 891 return nil 892 }) 893 } 894 895 // mutex for synchronized output 896 var outMutex sync.Mutex 897 898 // Synchronized fmt.Fprintf 899 // 900 // Ignores errors from Fprintf 901 func syncFprintf(w io.Writer, format string, a ...interface{}) { 902 outMutex.Lock() 903 defer outMutex.Unlock() 904 _, _ = fmt.Fprintf(w, format, a...) 905 } 906 907 // List the Fs to the supplied writer 908 // 909 // Shows size and path - obeys includes and excludes 910 // 911 // Lists in parallel which may get them out of order 912 func List(ctx context.Context, f fs.Fs, w io.Writer) error { 913 return ListFn(ctx, f, func(o fs.Object) { 914 syncFprintf(w, "%9d %s\n", o.Size(), o.Remote()) 915 }) 916 } 917 918 // ListLong lists the Fs to the supplied writer 919 // 920 // Shows size, mod time and path - obeys includes and excludes 921 // 922 // Lists in parallel which may get them out of order 923 func ListLong(ctx context.Context, f fs.Fs, w io.Writer) error { 924 return ListFn(ctx, f, func(o fs.Object) { 925 accounting.Stats.Checking(o.Remote()) 926 modTime := o.ModTime(ctx) 927 accounting.Stats.DoneChecking(o.Remote()) 928 syncFprintf(w, "%9d %s %s\n", o.Size(), modTime.Local().Format("2006-01-02 15:04:05.000000000"), o.Remote()) 929 }) 930 } 931 932 // Md5sum list the Fs to the supplied writer 933 // 934 // Produces the same output as the md5sum command - obeys includes and 935 // excludes 936 // 937 // Lists in parallel which may get them out of order 938 func Md5sum(ctx context.Context, f fs.Fs, w io.Writer) error { 939 return HashLister(ctx, hash.MD5, f, w) 940 } 941 942 // Sha1sum list the Fs to the supplied writer 943 // 944 // Obeys includes and excludes 945 // 946 // Lists in parallel which may get them out of order 947 func Sha1sum(ctx context.Context, f fs.Fs, w io.Writer) error { 948 return HashLister(ctx, hash.SHA1, f, w) 949 } 950 951 // DropboxHashSum list the Fs to the supplied writer 952 // 953 // Obeys includes and excludes 954 // 955 // Lists in parallel which may get them out of order 956 func DropboxHashSum(ctx context.Context, f fs.Fs, w io.Writer) error { 957 return HashLister(ctx, hash.Dropbox, f, w) 958 } 959 960 // hashSum returns the human readable hash for ht passed in. This may 961 // be UNSUPPORTED or ERROR. 962 func hashSum(ctx context.Context, ht hash.Type, o fs.Object) string { 963 accounting.Stats.Checking(o.Remote()) 964 sum, err := o.Hash(ctx, ht) 965 accounting.Stats.DoneChecking(o.Remote()) 966 if err == hash.ErrUnsupported { 967 sum = "UNSUPPORTED" 968 } else if err != nil { 969 fs.Debugf(o, "Failed to read %v: %v", ht, err) 970 sum = "ERROR" 971 } 972 return sum 973 } 974 975 // HashLister does a md5sum equivalent for the hash type passed in 976 func HashLister(ctx context.Context, ht hash.Type, f fs.Fs, w io.Writer) error { 977 return ListFn(ctx, f, func(o fs.Object) { 978 sum := hashSum(ctx, ht, o) 979 syncFprintf(w, "%*s %s\n", hash.Width[ht], sum, o.Remote()) 980 }) 981 } 982 983 // Count counts the objects and their sizes in the Fs 984 // 985 // Obeys includes and excludes 986 func Count(ctx context.Context, f fs.Fs) (objects int64, size int64, err error) { 987 err = ListFn(ctx, f, func(o fs.Object) { 988 atomic.AddInt64(&objects, 1) 989 objectSize := o.Size() 990 if objectSize > 0 { 991 atomic.AddInt64(&size, objectSize) 992 } 993 }) 994 return 995 } 996 997 // ConfigMaxDepth returns the depth to use for a recursive or non recursive listing. 998 func ConfigMaxDepth(recursive bool) int { 999 depth := fs.Config.MaxDepth 1000 if !recursive && depth < 0 { 1001 depth = 1 1002 } 1003 return depth 1004 } 1005 1006 // ListDir lists the directories/buckets/containers in the Fs to the supplied writer 1007 func ListDir(ctx context.Context, f fs.Fs, w io.Writer) error { 1008 return walk.ListR(ctx, f, "", false, ConfigMaxDepth(false), walk.ListDirs, func(entries fs.DirEntries) error { 1009 entries.ForDir(func(dir fs.Directory) { 1010 if dir != nil { 1011 syncFprintf(w, "%12d %13s %9d %s\n", dir.Size(), dir.ModTime(ctx).Local().Format("2006-01-02 15:04:05"), dir.Items(), dir.Remote()) 1012 } 1013 }) 1014 return nil 1015 }) 1016 } 1017 1018 // Mkdir makes a destination directory or container 1019 func Mkdir(ctx context.Context, f fs.Fs, dir string) error { 1020 if fs.Config.DryRun { 1021 fs.Logf(fs.LogDirName(f, dir), "Not making directory as dry run is set") 1022 return nil 1023 } 1024 fs.Debugf(fs.LogDirName(f, dir), "Making directory") 1025 err := f.Mkdir(ctx, dir) 1026 if err != nil { 1027 fs.CountError(err) 1028 return err 1029 } 1030 return nil 1031 } 1032 1033 // TryRmdir removes a container but not if not empty. It doesn't 1034 // count errors but may return one. 1035 func TryRmdir(ctx context.Context, f fs.Fs, dir string) error { 1036 if fs.Config.DryRun { 1037 fs.Logf(fs.LogDirName(f, dir), "Not deleting as dry run is set") 1038 return nil 1039 } 1040 fs.Debugf(fs.LogDirName(f, dir), "Removing directory") 1041 return f.Rmdir(ctx, dir) 1042 } 1043 1044 // Rmdir removes a container but not if not empty 1045 func Rmdir(ctx context.Context, f fs.Fs, dir string) error { 1046 err := TryRmdir(ctx, f, dir) 1047 if err != nil { 1048 fs.CountError(err) 1049 return err 1050 } 1051 return err 1052 } 1053 1054 // Purge removes a directory and all of its contents 1055 func Purge(ctx context.Context, f fs.Fs, dir string) error { 1056 doFallbackPurge := true 1057 var err error 1058 if dir == "" { 1059 // FIXME change the Purge interface so it takes a dir - see #1891 1060 if doPurge := f.Features().Purge; doPurge != nil { 1061 doFallbackPurge = false 1062 if fs.Config.DryRun { 1063 fs.Logf(f, "Not purging as --dry-run set") 1064 } else { 1065 err = doPurge(ctx) 1066 if err == fs.ErrorCantPurge { 1067 doFallbackPurge = true 1068 } 1069 } 1070 } 1071 } 1072 if doFallbackPurge { 1073 // DeleteFiles and Rmdir observe --dry-run 1074 err = DeleteFiles(ctx, listToChan(ctx, f, dir)) 1075 if err != nil { 1076 return err 1077 } 1078 err = Rmdirs(ctx, f, dir, false) 1079 } 1080 if err != nil { 1081 fs.CountError(err) 1082 return err 1083 } 1084 return nil 1085 } 1086 1087 // Delete removes all the contents of a container. Unlike Purge, it 1088 // obeys includes and excludes. 1089 func Delete(ctx context.Context, f fs.Fs) error { 1090 delChan := make(fs.ObjectsChan, fs.Config.Transfers) 1091 delErr := make(chan error, 1) 1092 go func() { 1093 delErr <- DeleteFiles(ctx, delChan) 1094 }() 1095 err := ListFn(ctx, f, func(o fs.Object) { 1096 delChan <- o 1097 }) 1098 close(delChan) 1099 delError := <-delErr 1100 if err == nil { 1101 err = delError 1102 } 1103 return err 1104 } 1105 1106 // listToChan will transfer all objects in the listing to the output 1107 // 1108 // If an error occurs, the error will be logged, and it will close the 1109 // channel. 1110 // 1111 // If the error was ErrorDirNotFound then it will be ignored 1112 func listToChan(ctx context.Context, f fs.Fs, dir string) fs.ObjectsChan { 1113 o := make(fs.ObjectsChan, fs.Config.Checkers) 1114 go func() { 1115 defer close(o) 1116 err := walk.ListR(ctx, f, dir, true, fs.Config.MaxDepth, walk.ListObjects, func(entries fs.DirEntries) error { 1117 entries.ForObject(func(obj fs.Object) { 1118 o <- obj 1119 }) 1120 return nil 1121 }) 1122 if err != nil && err != fs.ErrorDirNotFound { 1123 err = errors.Wrap(err, "failed to list") 1124 fs.CountError(err) 1125 fs.Errorf(nil, "%v", err) 1126 } 1127 }() 1128 return o 1129 } 1130 1131 // CleanUp removes the trash for the Fs 1132 func CleanUp(ctx context.Context, f fs.Fs) error { 1133 doCleanUp := f.Features().CleanUp 1134 if doCleanUp == nil { 1135 return errors.Errorf("%v doesn't support cleanup", f) 1136 } 1137 if fs.Config.DryRun { 1138 fs.Logf(f, "Not running cleanup as --dry-run set") 1139 return nil 1140 } 1141 return doCleanUp(ctx) 1142 } 1143 1144 // wrap a Reader and a Closer together into a ReadCloser 1145 type readCloser struct { 1146 io.Reader 1147 io.Closer 1148 } 1149 1150 // Cat any files to the io.Writer 1151 // 1152 // if offset == 0 it will be ignored 1153 // if offset > 0 then the file will be seeked to that offset 1154 // if offset < 0 then the file will be seeked that far from the end 1155 // 1156 // if count < 0 then it will be ignored 1157 // if count >= 0 then only that many characters will be output 1158 func Cat(ctx context.Context, f fs.Fs, w io.Writer, offset, count int64) error { 1159 var mu sync.Mutex 1160 return ListFn(ctx, f, func(o fs.Object) { 1161 var err error 1162 accounting.Stats.Transferring(o.Remote()) 1163 defer func() { 1164 accounting.Stats.DoneTransferring(o.Remote(), err == nil) 1165 }() 1166 opt := fs.RangeOption{Start: offset, End: -1} 1167 size := o.Size() 1168 if opt.Start < 0 { 1169 opt.Start += size 1170 } 1171 if count >= 0 { 1172 opt.End = opt.Start + count - 1 1173 } 1174 var options []fs.OpenOption 1175 if opt.Start > 0 || opt.End >= 0 { 1176 options = append(options, &opt) 1177 } 1178 in, err := o.Open(ctx, options...) 1179 if err != nil { 1180 fs.CountError(err) 1181 fs.Errorf(o, "Failed to open: %v", err) 1182 return 1183 } 1184 if count >= 0 { 1185 in = &readCloser{Reader: &io.LimitedReader{R: in, N: count}, Closer: in} 1186 // reduce remaining size to count 1187 if size > count { 1188 size = count 1189 } 1190 } 1191 in = accounting.NewAccountSizeName(in, size, o.Remote()).WithBuffer() // account and buffer the transfer 1192 defer func() { 1193 err = in.Close() 1194 if err != nil { 1195 fs.CountError(err) 1196 fs.Errorf(o, "Failed to close: %v", err) 1197 } 1198 }() 1199 // take the lock just before we output stuff, so at the last possible moment 1200 mu.Lock() 1201 defer mu.Unlock() 1202 _, err = io.Copy(w, in) 1203 if err != nil { 1204 fs.CountError(err) 1205 fs.Errorf(o, "Failed to send to output: %v", err) 1206 } 1207 }) 1208 } 1209 1210 // Rcat reads data from the Reader until EOF and uploads it to a file on remote 1211 func Rcat(ctx context.Context, fdst fs.Fs, dstFileName string, in io.ReadCloser, modTime time.Time) (dst fs.Object, err error) { 1212 accounting.Stats.Transferring(dstFileName) 1213 in = accounting.NewAccountSizeName(in, -1, dstFileName).WithBuffer() 1214 defer func() { 1215 accounting.Stats.DoneTransferring(dstFileName, err == nil) 1216 if otherErr := in.Close(); otherErr != nil { 1217 fs.Debugf(fdst, "Rcat: failed to close source: %v", err) 1218 } 1219 }() 1220 1221 hashOption := &fs.HashesOption{Hashes: fdst.Hashes()} 1222 hash, err := hash.NewMultiHasherTypes(fdst.Hashes()) 1223 if err != nil { 1224 return nil, err 1225 } 1226 readCounter := readers.NewCountingReader(in) 1227 trackingIn := io.TeeReader(readCounter, hash) 1228 1229 compare := func(dst fs.Object) error { 1230 src := object.NewStaticObjectInfo(dstFileName, modTime, int64(readCounter.BytesRead()), false, hash.Sums(), fdst) 1231 if !Equal(ctx, src, dst) { 1232 err = errors.Errorf("corrupted on transfer") 1233 fs.CountError(err) 1234 fs.Errorf(dst, "%v", err) 1235 return err 1236 } 1237 return nil 1238 } 1239 1240 // check if file small enough for direct upload 1241 buf := make([]byte, fs.Config.StreamingUploadCutoff) 1242 if n, err := io.ReadFull(trackingIn, buf); err == io.EOF || err == io.ErrUnexpectedEOF { 1243 fs.Debugf(fdst, "File to upload is small (%d bytes), uploading instead of streaming", n) 1244 src := object.NewMemoryObject(dstFileName, modTime, buf[:n]) 1245 return Copy(ctx, fdst, nil, dstFileName, src) 1246 } 1247 1248 // Make a new ReadCloser with the bits we've already read 1249 in = &readCloser{ 1250 Reader: io.MultiReader(bytes.NewReader(buf), trackingIn), 1251 Closer: in, 1252 } 1253 1254 fStreamTo := fdst 1255 canStream := fdst.Features().PutStream != nil 1256 if !canStream { 1257 fs.Debugf(fdst, "Target remote doesn't support streaming uploads, creating temporary local FS to spool file") 1258 tmpLocalFs, err := fs.TemporaryLocalFs() 1259 if err != nil { 1260 return nil, errors.Wrap(err, "Failed to create temporary local FS to spool file") 1261 } 1262 defer func() { 1263 err := Purge(ctx, tmpLocalFs, "") 1264 if err != nil { 1265 fs.Infof(tmpLocalFs, "Failed to cleanup temporary FS: %v", err) 1266 } 1267 }() 1268 fStreamTo = tmpLocalFs 1269 } 1270 1271 if fs.Config.DryRun { 1272 fs.Logf("stdin", "Not uploading as --dry-run") 1273 // prevents "broken pipe" errors 1274 _, err = io.Copy(ioutil.Discard, in) 1275 return nil, err 1276 } 1277 1278 objInfo := object.NewStaticObjectInfo(dstFileName, modTime, -1, false, nil, nil) 1279 if dst, err = fStreamTo.Features().PutStream(ctx, in, objInfo, hashOption); err != nil { 1280 return dst, err 1281 } 1282 if err = compare(dst); err != nil { 1283 return dst, err 1284 } 1285 if !canStream { 1286 // copy dst (which is the local object we have just streamed to) to the remote 1287 return Copy(ctx, fdst, nil, dstFileName, dst) 1288 } 1289 return dst, nil 1290 } 1291 1292 // PublicLink adds a "readable by anyone with link" permission on the given file or folder. 1293 func PublicLink(ctx context.Context, f fs.Fs, remote string) (string, error) { 1294 doPublicLink := f.Features().PublicLink 1295 if doPublicLink == nil { 1296 return "", errors.Errorf("%v doesn't support public links", f) 1297 } 1298 return doPublicLink(ctx, remote) 1299 } 1300 1301 // Rmdirs removes any empty directories (or directories only 1302 // containing empty directories) under f, including f. 1303 func Rmdirs(ctx context.Context, f fs.Fs, dir string, leaveRoot bool) error { 1304 dirEmpty := make(map[string]bool) 1305 dirEmpty[dir] = !leaveRoot 1306 err := walk.Walk(ctx, f, dir, true, fs.Config.MaxDepth, func(dirPath string, entries fs.DirEntries, err error) error { 1307 if err != nil { 1308 fs.CountError(err) 1309 fs.Errorf(f, "Failed to list %q: %v", dirPath, err) 1310 return nil 1311 } 1312 for _, entry := range entries { 1313 switch x := entry.(type) { 1314 case fs.Directory: 1315 // add a new directory as empty 1316 dir := x.Remote() 1317 _, found := dirEmpty[dir] 1318 if !found { 1319 dirEmpty[dir] = true 1320 } 1321 case fs.Object: 1322 // mark the parents of the file as being non-empty 1323 dir := x.Remote() 1324 for dir != "" { 1325 dir = path.Dir(dir) 1326 if dir == "." || dir == "/" { 1327 dir = "" 1328 } 1329 empty, found := dirEmpty[dir] 1330 // End if we reach a directory which is non-empty 1331 if found && !empty { 1332 break 1333 } 1334 dirEmpty[dir] = false 1335 } 1336 } 1337 } 1338 return nil 1339 }) 1340 if err != nil { 1341 return errors.Wrap(err, "failed to rmdirs") 1342 } 1343 // Now delete the empty directories, starting from the longest path 1344 var toDelete []string 1345 for dir, empty := range dirEmpty { 1346 if empty { 1347 toDelete = append(toDelete, dir) 1348 } 1349 } 1350 sort.Strings(toDelete) 1351 for i := len(toDelete) - 1; i >= 0; i-- { 1352 dir := toDelete[i] 1353 err := TryRmdir(ctx, f, dir) 1354 if err != nil { 1355 fs.CountError(err) 1356 fs.Errorf(dir, "Failed to rmdir: %v", err) 1357 return err 1358 } 1359 } 1360 return nil 1361 } 1362 1363 // GetCompareDest sets up --compare-dest 1364 func GetCompareDest() (CompareDest fs.Fs, err error) { 1365 CompareDest, err = cache.Get(fs.Config.CompareDest) 1366 if err != nil { 1367 return nil, fserrors.FatalError(errors.Errorf("Failed to make fs for --compare-dest %q: %v", fs.Config.CompareDest, err)) 1368 } 1369 return CompareDest, nil 1370 } 1371 1372 // compareDest checks --compare-dest to see if src needs to 1373 // be copied 1374 // 1375 // Returns True if src is in --compare-dest 1376 func compareDest(ctx context.Context, dst, src fs.Object, CompareDest fs.Fs) (NoNeedTransfer bool, err error) { 1377 var remote string 1378 if dst == nil { 1379 remote = src.Remote() 1380 } else { 1381 remote = dst.Remote() 1382 } 1383 CompareDestFile, err := CompareDest.NewObject(ctx, remote) 1384 switch err { 1385 case fs.ErrorObjectNotFound: 1386 return false, nil 1387 case nil: 1388 break 1389 default: 1390 return false, err 1391 } 1392 if Equal(ctx, src, CompareDestFile) { 1393 fs.Debugf(src, "Destination found in --compare-dest, skipping") 1394 return true, nil 1395 } 1396 return false, nil 1397 } 1398 1399 // GetCopyDest sets up --copy-dest 1400 func GetCopyDest(fdst fs.Fs) (CopyDest fs.Fs, err error) { 1401 CopyDest, err = cache.Get(fs.Config.CopyDest) 1402 if err != nil { 1403 return nil, fserrors.FatalError(errors.Errorf("Failed to make fs for --copy-dest %q: %v", fs.Config.CopyDest, err)) 1404 } 1405 if !SameConfig(fdst, CopyDest) { 1406 return nil, fserrors.FatalError(errors.New("parameter to --copy-dest has to be on the same remote as destination")) 1407 } 1408 if CopyDest.Features().Copy == nil { 1409 return nil, fserrors.FatalError(errors.New("can't use --copy-dest on a remote which doesn't support server side copy")) 1410 } 1411 return CopyDest, nil 1412 } 1413 1414 // copyDest checks --copy-dest to see if src needs to 1415 // be copied 1416 // 1417 // Returns True if src was copied from --copy-dest 1418 func copyDest(ctx context.Context, fdst fs.Fs, dst, src fs.Object, CopyDest, backupDir fs.Fs) (NoNeedTransfer bool, err error) { 1419 var remote string 1420 if dst == nil { 1421 remote = src.Remote() 1422 } else { 1423 remote = dst.Remote() 1424 } 1425 CopyDestFile, err := CopyDest.NewObject(ctx, remote) 1426 switch err { 1427 case fs.ErrorObjectNotFound: 1428 return false, nil 1429 case nil: 1430 break 1431 default: 1432 return false, err 1433 } 1434 if equal(ctx, src, CopyDestFile, fs.Config.SizeOnly, fs.Config.CheckSum, false) { 1435 if dst == nil || !Equal(ctx, src, dst) { 1436 if dst != nil && backupDir != nil { 1437 err = MoveBackupDir(ctx, backupDir, dst) 1438 if err != nil { 1439 return false, errors.Wrap(err, "moving to --backup-dir failed") 1440 } 1441 // If successful zero out the dstObj as it is no longer there 1442 dst = nil 1443 } 1444 _, err := Copy(ctx, fdst, dst, remote, CopyDestFile) 1445 if err != nil { 1446 fs.Errorf(src, "Destination found in --copy-dest, error copying") 1447 return false, nil 1448 } 1449 fs.Debugf(src, "Destination found in --copy-dest, using server side copy") 1450 return true, nil 1451 } 1452 fs.Debugf(src, "Unchanged skipping") 1453 return true, nil 1454 } 1455 fs.Debugf(src, "Destination not found in --copy-dest") 1456 return false, nil 1457 } 1458 1459 // CompareOrCopyDest checks --compare-dest and --copy-dest to see if src 1460 // does not need to be copied 1461 // 1462 // Returns True if src does not need to be copied 1463 func CompareOrCopyDest(ctx context.Context, fdst fs.Fs, dst, src fs.Object, CompareOrCopyDest, backupDir fs.Fs) (NoNeedTransfer bool, err error) { 1464 if fs.Config.CompareDest != "" { 1465 return compareDest(ctx, dst, src, CompareOrCopyDest) 1466 } else if fs.Config.CopyDest != "" { 1467 return copyDest(ctx, fdst, dst, src, CompareOrCopyDest, backupDir) 1468 } 1469 return false, nil 1470 } 1471 1472 // NeedTransfer checks to see if src needs to be copied to dst using 1473 // the current config. 1474 // 1475 // Returns a flag which indicates whether the file needs to be 1476 // transferred or not. 1477 func NeedTransfer(ctx context.Context, dst, src fs.Object) bool { 1478 if dst == nil { 1479 fs.Debugf(src, "Couldn't find file - need to transfer") 1480 return true 1481 } 1482 // If we should ignore existing files, don't transfer 1483 if fs.Config.IgnoreExisting { 1484 fs.Debugf(src, "Destination exists, skipping") 1485 return false 1486 } 1487 // If we should upload unconditionally 1488 if fs.Config.IgnoreTimes { 1489 fs.Debugf(src, "Transferring unconditionally as --ignore-times is in use") 1490 return true 1491 } 1492 // If UpdateOlder is in effect, skip if dst is newer than src 1493 if fs.Config.UpdateOlder { 1494 srcModTime := src.ModTime(ctx) 1495 dstModTime := dst.ModTime(ctx) 1496 dt := dstModTime.Sub(srcModTime) 1497 // If have a mutually agreed precision then use that 1498 modifyWindow := fs.GetModifyWindow(dst.Fs(), src.Fs()) 1499 if modifyWindow == fs.ModTimeNotSupported { 1500 // Otherwise use 1 second as a safe default as 1501 // the resolution of the time a file was 1502 // uploaded. 1503 modifyWindow = time.Second 1504 } 1505 switch { 1506 case dt >= modifyWindow: 1507 fs.Debugf(src, "Destination is newer than source, skipping") 1508 return false 1509 case dt <= -modifyWindow: 1510 fs.Debugf(src, "Destination is older than source, transferring") 1511 default: 1512 if src.Size() == dst.Size() { 1513 fs.Debugf(src, "Destination mod time is within %v of source and sizes identical, skipping", modifyWindow) 1514 return false 1515 } 1516 fs.Debugf(src, "Destination mod time is within %v of source but sizes differ, transferring", modifyWindow) 1517 } 1518 } else { 1519 // Check to see if changed or not 1520 if Equal(ctx, src, dst) { 1521 fs.Debugf(src, "Unchanged skipping") 1522 return false 1523 } 1524 } 1525 return true 1526 } 1527 1528 // RcatSize reads data from the Reader until EOF and uploads it to a file on remote. 1529 // Pass in size >=0 if known, <0 if not known 1530 func RcatSize(ctx context.Context, fdst fs.Fs, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (dst fs.Object, err error) { 1531 var obj fs.Object 1532 1533 if size >= 0 { 1534 // Size known use Put 1535 accounting.Stats.Transferring(dstFileName) 1536 body := ioutil.NopCloser(in) // we let the server close the body 1537 in := accounting.NewAccountSizeName(body, size, dstFileName) // account the transfer (no buffering) 1538 1539 if fs.Config.DryRun { 1540 fs.Logf("stdin", "Not uploading as --dry-run") 1541 // prevents "broken pipe" errors 1542 _, err = io.Copy(ioutil.Discard, in) 1543 return nil, err 1544 } 1545 1546 var err error 1547 defer func() { 1548 closeErr := in.Close() 1549 if closeErr != nil { 1550 accounting.Stats.Error(closeErr) 1551 fs.Errorf(dstFileName, "Post request: close failed: %v", closeErr) 1552 } 1553 accounting.Stats.DoneTransferring(dstFileName, err == nil) 1554 }() 1555 info := object.NewStaticObjectInfo(dstFileName, modTime, size, true, nil, fdst) 1556 obj, err = fdst.Put(ctx, in, info) 1557 if err != nil { 1558 fs.Errorf(dstFileName, "Post request put error: %v", err) 1559 1560 return nil, err 1561 } 1562 } else { 1563 // Size unknown use Rcat 1564 obj, err = Rcat(ctx, fdst, dstFileName, in, modTime) 1565 if err != nil { 1566 fs.Errorf(dstFileName, "Post request rcat error: %v", err) 1567 1568 return nil, err 1569 } 1570 } 1571 1572 return obj, nil 1573 } 1574 1575 // CopyURL copies the data from the url to (fdst, dstFileName) 1576 func CopyURL(ctx context.Context, fdst fs.Fs, dstFileName string, url string) (dst fs.Object, err error) { 1577 client := fshttp.NewClient(fs.Config) 1578 resp, err := client.Get(url) 1579 1580 if err != nil { 1581 return nil, err 1582 } 1583 defer fs.CheckClose(resp.Body, &err) 1584 return RcatSize(ctx, fdst, dstFileName, resp.Body, resp.ContentLength, time.Now()) 1585 } 1586 1587 // BackupDir returns the correctly configured --backup-dir 1588 func BackupDir(fdst fs.Fs, fsrc fs.Fs, srcFileName string) (backupDir fs.Fs, err error) { 1589 if fs.Config.BackupDir != "" { 1590 backupDir, err = cache.Get(fs.Config.BackupDir) 1591 if err != nil { 1592 return nil, fserrors.FatalError(errors.Errorf("Failed to make fs for --backup-dir %q: %v", fs.Config.BackupDir, err)) 1593 } 1594 if !SameConfig(fdst, backupDir) { 1595 return nil, fserrors.FatalError(errors.New("parameter to --backup-dir has to be on the same remote as destination")) 1596 } 1597 if srcFileName == "" { 1598 if Overlapping(fdst, backupDir) { 1599 return nil, fserrors.FatalError(errors.New("destination and parameter to --backup-dir mustn't overlap")) 1600 } 1601 if Overlapping(fsrc, backupDir) { 1602 return nil, fserrors.FatalError(errors.New("source and parameter to --backup-dir mustn't overlap")) 1603 } 1604 } else { 1605 if fs.Config.Suffix == "" { 1606 if SameDir(fdst, backupDir) { 1607 return nil, fserrors.FatalError(errors.New("destination and parameter to --backup-dir mustn't be the same")) 1608 } 1609 if SameDir(fsrc, backupDir) { 1610 return nil, fserrors.FatalError(errors.New("source and parameter to --backup-dir mustn't be the same")) 1611 } 1612 } 1613 } 1614 } else { 1615 if srcFileName == "" { 1616 return nil, fserrors.FatalError(errors.New("--suffix must be used with a file or with --backup-dir")) 1617 } 1618 // --backup-dir is not set but --suffix is - use the destination as the backupDir 1619 backupDir = fdst 1620 } 1621 if !CanServerSideMove(backupDir) { 1622 return nil, fserrors.FatalError(errors.New("can't use --backup-dir on a remote which doesn't support server side move or copy")) 1623 } 1624 return backupDir, nil 1625 } 1626 1627 // MoveBackupDir moves a file to the backup dir 1628 func MoveBackupDir(ctx context.Context, backupDir fs.Fs, dst fs.Object) (err error) { 1629 remoteWithSuffix := SuffixName(dst.Remote()) 1630 overwritten, _ := backupDir.NewObject(ctx, remoteWithSuffix) 1631 _, err = Move(ctx, backupDir, overwritten, remoteWithSuffix, dst) 1632 return err 1633 } 1634 1635 // moveOrCopyFile moves or copies a single file possibly to a new name 1636 func moveOrCopyFile(ctx context.Context, fdst fs.Fs, fsrc fs.Fs, dstFileName string, srcFileName string, cp bool) (err error) { 1637 dstFilePath := path.Join(fdst.Root(), dstFileName) 1638 srcFilePath := path.Join(fsrc.Root(), srcFileName) 1639 if fdst.Name() == fsrc.Name() && dstFilePath == srcFilePath { 1640 fs.Debugf(fdst, "don't need to copy/move %s, it is already at target location", dstFileName) 1641 return nil 1642 } 1643 1644 // Choose operations 1645 Op := Move 1646 if cp { 1647 Op = Copy 1648 } 1649 1650 // Find src object 1651 srcObj, err := fsrc.NewObject(ctx, srcFileName) 1652 if err != nil { 1653 return err 1654 } 1655 1656 // Find dst object if it exists 1657 dstObj, err := fdst.NewObject(ctx, dstFileName) 1658 if err == fs.ErrorObjectNotFound { 1659 dstObj = nil 1660 } else if err != nil { 1661 return err 1662 } 1663 1664 // Special case for changing case of a file on a case insensitive remote 1665 // This will move the file to a temporary name then 1666 // move it back to the intended destination. This is required 1667 // to avoid issues with certain remotes and avoid file deletion. 1668 if !cp && fdst.Name() == fsrc.Name() && fdst.Features().CaseInsensitive && dstFileName != srcFileName && strings.ToLower(dstFilePath) == strings.ToLower(srcFilePath) { 1669 // Create random name to temporarily move file to 1670 tmpObjName := dstFileName + "-rclone-move-" + random(8) 1671 _, err := fdst.NewObject(ctx, tmpObjName) 1672 if err != fs.ErrorObjectNotFound { 1673 if err == nil { 1674 return errors.New("found an already existing file with a randomly generated name. Try the operation again") 1675 } 1676 return errors.Wrap(err, "error while attempting to move file to a temporary location") 1677 } 1678 accounting.Stats.Transferring(srcFileName) 1679 tmpObj, err := Op(ctx, fdst, nil, tmpObjName, srcObj) 1680 if err != nil { 1681 accounting.Stats.DoneTransferring(srcFileName, false) 1682 return errors.Wrap(err, "error while moving file to temporary location") 1683 } 1684 _, err = Op(ctx, fdst, nil, dstFileName, tmpObj) 1685 accounting.Stats.DoneTransferring(srcFileName, err == nil) 1686 return err 1687 } 1688 1689 var backupDir, copyDestDir fs.Fs 1690 if fs.Config.BackupDir != "" || fs.Config.Suffix != "" { 1691 backupDir, err = BackupDir(fdst, fsrc, srcFileName) 1692 if err != nil { 1693 return errors.Wrap(err, "creating Fs for --backup-dir failed") 1694 } 1695 } 1696 if fs.Config.CompareDest != "" { 1697 copyDestDir, err = GetCompareDest() 1698 if err != nil { 1699 return err 1700 } 1701 } else if fs.Config.CopyDest != "" { 1702 copyDestDir, err = GetCopyDest(fdst) 1703 if err != nil { 1704 return err 1705 } 1706 } 1707 NoNeedTransfer, err := CompareOrCopyDest(ctx, fdst, dstObj, srcObj, copyDestDir, backupDir) 1708 if err != nil { 1709 return err 1710 } 1711 if !NoNeedTransfer && NeedTransfer(ctx, dstObj, srcObj) { 1712 // If destination already exists, then we must move it into --backup-dir if required 1713 if dstObj != nil && backupDir != nil { 1714 err = MoveBackupDir(ctx, backupDir, dstObj) 1715 if err != nil { 1716 return errors.Wrap(err, "moving to --backup-dir failed") 1717 } 1718 // If successful zero out the dstObj as it is no longer there 1719 dstObj = nil 1720 } 1721 1722 _, err = Op(ctx, fdst, dstObj, dstFileName, srcObj) 1723 } else { 1724 accounting.Stats.Checking(srcFileName) 1725 if !cp { 1726 err = DeleteFile(ctx, srcObj) 1727 } 1728 defer accounting.Stats.DoneChecking(srcFileName) 1729 } 1730 return err 1731 } 1732 1733 // random generates a pseudorandom alphanumeric string 1734 func random(length int) string { 1735 randomOutput := make([]byte, length) 1736 possibleCharacters := "123567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 1737 rand.Seed(time.Now().Unix()) 1738 for i := range randomOutput { 1739 randomOutput[i] = possibleCharacters[rand.Intn(len(possibleCharacters))] 1740 } 1741 return string(randomOutput) 1742 } 1743 1744 // MoveFile moves a single file possibly to a new name 1745 func MoveFile(ctx context.Context, fdst fs.Fs, fsrc fs.Fs, dstFileName string, srcFileName string) (err error) { 1746 return moveOrCopyFile(ctx, fdst, fsrc, dstFileName, srcFileName, false) 1747 } 1748 1749 // CopyFile moves a single file possibly to a new name 1750 func CopyFile(ctx context.Context, fdst fs.Fs, fsrc fs.Fs, dstFileName string, srcFileName string) (err error) { 1751 return moveOrCopyFile(ctx, fdst, fsrc, dstFileName, srcFileName, true) 1752 } 1753 1754 // SetTier changes tier of object in remote 1755 func SetTier(ctx context.Context, fsrc fs.Fs, tier string) error { 1756 return ListFn(ctx, fsrc, func(o fs.Object) { 1757 objImpl, ok := o.(fs.SetTierer) 1758 if !ok { 1759 fs.Errorf(fsrc, "Remote object does not implement SetTier") 1760 return 1761 } 1762 err := objImpl.SetTier(tier) 1763 if err != nil { 1764 fs.Errorf(fsrc, "Failed to do SetTier, %v", err) 1765 } 1766 }) 1767 } 1768 1769 // ListFormat defines files information print format 1770 type ListFormat struct { 1771 separator string 1772 dirSlash bool 1773 absolute bool 1774 output []func(entry *ListJSONItem) string 1775 csv *csv.Writer 1776 buf bytes.Buffer 1777 } 1778 1779 // SetSeparator changes separator in struct 1780 func (l *ListFormat) SetSeparator(separator string) { 1781 l.separator = separator 1782 } 1783 1784 // SetDirSlash defines if slash should be printed 1785 func (l *ListFormat) SetDirSlash(dirSlash bool) { 1786 l.dirSlash = dirSlash 1787 } 1788 1789 // SetAbsolute prints a leading slash in front of path names 1790 func (l *ListFormat) SetAbsolute(absolute bool) { 1791 l.absolute = absolute 1792 } 1793 1794 // SetCSV defines if the output should be csv 1795 // 1796 // Note that you should call SetSeparator before this if you want a 1797 // custom separator 1798 func (l *ListFormat) SetCSV(useCSV bool) { 1799 if useCSV { 1800 l.csv = csv.NewWriter(&l.buf) 1801 if l.separator != "" { 1802 l.csv.Comma = []rune(l.separator)[0] 1803 } 1804 } else { 1805 l.csv = nil 1806 } 1807 } 1808 1809 // SetOutput sets functions used to create files information 1810 func (l *ListFormat) SetOutput(output []func(entry *ListJSONItem) string) { 1811 l.output = output 1812 } 1813 1814 // AddModTime adds file's Mod Time to output 1815 func (l *ListFormat) AddModTime() { 1816 l.AppendOutput(func(entry *ListJSONItem) string { 1817 return entry.ModTime.When.Local().Format("2006-01-02 15:04:05") 1818 }) 1819 } 1820 1821 // AddSize adds file's size to output 1822 func (l *ListFormat) AddSize() { 1823 l.AppendOutput(func(entry *ListJSONItem) string { 1824 return strconv.FormatInt(entry.Size, 10) 1825 }) 1826 } 1827 1828 // normalisePath makes sure the path has the correct slashes for the current mode 1829 func (l *ListFormat) normalisePath(entry *ListJSONItem, remote string) string { 1830 if l.absolute && !strings.HasPrefix(remote, "/") { 1831 remote = "/" + remote 1832 } 1833 if entry.IsDir && l.dirSlash { 1834 remote += "/" 1835 } 1836 return remote 1837 } 1838 1839 // AddPath adds path to file to output 1840 func (l *ListFormat) AddPath() { 1841 l.AppendOutput(func(entry *ListJSONItem) string { 1842 return l.normalisePath(entry, entry.Path) 1843 }) 1844 } 1845 1846 // AddEncrypted adds the encrypted path to file to output 1847 func (l *ListFormat) AddEncrypted() { 1848 l.AppendOutput(func(entry *ListJSONItem) string { 1849 return l.normalisePath(entry, entry.Encrypted) 1850 }) 1851 } 1852 1853 // AddHash adds the hash of the type given to the output 1854 func (l *ListFormat) AddHash(ht hash.Type) { 1855 hashName := ht.String() 1856 l.AppendOutput(func(entry *ListJSONItem) string { 1857 if entry.IsDir { 1858 return "" 1859 } 1860 return entry.Hashes[hashName] 1861 }) 1862 } 1863 1864 // AddID adds file's ID to the output if known 1865 func (l *ListFormat) AddID() { 1866 l.AppendOutput(func(entry *ListJSONItem) string { 1867 return entry.ID 1868 }) 1869 } 1870 1871 // AddOrigID adds file's Original ID to the output if known 1872 func (l *ListFormat) AddOrigID() { 1873 l.AppendOutput(func(entry *ListJSONItem) string { 1874 return entry.OrigID 1875 }) 1876 } 1877 1878 // AddTier adds file's Tier to the output if known 1879 func (l *ListFormat) AddTier() { 1880 l.AppendOutput(func(entry *ListJSONItem) string { 1881 return entry.Tier 1882 }) 1883 } 1884 1885 // AddMimeType adds file's MimeType to the output if known 1886 func (l *ListFormat) AddMimeType() { 1887 l.AppendOutput(func(entry *ListJSONItem) string { 1888 return entry.MimeType 1889 }) 1890 } 1891 1892 // AppendOutput adds string generated by specific function to printed output 1893 func (l *ListFormat) AppendOutput(functionToAppend func(item *ListJSONItem) string) { 1894 l.output = append(l.output, functionToAppend) 1895 } 1896 1897 // Format prints information about the DirEntry in the format defined 1898 func (l *ListFormat) Format(entry *ListJSONItem) (result string) { 1899 var out []string 1900 for _, fun := range l.output { 1901 out = append(out, fun(entry)) 1902 } 1903 if l.csv != nil { 1904 l.buf.Reset() 1905 _ = l.csv.Write(out) // can't fail writing to bytes.Buffer 1906 l.csv.Flush() 1907 result = strings.TrimRight(l.buf.String(), "\n") 1908 } else { 1909 result = strings.Join(out, l.separator) 1910 } 1911 return result 1912 } 1913 1914 // DirMove renames srcRemote to dstRemote 1915 // 1916 // It does this by loading the directory tree into memory (using ListR 1917 // if available) and doing renames in parallel. 1918 func DirMove(ctx context.Context, f fs.Fs, srcRemote, dstRemote string) (err error) { 1919 // Use DirMove if possible 1920 if doDirMove := f.Features().DirMove; doDirMove != nil { 1921 return doDirMove(ctx, f, srcRemote, dstRemote) 1922 } 1923 1924 // Load the directory tree into memory 1925 tree, err := walk.NewDirTree(ctx, f, srcRemote, true, -1) 1926 if err != nil { 1927 return errors.Wrap(err, "RenameDir tree walk") 1928 } 1929 1930 // Get the directories in sorted order 1931 dirs := tree.Dirs() 1932 1933 // Make the destination directories - must be done in order not in parallel 1934 for _, dir := range dirs { 1935 dstPath := dstRemote + dir[len(srcRemote):] 1936 err := f.Mkdir(ctx, dstPath) 1937 if err != nil { 1938 return errors.Wrap(err, "RenameDir mkdir") 1939 } 1940 } 1941 1942 // Rename the files in parallel 1943 type rename struct { 1944 o fs.Object 1945 newPath string 1946 } 1947 renames := make(chan rename, fs.Config.Transfers) 1948 g, gCtx := errgroup.WithContext(context.Background()) 1949 for i := 0; i < fs.Config.Transfers; i++ { 1950 g.Go(func() error { 1951 for job := range renames { 1952 dstOverwritten, _ := f.NewObject(gCtx, job.newPath) 1953 _, err := Move(gCtx, f, dstOverwritten, job.newPath, job.o) 1954 if err != nil { 1955 return err 1956 } 1957 select { 1958 case <-gCtx.Done(): 1959 return gCtx.Err() 1960 default: 1961 } 1962 1963 } 1964 return nil 1965 }) 1966 } 1967 for dir, entries := range tree { 1968 dstPath := dstRemote + dir[len(srcRemote):] 1969 for _, entry := range entries { 1970 if o, ok := entry.(fs.Object); ok { 1971 renames <- rename{o, path.Join(dstPath, path.Base(o.Remote()))} 1972 } 1973 } 1974 } 1975 close(renames) 1976 err = g.Wait() 1977 if err != nil { 1978 return errors.Wrap(err, "RenameDir renames") 1979 } 1980 1981 // Remove the source directories in reverse order 1982 for i := len(dirs) - 1; i >= 0; i-- { 1983 err := f.Rmdir(ctx, dirs[i]) 1984 if err != nil { 1985 return errors.Wrap(err, "RenameDir rmdir") 1986 } 1987 } 1988 1989 return nil 1990 } 1991 1992 // FsInfo provides information about a remote 1993 type FsInfo struct { 1994 // Name of the remote (as passed into NewFs) 1995 Name string 1996 1997 // Root of the remote (as passed into NewFs) 1998 Root string 1999 2000 // String returns a description of the FS 2001 String string 2002 2003 // Precision of the ModTimes in this Fs in Nanoseconds 2004 Precision time.Duration 2005 2006 // Returns the supported hash types of the filesystem 2007 Hashes []string 2008 2009 // Features returns the optional features of this Fs 2010 Features map[string]bool 2011 } 2012 2013 // GetFsInfo gets the information (FsInfo) about a given Fs 2014 func GetFsInfo(f fs.Fs) *FsInfo { 2015 info := &FsInfo{ 2016 Name: f.Name(), 2017 Root: f.Root(), 2018 String: f.String(), 2019 Precision: f.Precision(), 2020 Hashes: make([]string, 0, 4), 2021 Features: f.Features().Enabled(), 2022 } 2023 for _, hashType := range f.Hashes().Array() { 2024 info.Hashes = append(info.Hashes, hashType.String()) 2025 } 2026 return info 2027 }