github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/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/base64" 8 "encoding/csv" 9 "encoding/hex" 10 "encoding/json" 11 "errors" 12 "fmt" 13 "io" 14 "mime" 15 "net/http" 16 "os" 17 "path" 18 "path/filepath" 19 "runtime" 20 "sort" 21 "strconv" 22 "strings" 23 "sync" 24 "sync/atomic" 25 "time" 26 27 "github.com/rclone/rclone/fs" 28 "github.com/rclone/rclone/fs/accounting" 29 "github.com/rclone/rclone/fs/cache" 30 "github.com/rclone/rclone/fs/config" 31 "github.com/rclone/rclone/fs/filter" 32 "github.com/rclone/rclone/fs/fserrors" 33 "github.com/rclone/rclone/fs/fshttp" 34 "github.com/rclone/rclone/fs/hash" 35 "github.com/rclone/rclone/fs/object" 36 "github.com/rclone/rclone/fs/walk" 37 "github.com/rclone/rclone/lib/atexit" 38 "github.com/rclone/rclone/lib/errcount" 39 "github.com/rclone/rclone/lib/random" 40 "github.com/rclone/rclone/lib/readers" 41 "golang.org/x/sync/errgroup" 42 "golang.org/x/text/unicode/norm" 43 ) 44 45 // CheckHashes checks the two files to see if they have common 46 // known hash types and compares them 47 // 48 // Returns. 49 // 50 // equal - which is equality of the hashes 51 // 52 // hash - the HashType. This is HashNone if either of the hashes were 53 // unset or a compatible hash couldn't be found. 54 // 55 // err - may return an error which will already have been logged 56 // 57 // If an error is returned it will return equal as false 58 func CheckHashes(ctx context.Context, src fs.ObjectInfo, dst fs.Object) (equal bool, ht hash.Type, err error) { 59 common := src.Fs().Hashes().Overlap(dst.Fs().Hashes()) 60 // fs.Debugf(nil, "Shared hashes: %v", common) 61 if common.Count() == 0 { 62 return true, hash.None, nil 63 } 64 equal, ht, _, _, err = checkHashes(ctx, src, dst, common.GetOne()) 65 return equal, ht, err 66 } 67 68 var errNoHash = errors.New("no hash available") 69 70 // checkHashes does the work of CheckHashes but takes a hash.Type and 71 // returns the effective hash type used. 72 func checkHashes(ctx context.Context, src fs.ObjectInfo, dst fs.Object, ht hash.Type) (equal bool, htOut hash.Type, srcHash, dstHash string, err error) { 73 // Calculate hashes in parallel 74 g, ctx := errgroup.WithContext(ctx) 75 var srcErr, dstErr error 76 g.Go(func() (err error) { 77 srcHash, srcErr = src.Hash(ctx, ht) 78 if srcErr != nil { 79 return srcErr 80 } 81 if srcHash == "" { 82 fs.Debugf(src, "Src hash empty - aborting Dst hash check") 83 return errNoHash 84 } 85 return nil 86 }) 87 g.Go(func() (err error) { 88 dstHash, dstErr = dst.Hash(ctx, ht) 89 if dstErr != nil { 90 return dstErr 91 } 92 if dstHash == "" { 93 fs.Debugf(dst, "Dst hash empty - aborting Src hash check") 94 return errNoHash 95 } 96 return nil 97 }) 98 err = g.Wait() 99 if err == errNoHash { 100 return true, hash.None, srcHash, dstHash, nil 101 } 102 if srcErr != nil { 103 err = fs.CountError(srcErr) 104 fs.Errorf(src, "Failed to calculate src hash: %v", err) 105 } 106 if dstErr != nil { 107 err = fs.CountError(dstErr) 108 fs.Errorf(dst, "Failed to calculate dst hash: %v", err) 109 } 110 if err != nil { 111 return false, ht, srcHash, dstHash, err 112 } 113 if srcHash != dstHash { 114 fs.Debugf(src, "%v = %s (%v)", ht, srcHash, src.Fs()) 115 fs.Debugf(dst, "%v = %s (%v)", ht, dstHash, dst.Fs()) 116 } else { 117 fs.Debugf(src, "%v = %s OK", ht, srcHash) 118 } 119 return srcHash == dstHash, ht, srcHash, dstHash, nil 120 } 121 122 // Equal checks to see if the src and dst objects are equal by looking at 123 // size, mtime and hash 124 // 125 // If the src and dst size are different then it is considered to be 126 // not equal. If --size-only is in effect then this is the only check 127 // that is done. If --ignore-size is in effect then this check is 128 // skipped and the files are considered the same size. 129 // 130 // If the size is the same and the mtime is the same then it is 131 // considered to be equal. This check is skipped if using --checksum. 132 // 133 // If the size is the same and mtime is different, unreadable or 134 // --checksum is set and the hash is the same then the file is 135 // considered to be equal. In this case the mtime on the dst is 136 // updated if --checksum is not set. 137 // 138 // Otherwise the file is considered to be not equal including if there 139 // were errors reading info. 140 func Equal(ctx context.Context, src fs.ObjectInfo, dst fs.Object) bool { 141 return equal(ctx, src, dst, defaultEqualOpt(ctx)) 142 } 143 144 // DirsEqual is like Equal but for dirs instead of objects. 145 // It returns true if two dirs should be considered "equal" for the purposes of syncCopyMove 146 // (in other words, true == "skip updating modtime/metadata for this dir".) 147 // Unlike Equal, it does not consider size or checksum, as these do not apply to directories. 148 func DirsEqual(ctx context.Context, src, dst fs.Directory, opt DirsEqualOpt) (equal bool) { 149 if dst == nil { 150 return false 151 } 152 ci := fs.GetConfig(ctx) 153 if ci.SizeOnly || ci.Immutable || ci.IgnoreExisting || opt.ModifyWindow == fs.ModTimeNotSupported { 154 return true 155 } 156 if ci.IgnoreTimes { 157 return false 158 } 159 if !(opt.SetDirModtime || opt.SetDirMetadata) { 160 return true 161 } 162 srcModTime, dstModTime := src.ModTime(ctx), dst.ModTime(ctx) 163 if srcModTime.IsZero() || dstModTime.IsZero() { 164 return false 165 } 166 dt := dstModTime.Sub(srcModTime) 167 if dt < opt.ModifyWindow && dt > -opt.ModifyWindow { 168 fs.Debugf(dst, "Directory modification time the same (differ by %s, within tolerance %s)", dt, opt.ModifyWindow) 169 return true 170 } 171 if ci.UpdateOlder && dt >= opt.ModifyWindow { 172 fs.Debugf(dst, "Destination directory is newer than source, skipping") 173 return true 174 } 175 return false 176 } 177 178 // sizeDiffers compare the size of src and dst taking into account the 179 // various ways of ignoring sizes 180 func sizeDiffers(ctx context.Context, src, dst fs.ObjectInfo) bool { 181 ci := fs.GetConfig(ctx) 182 if ci.IgnoreSize || src.Size() < 0 || dst.Size() < 0 { 183 return false 184 } 185 return src.Size() != dst.Size() 186 } 187 188 var checksumWarning sync.Once 189 190 // options for equal function() 191 type equalOpt struct { 192 sizeOnly bool // if set only check size 193 checkSum bool // if set check checksum+size instead of modtime+size 194 updateModTime bool // if set update the modtime if hashes identical and checking with modtime+size 195 forceModTimeMatch bool // if set assume modtimes match 196 } 197 198 // default set of options for equal() 199 func defaultEqualOpt(ctx context.Context) equalOpt { 200 ci := fs.GetConfig(ctx) 201 return equalOpt{ 202 sizeOnly: ci.SizeOnly, 203 checkSum: ci.CheckSum, 204 updateModTime: !ci.NoUpdateModTime, 205 forceModTimeMatch: false, 206 } 207 } 208 209 // DirsEqualOpt represents options for DirsEqual function() 210 type DirsEqualOpt struct { 211 ModifyWindow time.Duration // Max time diff to be considered the same 212 SetDirModtime bool // whether to consider dir modtime 213 SetDirMetadata bool // whether to consider dir metadata 214 } 215 216 var modTimeUploadOnce sync.Once 217 218 // emit a log if we are about to upload a file to set its modification time 219 func logModTimeUpload(dst fs.Object) { 220 modTimeUploadOnce.Do(func() { 221 fs.Logf(dst.Fs(), "Forced to upload files to set modification times on this backend.") 222 }) 223 } 224 225 // EqualFn allows replacing Equal() with a custom function during NeedTransfer() 226 type ( 227 EqualFn func(ctx context.Context, src fs.ObjectInfo, dst fs.Object) bool 228 equalFnContextKey struct{} 229 ) 230 231 var equalFnKey = equalFnContextKey{} 232 233 // WithEqualFn stores equalFn in ctx and returns a copy of ctx in which equalFnKey = equalFn 234 func WithEqualFn(ctx context.Context, equalFn EqualFn) context.Context { 235 return context.WithValue(ctx, equalFnKey, equalFn) 236 } 237 238 func equal(ctx context.Context, src fs.ObjectInfo, dst fs.Object, opt equalOpt) bool { 239 ci := fs.GetConfig(ctx) 240 logger, _ := GetLogger(ctx) 241 if sizeDiffers(ctx, src, dst) { 242 fs.Debugf(src, "Sizes differ (src %d vs dst %d)", src.Size(), dst.Size()) 243 logger(ctx, Differ, src, dst, nil) 244 return false 245 } 246 if opt.sizeOnly { 247 fs.Debugf(src, "Sizes identical") 248 logger(ctx, Match, src, dst, nil) 249 return true 250 } 251 252 // Assert: Size is equal or being ignored 253 254 // If checking checksum and not modtime 255 if opt.checkSum { 256 // Check the hash 257 same, ht, _ := CheckHashes(ctx, src, dst) 258 if !same { 259 fs.Debugf(src, "%v differ", ht) 260 logger(ctx, Differ, src, dst, nil) 261 return false 262 } 263 if ht == hash.None { 264 common := src.Fs().Hashes().Overlap(dst.Fs().Hashes()) 265 if common.Count() == 0 { 266 checksumWarning.Do(func() { 267 fs.Logf(dst.Fs(), "--checksum is in use but the source and destination have no hashes in common; falling back to --size-only") 268 }) 269 } 270 fs.Debugf(src, "Size of src and dst objects identical") 271 } else { 272 fs.Debugf(src, "Size and %v of src and dst objects identical", ht) 273 } 274 logger(ctx, Match, src, dst, nil) 275 return true 276 } 277 278 srcModTime := src.ModTime(ctx) 279 if !opt.forceModTimeMatch { 280 // Sizes the same so check the mtime 281 modifyWindow := fs.GetModifyWindow(ctx, src.Fs(), dst.Fs()) 282 if modifyWindow == fs.ModTimeNotSupported { 283 fs.Debugf(src, "Sizes identical") 284 logger(ctx, Match, src, dst, nil) 285 return true 286 } 287 dstModTime := dst.ModTime(ctx) 288 dt := dstModTime.Sub(srcModTime) 289 if dt < modifyWindow && dt > -modifyWindow { 290 fs.Debugf(src, "Size and modification time the same (differ by %s, within tolerance %s)", dt, modifyWindow) 291 logger(ctx, Match, src, dst, nil) 292 return true 293 } 294 295 fs.Debugf(src, "Modification times differ by %s: %v, %v", dt, srcModTime, dstModTime) 296 } 297 298 // Check if the hashes are the same 299 same, ht, _ := CheckHashes(ctx, src, dst) 300 if !same { 301 fs.Debugf(src, "%v differ", ht) 302 logger(ctx, Differ, src, dst, nil) 303 return false 304 } 305 if ht == hash.None && !ci.RefreshTimes { 306 // if couldn't check hash, return that they differ 307 logger(ctx, Differ, src, dst, nil) 308 return false 309 } 310 311 // mod time differs but hash is the same to reset mod time if required 312 if opt.updateModTime { 313 if !SkipDestructive(ctx, src, "update modification time") { 314 // Size and hash the same but mtime different 315 // Error if objects are treated as immutable 316 if ci.Immutable { 317 fs.Errorf(dst, "Timestamp mismatch between immutable objects") 318 logger(ctx, Differ, src, dst, nil) 319 return false 320 } 321 // Update the mtime of the dst object here 322 err := dst.SetModTime(ctx, srcModTime) 323 if errors.Is(err, fs.ErrorCantSetModTime) { 324 logModTimeUpload(dst) 325 fs.Infof(dst, "src and dst identical but can't set mod time without re-uploading") 326 logger(ctx, Differ, src, dst, nil) 327 return false 328 } else if errors.Is(err, fs.ErrorCantSetModTimeWithoutDelete) { 329 logModTimeUpload(dst) 330 fs.Infof(dst, "src and dst identical but can't set mod time without deleting and re-uploading") 331 // Remove the file if BackupDir isn't set. If BackupDir is set we would rather have the old file 332 // put in the BackupDir than deleted which is what will happen if we don't delete it. 333 if ci.BackupDir == "" { 334 err = dst.Remove(ctx) 335 if err != nil { 336 fs.Errorf(dst, "failed to delete before re-upload: %v", err) 337 } 338 } 339 logger(ctx, Differ, src, dst, nil) 340 return false 341 } else if err != nil { 342 err = fs.CountError(err) 343 fs.Errorf(dst, "Failed to set modification time: %v", err) 344 } else { 345 fs.Infof(src, "Updated modification time in destination") 346 } 347 } 348 } 349 logger(ctx, Match, src, dst, nil) 350 return true 351 } 352 353 // CommonHash returns a single hash.Type and a HashOption with that 354 // type which is in common between the two fs.Fs. 355 func CommonHash(ctx context.Context, fa, fb fs.Info) (hash.Type, *fs.HashesOption) { 356 ci := fs.GetConfig(ctx) 357 // work out which hash to use - limit to 1 hash in common 358 var common hash.Set 359 hashType := hash.None 360 if !ci.IgnoreChecksum { 361 common = fb.Hashes().Overlap(fa.Hashes()) 362 if common.Count() > 0 { 363 hashType = common.GetOne() 364 common = hash.Set(hashType) 365 } 366 } 367 return hashType, &fs.HashesOption{Hashes: common} 368 } 369 370 // SameObject returns true if src and dst could be pointing to the 371 // same object. 372 func SameObject(src, dst fs.Object) bool { 373 srcFs, dstFs := src.Fs(), dst.Fs() 374 if !SameConfig(srcFs, dstFs) { 375 // If same remote type then check ID of objects if available 376 doSrcID, srcIDOK := src.(fs.IDer) 377 doDstID, dstIDOK := dst.(fs.IDer) 378 if srcIDOK && dstIDOK && SameRemoteType(srcFs, dstFs) { 379 srcID, dstID := doSrcID.ID(), doDstID.ID() 380 if srcID != "" && srcID == dstID { 381 return true 382 } 383 } 384 return false 385 } 386 srcPath := path.Join(srcFs.Root(), src.Remote()) 387 dstPath := path.Join(dstFs.Root(), dst.Remote()) 388 if srcFs.Features().IsLocal && dstFs.Features().IsLocal && runtime.GOOS == "darwin" { 389 if norm.NFC.String(srcPath) == norm.NFC.String(dstPath) { 390 return true 391 } 392 } 393 if dst.Fs().Features().CaseInsensitive { 394 srcPath = strings.ToLower(srcPath) 395 dstPath = strings.ToLower(dstPath) 396 } 397 return srcPath == dstPath 398 } 399 400 // Move src object to dst or fdst if nil. If dst is nil then it uses 401 // remote as the name of the new object. 402 // 403 // Note that you must check the destination does not exist before 404 // calling this and pass it as dst. If you pass dst=nil and the 405 // destination does exist then this may create duplicates or return 406 // errors. 407 // 408 // It returns the destination object if possible. Note that this may 409 // be nil. 410 // 411 // This is accounted as a check. 412 func Move(ctx context.Context, fdst fs.Fs, dst fs.Object, remote string, src fs.Object) (newDst fs.Object, err error) { 413 return move(ctx, fdst, dst, remote, src, false) 414 } 415 416 // MoveTransfer moves src object to dst or fdst if nil. If dst is nil 417 // then it uses remote as the name of the new object. 418 // 419 // This is identical to Move but is accounted as a transfer. 420 func MoveTransfer(ctx context.Context, fdst fs.Fs, dst fs.Object, remote string, src fs.Object) (newDst fs.Object, err error) { 421 return move(ctx, fdst, dst, remote, src, true) 422 } 423 424 // move - see Move for help 425 func move(ctx context.Context, fdst fs.Fs, dst fs.Object, remote string, src fs.Object, isTransfer bool) (newDst fs.Object, err error) { 426 ci := fs.GetConfig(ctx) 427 var tr *accounting.Transfer 428 if isTransfer { 429 tr = accounting.Stats(ctx).NewTransfer(src, fdst) 430 } else { 431 tr = accounting.Stats(ctx).NewCheckingTransfer(src, "moving") 432 } 433 defer func() { 434 if err == nil { 435 accounting.Stats(ctx).Renames(1) 436 } 437 tr.Done(ctx, err) 438 }() 439 newDst = dst 440 if SkipDestructive(ctx, src, "move") { 441 in := tr.Account(ctx, nil) 442 in.DryRun(src.Size()) 443 return newDst, nil 444 } 445 // See if we have Move available 446 if doMove := fdst.Features().Move; doMove != nil && (SameConfig(src.Fs(), fdst) || (SameRemoteType(src.Fs(), fdst) && (fdst.Features().ServerSideAcrossConfigs || ci.ServerSideAcrossConfigs))) { 447 // 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) 448 if dst != nil { 449 remote = dst.Remote() 450 if !SameObject(src, dst) { 451 err = DeleteFile(ctx, dst) 452 if err != nil { 453 return newDst, err 454 } 455 } else if needsMoveCaseInsensitive(fdst, fdst, remote, src.Remote(), false) { 456 doMove = func(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 457 return MoveCaseInsensitive(ctx, fdst, fdst, remote, src.Remote(), false, src) 458 } 459 } 460 } else if needsMoveCaseInsensitive(fdst, fdst, remote, src.Remote(), false) { 461 doMove = func(ctx context.Context, src fs.Object, remote string) (fs.Object, error) { 462 return MoveCaseInsensitive(ctx, fdst, fdst, remote, src.Remote(), false, src) 463 } 464 } 465 // Move dst <- src 466 in := tr.Account(ctx, nil) // account the transfer 467 in.ServerSideTransferStart() 468 newDst, err = doMove(ctx, src, remote) 469 switch err { 470 case nil: 471 if newDst != nil && src.String() != newDst.String() { 472 fs.Infof(src, "Moved (server-side) to: %s", newDst.String()) 473 } else { 474 fs.Infof(src, "Moved (server-side)") 475 } 476 in.ServerSideMoveEnd(newDst.Size()) // account the bytes for the server-side transfer 477 _ = in.Close() 478 return newDst, nil 479 case fs.ErrorCantMove: 480 fs.Debugf(src, "Can't move, switching to copy") 481 _ = in.Close() 482 default: 483 err = fs.CountError(err) 484 fs.Errorf(src, "Couldn't move: %v", err) 485 _ = in.Close() 486 return newDst, err 487 } 488 } 489 // Move not found or didn't work so copy dst <- src 490 newDst, err = Copy(ctx, fdst, dst, remote, src) 491 if err != nil { 492 fs.Errorf(src, "Not deleting source as copy failed: %v", err) 493 return newDst, err 494 } 495 // Delete src if no error on copy 496 return newDst, DeleteFile(ctx, src) 497 } 498 499 // CanServerSideMove returns true if fdst support server-side moves or 500 // server-side copies 501 // 502 // Some remotes simulate rename by server-side copy and delete, so include 503 // remotes that implements either Mover or Copier. 504 func CanServerSideMove(fdst fs.Fs) bool { 505 canMove := fdst.Features().Move != nil 506 canCopy := fdst.Features().Copy != nil 507 return canMove || canCopy 508 } 509 510 // SuffixName adds the current --suffix to the remote, obeying 511 // --suffix-keep-extension if set 512 func SuffixName(ctx context.Context, remote string) string { 513 ci := fs.GetConfig(ctx) 514 if ci.Suffix == "" { 515 return remote 516 } 517 if ci.SuffixKeepExtension { 518 var ( 519 base = remote 520 exts = "" 521 first = true 522 ext = path.Ext(remote) 523 ) 524 for ext != "" { 525 // Look second and subsequent extensions in mime types. 526 // If they aren't found then don't keep it as an extension. 527 if !first && mime.TypeByExtension(ext) == "" { 528 break 529 } 530 base = base[:len(base)-len(ext)] 531 exts = ext + exts 532 first = false 533 ext = path.Ext(base) 534 } 535 return base + ci.Suffix + exts 536 } 537 return remote + ci.Suffix 538 } 539 540 // DeleteFileWithBackupDir deletes a single file respecting --dry-run 541 // and accumulating stats and errors. 542 // 543 // If backupDir is set then it moves the file to there instead of 544 // deleting 545 func DeleteFileWithBackupDir(ctx context.Context, dst fs.Object, backupDir fs.Fs) (err error) { 546 tr := accounting.Stats(ctx).NewCheckingTransfer(dst, "deleting") 547 defer func() { 548 tr.Done(ctx, err) 549 }() 550 err = accounting.Stats(ctx).DeleteFile(ctx, dst.Size()) 551 if err != nil { 552 return err 553 } 554 action, actioned := "delete", "Deleted" 555 if backupDir != nil { 556 action, actioned = "move into backup dir", "Moved into backup dir" 557 } 558 skip := SkipDestructive(ctx, dst, action) 559 if skip { 560 // do nothing 561 } else if backupDir != nil { 562 err = MoveBackupDir(ctx, backupDir, dst) 563 } else { 564 err = dst.Remove(ctx) 565 } 566 if err != nil { 567 fs.Errorf(dst, "Couldn't %s: %v", action, err) 568 err = fs.CountError(err) 569 } else if !skip { 570 fs.Infof(dst, actioned) 571 } 572 return err 573 } 574 575 // DeleteFile deletes a single file respecting --dry-run and accumulating stats and errors. 576 // 577 // If useBackupDir is set and --backup-dir is in effect then it moves 578 // the file to there instead of deleting 579 func DeleteFile(ctx context.Context, dst fs.Object) (err error) { 580 return DeleteFileWithBackupDir(ctx, dst, nil) 581 } 582 583 // DeleteFilesWithBackupDir removes all the files passed in the 584 // channel 585 // 586 // If backupDir is set the files will be placed into that directory 587 // instead of being deleted. 588 func DeleteFilesWithBackupDir(ctx context.Context, toBeDeleted fs.ObjectsChan, backupDir fs.Fs) error { 589 var wg sync.WaitGroup 590 ci := fs.GetConfig(ctx) 591 wg.Add(ci.Checkers) 592 var errorCount atomic.Int32 593 var fatalErrorCount atomic.Int32 594 595 for i := 0; i < ci.Checkers; i++ { 596 go func() { 597 defer wg.Done() 598 for dst := range toBeDeleted { 599 err := DeleteFileWithBackupDir(ctx, dst, backupDir) 600 if err != nil { 601 errorCount.Add(1) 602 logger, _ := GetLogger(ctx) 603 logger(ctx, TransferError, nil, dst, err) 604 if fserrors.IsFatalError(err) { 605 fs.Errorf(dst, "Got fatal error on delete: %s", err) 606 fatalErrorCount.Add(1) 607 return 608 } 609 } 610 } 611 }() 612 } 613 fs.Debugf(nil, "Waiting for deletions to finish") 614 wg.Wait() 615 if errorCount.Load() > 0 { 616 err := fmt.Errorf("failed to delete %d files", errorCount.Load()) 617 if fatalErrorCount.Load() > 0 { 618 return fserrors.FatalError(err) 619 } 620 return err 621 } 622 return nil 623 } 624 625 // DeleteFiles removes all the files passed in the channel 626 func DeleteFiles(ctx context.Context, toBeDeleted fs.ObjectsChan) error { 627 return DeleteFilesWithBackupDir(ctx, toBeDeleted, nil) 628 } 629 630 // SameRemoteType returns true if fdst and fsrc are the same type 631 func SameRemoteType(fdst, fsrc fs.Info) bool { 632 return fmt.Sprintf("%T", fdst) == fmt.Sprintf("%T", fsrc) 633 } 634 635 // SameConfig returns true if fdst and fsrc are using the same config 636 // file entry 637 func SameConfig(fdst, fsrc fs.Info) bool { 638 return fdst.Name() == fsrc.Name() 639 } 640 641 // SameConfigArr returns true if any of []fsrcs has same config file entry with fdst 642 func SameConfigArr(fdst fs.Info, fsrcs []fs.Fs) bool { 643 for _, fsrc := range fsrcs { 644 if fdst.Name() == fsrc.Name() { 645 return true 646 } 647 } 648 return false 649 } 650 651 // Same returns true if fdst and fsrc point to the same underlying Fs 652 func Same(fdst, fsrc fs.Info) bool { 653 return SameConfig(fdst, fsrc) && strings.Trim(fdst.Root(), "/") == strings.Trim(fsrc.Root(), "/") 654 } 655 656 // fixRoot returns the Root with a trailing / if not empty. 657 // 658 // It returns a case folded version for case insensitive file systems 659 func fixRoot(f fs.Info) (s string, folded string) { 660 s = strings.Trim(filepath.ToSlash(f.Root()), "/") 661 if s != "" { 662 s += "/" 663 } 664 folded = s 665 if f.Features().CaseInsensitive { 666 folded = strings.ToLower(s) 667 } 668 return s, folded 669 } 670 671 // OverlappingFilterCheck returns true if fdst and fsrc point to the same 672 // underlying Fs and they overlap without fdst being excluded by any filter rule. 673 func OverlappingFilterCheck(ctx context.Context, fdst fs.Fs, fsrc fs.Fs) bool { 674 if !SameConfig(fdst, fsrc) { 675 return false 676 } 677 fdstRoot, fdstRootFolded := fixRoot(fdst) 678 fsrcRoot, fsrcRootFolded := fixRoot(fsrc) 679 if fdstRootFolded == fsrcRootFolded { 680 return true 681 } else if strings.HasPrefix(fdstRootFolded, fsrcRootFolded) { 682 fdstRelative := fdstRoot[len(fsrcRoot):] 683 return filterCheck(ctx, fsrc, fdstRelative) 684 } else if strings.HasPrefix(fsrcRootFolded, fdstRootFolded) { 685 fsrcRelative := fsrcRoot[len(fdstRoot):] 686 return filterCheck(ctx, fdst, fsrcRelative) 687 } 688 return false 689 } 690 691 // filterCheck checks if dir is included in f 692 func filterCheck(ctx context.Context, f fs.Fs, dir string) bool { 693 fi := filter.GetConfig(ctx) 694 includeDirectory := fi.IncludeDirectory(ctx, f) 695 include, err := includeDirectory(dir) 696 if err != nil { 697 fs.Errorf(f, "Failed to discover whether directory is included: %v", err) 698 return true 699 } 700 return include 701 } 702 703 // SameDir returns true if fdst and fsrc point to the same 704 // underlying Fs and they are the same directory. 705 func SameDir(fdst, fsrc fs.Info) bool { 706 if !SameConfig(fdst, fsrc) { 707 return false 708 } 709 _, fdstRootFolded := fixRoot(fdst) 710 _, fsrcRootFolded := fixRoot(fsrc) 711 return fdstRootFolded == fsrcRootFolded 712 } 713 714 // Retry runs fn up to maxTries times if it returns a retriable error 715 func Retry(ctx context.Context, o interface{}, maxTries int, fn func() error) (err error) { 716 for tries := 1; tries <= maxTries; tries++ { 717 // Call the function which might error 718 err = fn() 719 if err == nil { 720 break 721 } 722 // Retry if err returned a retry error 723 if fserrors.ContextError(ctx, &err) { 724 break 725 } 726 if fserrors.IsRetryError(err) || fserrors.ShouldRetry(err) { 727 fs.Debugf(o, "Received error: %v - low level retry %d/%d", err, tries, maxTries) 728 continue 729 } 730 break 731 } 732 return err 733 } 734 735 // ListFn lists the Fs to the supplied function 736 // 737 // Lists in parallel which may get them out of order 738 func ListFn(ctx context.Context, f fs.Fs, fn func(fs.Object)) error { 739 ci := fs.GetConfig(ctx) 740 return walk.ListR(ctx, f, "", false, ci.MaxDepth, walk.ListObjects, func(entries fs.DirEntries) error { 741 entries.ForObject(fn) 742 return nil 743 }) 744 } 745 746 // StdoutMutex mutex for synchronized output on stdout 747 var StdoutMutex sync.Mutex 748 749 // SyncPrintf is a global var holding the Printf function so that it 750 // can be overridden. 751 // 752 // This writes to stdout holding the StdoutMutex. If you are going to 753 // override it and write to os.Stdout then you should hold the 754 // StdoutMutex too. 755 var SyncPrintf = func(format string, a ...interface{}) { 756 StdoutMutex.Lock() 757 defer StdoutMutex.Unlock() 758 fmt.Printf(format, a...) 759 } 760 761 // SyncFprintf - Synchronized fmt.Fprintf 762 // 763 // Ignores errors from Fprintf. 764 // 765 // Prints to stdout if w is nil 766 func SyncFprintf(w io.Writer, format string, a ...interface{}) { 767 if w == nil || w == os.Stdout { 768 SyncPrintf(format, a...) 769 } else { 770 StdoutMutex.Lock() 771 defer StdoutMutex.Unlock() 772 _, _ = fmt.Fprintf(w, format, a...) 773 } 774 } 775 776 // SizeString make string representation of size for output 777 // 778 // Optional human-readable format including a binary suffix 779 func SizeString(size int64, humanReadable bool) string { 780 if humanReadable { 781 if size < 0 { 782 return "-" + fs.SizeSuffix(-size).String() 783 } 784 return fs.SizeSuffix(size).String() 785 } 786 return strconv.FormatInt(size, 10) 787 } 788 789 // SizeStringField make string representation of size for output in fixed width field 790 // 791 // Optional human-readable format including a binary suffix 792 // Argument rawWidth is used to format field with of raw value. When humanReadable 793 // option the width is hard coded to 9, since SizeSuffix strings have precision 3 794 // and longest value will be "999.999Ei". This way the width can be optimized 795 // depending to the humanReadable option. To always use a longer width the return 796 // value can always be fed into another format string with a specific field with. 797 func SizeStringField(size int64, humanReadable bool, rawWidth int) string { 798 str := SizeString(size, humanReadable) 799 if humanReadable { 800 return fmt.Sprintf("%9s", str) 801 } 802 return fmt.Sprintf("%[2]*[1]s", str, rawWidth) 803 } 804 805 // CountString make string representation of count for output 806 // 807 // Optional human-readable format including a decimal suffix 808 func CountString(count int64, humanReadable bool) string { 809 if humanReadable { 810 if count < 0 { 811 return "-" + fs.CountSuffix(-count).String() 812 } 813 return fs.CountSuffix(count).String() 814 } 815 return strconv.FormatInt(count, 10) 816 } 817 818 // CountStringField make string representation of count for output in fixed width field 819 // 820 // Similar to SizeStringField, but human readable with decimal prefix and field width 8 821 // since there is no 'i' in the decimal prefix symbols (e.g. "999.999E") 822 func CountStringField(count int64, humanReadable bool, rawWidth int) string { 823 str := CountString(count, humanReadable) 824 if humanReadable { 825 return fmt.Sprintf("%8s", str) 826 } 827 return fmt.Sprintf("%[2]*[1]s", str, rawWidth) 828 } 829 830 // List the Fs to the supplied writer 831 // 832 // Shows size and path - obeys includes and excludes. 833 // 834 // Lists in parallel which may get them out of order 835 func List(ctx context.Context, f fs.Fs, w io.Writer) error { 836 ci := fs.GetConfig(ctx) 837 return ListFn(ctx, f, func(o fs.Object) { 838 SyncFprintf(w, "%s %s\n", SizeStringField(o.Size(), ci.HumanReadable, 9), o.Remote()) 839 }) 840 } 841 842 // ListLong lists the Fs to the supplied writer 843 // 844 // Shows size, mod time and path - obeys includes and excludes. 845 // 846 // Lists in parallel which may get them out of order 847 func ListLong(ctx context.Context, f fs.Fs, w io.Writer) error { 848 ci := fs.GetConfig(ctx) 849 return ListFn(ctx, f, func(o fs.Object) { 850 tr := accounting.Stats(ctx).NewCheckingTransfer(o, "listing") 851 defer func() { 852 tr.Done(ctx, nil) 853 }() 854 modTime := o.ModTime(ctx) 855 SyncFprintf(w, "%s %s %s\n", SizeStringField(o.Size(), ci.HumanReadable, 9), modTime.Local().Format("2006-01-02 15:04:05.000000000"), o.Remote()) 856 }) 857 } 858 859 // HashSum returns the human-readable hash for ht passed in. This may 860 // be UNSUPPORTED or ERROR. If it isn't returning a valid hash it will 861 // return an error. 862 func HashSum(ctx context.Context, ht hash.Type, base64Encoded bool, downloadFlag bool, o fs.Object) (string, error) { 863 var sum string 864 var err error 865 866 // If downloadFlag is true, download and hash the file. 867 // If downloadFlag is false, call o.Hash asking the remote for the hash 868 if downloadFlag { 869 // Setup: Define accounting, open the file with NewReOpen to provide restarts, account for the transfer, and setup a multi-hasher with the appropriate type 870 // Execution: io.Copy file to hasher, get hash and encode in hex 871 872 tr := accounting.Stats(ctx).NewTransfer(o, nil) 873 defer func() { 874 tr.Done(ctx, err) 875 }() 876 877 // Open with NewReOpen to provide restarts 878 var options []fs.OpenOption 879 for _, option := range fs.GetConfig(ctx).DownloadHeaders { 880 options = append(options, option) 881 } 882 var in io.ReadCloser 883 in, err = Open(ctx, o, options...) 884 if err != nil { 885 return "ERROR", fmt.Errorf("failed to open file %v: %w", o, err) 886 } 887 888 // Account and buffer the transfer 889 in = tr.Account(ctx, in).WithBuffer() 890 891 // Setup hasher 892 hasher, err := hash.NewMultiHasherTypes(hash.NewHashSet(ht)) 893 if err != nil { 894 return "UNSUPPORTED", fmt.Errorf("hash unsupported: %w", err) 895 } 896 897 // Copy to hasher, downloading the file and passing directly to hash 898 _, err = io.Copy(hasher, in) 899 if err != nil { 900 return "ERROR", fmt.Errorf("failed to copy file to hasher: %w", err) 901 } 902 903 // Get hash as hex or base64 encoded string 904 sum, err = hasher.SumString(ht, base64Encoded) 905 if err != nil { 906 return "ERROR", fmt.Errorf("hasher returned an error: %w", err) 907 } 908 } else { 909 tr := accounting.Stats(ctx).NewCheckingTransfer(o, "hashing") 910 defer func() { 911 tr.Done(ctx, err) 912 }() 913 914 sum, err = o.Hash(ctx, ht) 915 if base64Encoded { 916 hexBytes, _ := hex.DecodeString(sum) 917 sum = base64.URLEncoding.EncodeToString(hexBytes) 918 } 919 if err == hash.ErrUnsupported { 920 return "", fmt.Errorf("hash unsupported: %w", err) 921 } 922 if err != nil { 923 return "", fmt.Errorf("failed to get hash %v from backend: %w", ht, err) 924 } 925 } 926 927 return sum, nil 928 } 929 930 // HashLister does an md5sum equivalent for the hash type passed in 931 // Updated to handle both standard hex encoding and base64 932 // Updated to perform multiple hashes concurrently 933 func HashLister(ctx context.Context, ht hash.Type, outputBase64 bool, downloadFlag bool, f fs.Fs, w io.Writer) error { 934 width := hash.Width(ht, outputBase64) 935 // Use --checkers concurrency unless downloading in which case use --transfers 936 concurrency := fs.GetConfig(ctx).Checkers 937 if downloadFlag { 938 concurrency = fs.GetConfig(ctx).Transfers 939 } 940 concurrencyControl := make(chan struct{}, concurrency) 941 var wg sync.WaitGroup 942 err := ListFn(ctx, f, func(o fs.Object) { 943 wg.Add(1) 944 concurrencyControl <- struct{}{} 945 go func() { 946 defer func() { 947 <-concurrencyControl 948 wg.Done() 949 }() 950 sum, err := HashSum(ctx, ht, outputBase64, downloadFlag, o) 951 if err != nil { 952 fs.Errorf(o, "%v", fs.CountError(err)) 953 return 954 } 955 SyncFprintf(w, "%*s %s\n", width, sum, o.Remote()) 956 }() 957 }) 958 wg.Wait() 959 return err 960 } 961 962 // HashSumStream outputs a line compatible with md5sum to w based on the 963 // input stream in and the hash type ht passed in. If outputBase64 is 964 // set then the hash will be base64 instead of hexadecimal. 965 func HashSumStream(ht hash.Type, outputBase64 bool, in io.ReadCloser, w io.Writer) error { 966 hasher, err := hash.NewMultiHasherTypes(hash.NewHashSet(ht)) 967 if err != nil { 968 return fmt.Errorf("hash unsupported: %w", err) 969 } 970 written, err := io.Copy(hasher, in) 971 fs.Debugf(nil, "Creating %s hash of %d bytes read from input stream", ht, written) 972 if err != nil { 973 return fmt.Errorf("failed to copy input to hasher: %w", err) 974 } 975 sum, err := hasher.SumString(ht, outputBase64) 976 if err != nil { 977 return fmt.Errorf("hasher returned an error: %w", err) 978 } 979 width := hash.Width(ht, outputBase64) 980 SyncFprintf(w, "%*s -\n", width, sum) 981 return nil 982 } 983 984 // Count counts the objects and their sizes in the Fs 985 // 986 // Obeys includes and excludes 987 func Count(ctx context.Context, f fs.Fs) (objects int64, size int64, sizelessObjects int64, err error) { 988 err = ListFn(ctx, f, func(o fs.Object) { 989 atomic.AddInt64(&objects, 1) 990 objectSize := o.Size() 991 if objectSize < 0 { 992 atomic.AddInt64(&sizelessObjects, 1) 993 } else if objectSize > 0 { 994 atomic.AddInt64(&size, objectSize) 995 } 996 }) 997 return 998 } 999 1000 // ConfigMaxDepth returns the depth to use for a recursive or non recursive listing. 1001 func ConfigMaxDepth(ctx context.Context, recursive bool) int { 1002 ci := fs.GetConfig(ctx) 1003 depth := ci.MaxDepth 1004 if !recursive && depth < 0 { 1005 depth = 1 1006 } 1007 return depth 1008 } 1009 1010 // ListDir lists the directories/buckets/containers in the Fs to the supplied writer 1011 func ListDir(ctx context.Context, f fs.Fs, w io.Writer) error { 1012 ci := fs.GetConfig(ctx) 1013 return walk.ListR(ctx, f, "", false, ConfigMaxDepth(ctx, false), walk.ListDirs, func(entries fs.DirEntries) error { 1014 entries.ForDir(func(dir fs.Directory) { 1015 if dir != nil { 1016 SyncFprintf(w, "%s %13s %s %s\n", SizeStringField(dir.Size(), ci.HumanReadable, 12), dir.ModTime(ctx).Local().Format("2006-01-02 15:04:05"), CountStringField(dir.Items(), ci.HumanReadable, 9), dir.Remote()) 1017 } 1018 }) 1019 return nil 1020 }) 1021 } 1022 1023 // Mkdir makes a destination directory or container 1024 func Mkdir(ctx context.Context, f fs.Fs, dir string) error { 1025 if SkipDestructive(ctx, fs.LogDirName(f, dir), "make directory") { 1026 return nil 1027 } 1028 fs.Debugf(fs.LogDirName(f, dir), "Making directory") 1029 err := f.Mkdir(ctx, dir) 1030 if err != nil { 1031 err = fs.CountError(err) 1032 return err 1033 } 1034 return nil 1035 } 1036 1037 // MkdirMetadata makes a destination directory or container with metadata 1038 // 1039 // If the destination Fs doesn't support this it will fall back to 1040 // Mkdir and in this case newDst will be nil. 1041 func MkdirMetadata(ctx context.Context, f fs.Fs, dir string, metadata fs.Metadata) (newDst fs.Directory, err error) { 1042 do := f.Features().MkdirMetadata 1043 if do == nil { 1044 return nil, Mkdir(ctx, f, dir) 1045 } 1046 logName := fs.LogDirName(f, dir) 1047 if SkipDestructive(ctx, logName, "make directory") { 1048 return nil, nil 1049 } 1050 fs.Debugf(fs.LogDirName(f, dir), "Making directory with metadata") 1051 newDst, err = do(ctx, dir, metadata) 1052 if err != nil { 1053 err = fs.CountError(err) 1054 return nil, err 1055 } 1056 if mtime, ok := metadata["mtime"]; ok { 1057 fs.Infof(logName, "Made directory with metadata (mtime=%s)", mtime) 1058 } else { 1059 fs.Infof(logName, "Made directory with metadata") 1060 } 1061 return newDst, err 1062 } 1063 1064 // MkdirModTime makes a destination directory or container with modtime 1065 // 1066 // It will try to make the directory with MkdirMetadata and if that 1067 // succeeds it will return a non-nil newDst. In all other cases newDst 1068 // will be nil. 1069 // 1070 // If the directory was created with MkDir then it will attempt to use 1071 // Fs.DirSetModTime to update the directory modtime if available. 1072 func MkdirModTime(ctx context.Context, f fs.Fs, dir string, modTime time.Time) (newDst fs.Directory, err error) { 1073 logName := fs.LogDirName(f, dir) 1074 if SkipDestructive(ctx, logName, "make directory") { 1075 return nil, nil 1076 } 1077 metadata := fs.Metadata{ 1078 "mtime": modTime.Format(time.RFC3339Nano), 1079 } 1080 newDst, err = MkdirMetadata(ctx, f, dir, metadata) 1081 if err != nil { 1082 return nil, err 1083 } 1084 if newDst != nil { 1085 // The directory was created and we have logged already 1086 return newDst, nil 1087 } 1088 // The directory was created with Mkdir then we should try to set the time 1089 if do := f.Features().DirSetModTime; do != nil { 1090 err = do(ctx, dir, modTime) 1091 } 1092 fs.Infof(logName, "Made directory with modification time %v", modTime) 1093 return newDst, err 1094 } 1095 1096 // TryRmdir removes a container but not if not empty. It doesn't 1097 // count errors but may return one. 1098 func TryRmdir(ctx context.Context, f fs.Fs, dir string) error { 1099 accounting.Stats(ctx).DeletedDirs(1) 1100 if SkipDestructive(ctx, fs.LogDirName(f, dir), "remove directory") { 1101 return nil 1102 } 1103 fs.Infof(fs.LogDirName(f, dir), "Removing directory") 1104 return f.Rmdir(ctx, dir) 1105 } 1106 1107 // Rmdir removes a container but not if not empty 1108 func Rmdir(ctx context.Context, f fs.Fs, dir string) error { 1109 err := TryRmdir(ctx, f, dir) 1110 if err != nil { 1111 err = fs.CountError(err) 1112 return err 1113 } 1114 return err 1115 } 1116 1117 // Purge removes a directory and all of its contents 1118 func Purge(ctx context.Context, f fs.Fs, dir string) (err error) { 1119 doFallbackPurge := true 1120 if doPurge := f.Features().Purge; doPurge != nil { 1121 doFallbackPurge = false 1122 accounting.Stats(ctx).DeletedDirs(1) 1123 if SkipDestructive(ctx, fs.LogDirName(f, dir), "purge directory") { 1124 return nil 1125 } 1126 err = doPurge(ctx, dir) 1127 if errors.Is(err, fs.ErrorCantPurge) { 1128 doFallbackPurge = true 1129 } 1130 } 1131 if doFallbackPurge { 1132 // DeleteFiles and Rmdir observe --dry-run 1133 err = DeleteFiles(ctx, listToChan(ctx, f, dir)) 1134 if err != nil { 1135 return err 1136 } 1137 err = Rmdirs(ctx, f, dir, false) 1138 } 1139 if err != nil { 1140 err = fs.CountError(err) 1141 return err 1142 } 1143 return nil 1144 } 1145 1146 // Delete removes all the contents of a container. Unlike Purge, it 1147 // obeys includes and excludes. 1148 func Delete(ctx context.Context, f fs.Fs) error { 1149 ci := fs.GetConfig(ctx) 1150 delChan := make(fs.ObjectsChan, ci.Checkers) 1151 delErr := make(chan error, 1) 1152 go func() { 1153 delErr <- DeleteFiles(ctx, delChan) 1154 }() 1155 err := ListFn(ctx, f, func(o fs.Object) { 1156 delChan <- o 1157 }) 1158 close(delChan) 1159 delError := <-delErr 1160 if err == nil { 1161 err = delError 1162 } 1163 return err 1164 } 1165 1166 // listToChan will transfer all objects in the listing to the output 1167 // 1168 // If an error occurs, the error will be logged, and it will close the 1169 // channel. 1170 // 1171 // If the error was ErrorDirNotFound then it will be ignored 1172 func listToChan(ctx context.Context, f fs.Fs, dir string) fs.ObjectsChan { 1173 ci := fs.GetConfig(ctx) 1174 o := make(fs.ObjectsChan, ci.Checkers) 1175 go func() { 1176 defer close(o) 1177 err := walk.ListR(ctx, f, dir, true, ci.MaxDepth, walk.ListObjects, func(entries fs.DirEntries) error { 1178 entries.ForObject(func(obj fs.Object) { 1179 o <- obj 1180 }) 1181 return nil 1182 }) 1183 if err != nil && err != fs.ErrorDirNotFound { 1184 err = fmt.Errorf("failed to list: %w", err) 1185 err = fs.CountError(err) 1186 fs.Errorf(nil, "%v", err) 1187 } 1188 }() 1189 return o 1190 } 1191 1192 // CleanUp removes the trash for the Fs 1193 func CleanUp(ctx context.Context, f fs.Fs) error { 1194 doCleanUp := f.Features().CleanUp 1195 if doCleanUp == nil { 1196 return fmt.Errorf("%v doesn't support cleanup", f) 1197 } 1198 if SkipDestructive(ctx, f, "clean up old files") { 1199 return nil 1200 } 1201 return doCleanUp(ctx) 1202 } 1203 1204 // wrap a Reader and a Closer together into a ReadCloser 1205 type readCloser struct { 1206 io.Reader 1207 io.Closer 1208 } 1209 1210 // Cat any files to the io.Writer 1211 // 1212 // if offset == 0 it will be ignored 1213 // if offset > 0 then the file will be seeked to that offset 1214 // if offset < 0 then the file will be seeked that far from the end 1215 // 1216 // if count < 0 then it will be ignored 1217 // if count >= 0 then only that many characters will be output 1218 func Cat(ctx context.Context, f fs.Fs, w io.Writer, offset, count int64, sep []byte) error { 1219 var mu sync.Mutex 1220 ci := fs.GetConfig(ctx) 1221 return ListFn(ctx, f, func(o fs.Object) { 1222 var err error 1223 tr := accounting.Stats(ctx).NewTransfer(o, nil) 1224 defer func() { 1225 tr.Done(ctx, err) 1226 }() 1227 opt := fs.RangeOption{Start: offset, End: -1} 1228 size := o.Size() 1229 if opt.Start < 0 { 1230 opt.Start += size 1231 } 1232 if count >= 0 { 1233 opt.End = opt.Start + count - 1 1234 } 1235 var options []fs.OpenOption 1236 if opt.Start > 0 || opt.End >= 0 { 1237 options = append(options, &opt) 1238 } 1239 for _, option := range ci.DownloadHeaders { 1240 options = append(options, option) 1241 } 1242 var in io.ReadCloser 1243 in, err = Open(ctx, o, options...) 1244 if err != nil { 1245 err = fs.CountError(err) 1246 fs.Errorf(o, "Failed to open: %v", err) 1247 return 1248 } 1249 if count >= 0 { 1250 in = &readCloser{Reader: &io.LimitedReader{R: in, N: count}, Closer: in} 1251 } 1252 in = tr.Account(ctx, in).WithBuffer() // account and buffer the transfer 1253 // take the lock just before we output stuff, so at the last possible moment 1254 mu.Lock() 1255 defer mu.Unlock() 1256 _, err = io.Copy(w, in) 1257 if err != nil { 1258 err = fs.CountError(err) 1259 fs.Errorf(o, "Failed to send to output: %v", err) 1260 } 1261 if len(sep) >= 0 { 1262 _, err = w.Write(sep) 1263 if err != nil { 1264 err = fs.CountError(err) 1265 fs.Errorf(o, "Failed to send separator to output: %v", err) 1266 } 1267 } 1268 }) 1269 } 1270 1271 // Rcat reads data from the Reader until EOF and uploads it to a file on remote 1272 func Rcat(ctx context.Context, fdst fs.Fs, dstFileName string, in io.ReadCloser, modTime time.Time, meta fs.Metadata) (dst fs.Object, err error) { 1273 ci := fs.GetConfig(ctx) 1274 tr := accounting.Stats(ctx).NewTransferRemoteSize(dstFileName, -1, nil, fdst) 1275 defer func() { 1276 tr.Done(ctx, err) 1277 }() 1278 in = tr.Account(ctx, in).WithBuffer() 1279 1280 readCounter := readers.NewCountingReader(in) 1281 var trackingIn io.Reader 1282 var hasher *hash.MultiHasher 1283 var options []fs.OpenOption 1284 if !ci.IgnoreChecksum { 1285 hashes := hash.NewHashSet(fdst.Hashes().GetOne()) // just pick one hash 1286 hashOption := &fs.HashesOption{Hashes: hashes} 1287 options = append(options, hashOption) 1288 hasher, err = hash.NewMultiHasherTypes(hashes) 1289 if err != nil { 1290 return nil, err 1291 } 1292 trackingIn = io.TeeReader(readCounter, hasher) 1293 } else { 1294 trackingIn = readCounter 1295 } 1296 for _, option := range ci.UploadHeaders { 1297 options = append(options, option) 1298 } 1299 if ci.MetadataSet != nil { 1300 options = append(options, fs.MetadataOption(ci.MetadataSet)) 1301 } 1302 1303 compare := func(dst fs.Object) error { 1304 var sums map[hash.Type]string 1305 opt := defaultEqualOpt(ctx) 1306 if hasher != nil { 1307 // force --checksum on if we have hashes 1308 opt.checkSum = true 1309 sums = hasher.Sums() 1310 } 1311 src := object.NewStaticObjectInfo(dstFileName, modTime, int64(readCounter.BytesRead()), false, sums, fdst).WithMetadata(meta) 1312 if !equal(ctx, src, dst, opt) { 1313 err = fmt.Errorf("corrupted on transfer") 1314 err = fs.CountError(err) 1315 fs.Errorf(dst, "%v", err) 1316 return err 1317 } 1318 return nil 1319 } 1320 1321 // check if file small enough for direct upload 1322 buf := make([]byte, ci.StreamingUploadCutoff) 1323 if n, err := io.ReadFull(trackingIn, buf); err == io.EOF || err == io.ErrUnexpectedEOF { 1324 fs.Debugf(fdst, "File to upload is small (%d bytes), uploading instead of streaming", n) 1325 src := object.NewMemoryObject(dstFileName, modTime, buf[:n]).WithMetadata(meta) 1326 return Copy(ctx, fdst, nil, dstFileName, src) 1327 } 1328 1329 // Make a new ReadCloser with the bits we've already read 1330 in = &readCloser{ 1331 Reader: io.MultiReader(bytes.NewReader(buf), trackingIn), 1332 Closer: in, 1333 } 1334 1335 fStreamTo := fdst 1336 canStream := fdst.Features().PutStream != nil 1337 if !canStream { 1338 fs.Debugf(fdst, "Target remote doesn't support streaming uploads, creating temporary local FS to spool file") 1339 tmpLocalFs, err := fs.TemporaryLocalFs(ctx) 1340 if err != nil { 1341 return nil, fmt.Errorf("failed to create temporary local FS to spool file: %w", err) 1342 } 1343 defer func() { 1344 err := Purge(ctx, tmpLocalFs, "") 1345 if err != nil { 1346 fs.Infof(tmpLocalFs, "Failed to cleanup temporary FS: %v", err) 1347 } 1348 }() 1349 fStreamTo = tmpLocalFs 1350 } 1351 1352 if SkipDestructive(ctx, dstFileName, "upload from pipe") { 1353 // prevents "broken pipe" errors 1354 _, err = io.Copy(io.Discard, in) 1355 return nil, err 1356 } 1357 1358 objInfo := object.NewStaticObjectInfo(dstFileName, modTime, -1, false, nil, nil).WithMetadata(meta) 1359 if dst, err = fStreamTo.Features().PutStream(ctx, in, objInfo, options...); err != nil { 1360 return dst, err 1361 } 1362 if err = compare(dst); err != nil { 1363 return dst, err 1364 } 1365 if !canStream { 1366 // copy dst (which is the local object we have just streamed to) to the remote 1367 newCtx := ctx 1368 if ci.Metadata && len(meta) != 0 { 1369 // If we have metadata and we are setting it then use 1370 // the --metadataset mechanism to supply it to Copy 1371 var newCi *fs.ConfigInfo 1372 newCtx, newCi = fs.AddConfig(ctx) 1373 if len(newCi.MetadataSet) == 0 { 1374 newCi.MetadataSet = meta 1375 } else { 1376 var newMeta fs.Metadata 1377 newMeta.Merge(meta) 1378 newMeta.Merge(newCi.MetadataSet) // --metadata-set takes priority 1379 newCi.MetadataSet = newMeta 1380 } 1381 } 1382 return Copy(newCtx, fdst, nil, dstFileName, dst) 1383 } 1384 return dst, nil 1385 } 1386 1387 // PublicLink adds a "readable by anyone with link" permission on the given file or folder. 1388 func PublicLink(ctx context.Context, f fs.Fs, remote string, expire fs.Duration, unlink bool) (string, error) { 1389 doPublicLink := f.Features().PublicLink 1390 if doPublicLink == nil { 1391 return "", fmt.Errorf("%v doesn't support public links", f) 1392 } 1393 return doPublicLink(ctx, remote, expire, unlink) 1394 } 1395 1396 // Rmdirs removes any empty directories (or directories only 1397 // containing empty directories) under f, including f. 1398 // 1399 // Rmdirs obeys the filters 1400 func Rmdirs(ctx context.Context, f fs.Fs, dir string, leaveRoot bool) error { 1401 ci := fs.GetConfig(ctx) 1402 fi := filter.GetConfig(ctx) 1403 dirEmpty := make(map[string]bool) 1404 dirEmpty[dir] = !leaveRoot 1405 err := walk.Walk(ctx, f, dir, false, ci.MaxDepth, func(dirPath string, entries fs.DirEntries, err error) error { 1406 if err != nil { 1407 err = fs.CountError(err) 1408 fs.Errorf(f, "Failed to list %q: %v", dirPath, err) 1409 return nil 1410 } 1411 for _, entry := range entries { 1412 switch x := entry.(type) { 1413 case fs.Directory: 1414 // add a new directory as empty 1415 dir := x.Remote() 1416 _, found := dirEmpty[dir] 1417 if !found { 1418 dirEmpty[dir] = true 1419 } 1420 case fs.Object: 1421 // mark the parents of the file as being non-empty 1422 dir := x.Remote() 1423 for dir != "" { 1424 dir = path.Dir(dir) 1425 if dir == "." || dir == "/" { 1426 dir = "" 1427 } 1428 empty, found := dirEmpty[dir] 1429 // End if we reach a directory which is non-empty 1430 if found && !empty { 1431 break 1432 } 1433 dirEmpty[dir] = false 1434 } 1435 } 1436 } 1437 return nil 1438 }) 1439 if err != nil { 1440 return fmt.Errorf("failed to rmdirs: %w", err) 1441 } 1442 1443 // Group directories to delete by level 1444 var toDelete [][]string 1445 for dir, empty := range dirEmpty { 1446 if empty { 1447 // If a filter matches the directory then that 1448 // directory is a candidate for deletion 1449 if fi.IncludeRemote(dir + "/") { 1450 level := strings.Count(dir, "/") + 1 1451 // The root directory "" is at the top level 1452 if dir == "" { 1453 level = 0 1454 } 1455 if len(toDelete) < level+1 { 1456 toDelete = append(toDelete, make([][]string, level+1-len(toDelete))...) 1457 } 1458 toDelete[level] = append(toDelete[level], dir) 1459 } 1460 } 1461 } 1462 1463 errCount := errcount.New() 1464 // Delete all directories at the same level in parallel 1465 for level := len(toDelete) - 1; level >= 0; level-- { 1466 dirs := toDelete[level] 1467 if len(dirs) == 0 { 1468 continue 1469 } 1470 fs.Debugf(nil, "removing %d level %d directories", len(dirs), level) 1471 sort.Strings(dirs) 1472 g, gCtx := errgroup.WithContext(ctx) 1473 g.SetLimit(ci.Checkers) 1474 for _, dir := range dirs { 1475 // End early if error 1476 if gCtx.Err() != nil { 1477 break 1478 } 1479 dir := dir 1480 g.Go(func() error { 1481 err := TryRmdir(gCtx, f, dir) 1482 if err != nil { 1483 err = fs.CountError(err) 1484 fs.Errorf(dir, "Failed to rmdir: %v", err) 1485 errCount.Add(err) 1486 } 1487 return nil // don't return errors, just count them 1488 }) 1489 } 1490 err := g.Wait() 1491 if err != nil { 1492 return err 1493 } 1494 } 1495 return errCount.Err("failed to remove directories") 1496 } 1497 1498 // GetCompareDest sets up --compare-dest 1499 func GetCompareDest(ctx context.Context) (CompareDest []fs.Fs, err error) { 1500 ci := fs.GetConfig(ctx) 1501 CompareDest, err = cache.GetArr(ctx, ci.CompareDest) 1502 if err != nil { 1503 return nil, fserrors.FatalError(fmt.Errorf("failed to make fs for --compare-dest %q: %w", ci.CompareDest, err)) 1504 } 1505 return CompareDest, nil 1506 } 1507 1508 // compareDest checks --compare-dest to see if src needs to 1509 // be copied 1510 // 1511 // Returns True if src is in --compare-dest 1512 func compareDest(ctx context.Context, dst, src fs.Object, CompareDest fs.Fs) (NoNeedTransfer bool, err error) { 1513 var remote string 1514 if dst == nil { 1515 remote = src.Remote() 1516 } else { 1517 remote = dst.Remote() 1518 } 1519 CompareDestFile, err := CompareDest.NewObject(ctx, remote) 1520 switch err { 1521 case fs.ErrorObjectNotFound: 1522 return false, nil 1523 case nil: 1524 break 1525 default: 1526 return false, err 1527 } 1528 opt := defaultEqualOpt(ctx) 1529 opt.updateModTime = false 1530 if equal(ctx, src, CompareDestFile, opt) { 1531 fs.Debugf(src, "Destination found in --compare-dest, skipping") 1532 return true, nil 1533 } 1534 return false, nil 1535 } 1536 1537 // GetCopyDest sets up --copy-dest 1538 func GetCopyDest(ctx context.Context, fdst fs.Fs) (CopyDest []fs.Fs, err error) { 1539 ci := fs.GetConfig(ctx) 1540 CopyDest, err = cache.GetArr(ctx, ci.CopyDest) 1541 if err != nil { 1542 return nil, fserrors.FatalError(fmt.Errorf("failed to make fs for --copy-dest %q: %w", ci.CopyDest, err)) 1543 } 1544 if !SameConfigArr(fdst, CopyDest) { 1545 return nil, fserrors.FatalError(errors.New("parameter to --copy-dest has to be on the same remote as destination")) 1546 } 1547 for _, cf := range CopyDest { 1548 if cf.Features().Copy == nil { 1549 return nil, fserrors.FatalError(errors.New("can't use --copy-dest on a remote which doesn't support server side copy")) 1550 } 1551 } 1552 1553 return CopyDest, nil 1554 } 1555 1556 // copyDest checks --copy-dest to see if src needs to 1557 // be copied 1558 // 1559 // Returns True if src was copied from --copy-dest 1560 func copyDest(ctx context.Context, fdst fs.Fs, dst, src fs.Object, CopyDest, backupDir fs.Fs) (NoNeedTransfer bool, err error) { 1561 var remote string 1562 if dst == nil { 1563 remote = src.Remote() 1564 } else { 1565 remote = dst.Remote() 1566 } 1567 CopyDestFile, err := CopyDest.NewObject(ctx, remote) 1568 switch err { 1569 case fs.ErrorObjectNotFound: 1570 return false, nil 1571 case nil: 1572 break 1573 default: 1574 return false, err 1575 } 1576 opt := defaultEqualOpt(ctx) 1577 opt.updateModTime = false 1578 if equal(ctx, src, CopyDestFile, opt) { 1579 if dst == nil || !Equal(ctx, src, dst) { 1580 if dst != nil && backupDir != nil { 1581 err = MoveBackupDir(ctx, backupDir, dst) 1582 if err != nil { 1583 return false, fmt.Errorf("moving to --backup-dir failed: %w", err) 1584 } 1585 // If successful zero out the dstObj as it is no longer there 1586 dst = nil 1587 } 1588 _, err := Copy(ctx, fdst, dst, remote, CopyDestFile) 1589 if err != nil { 1590 fs.Errorf(src, "Destination found in --copy-dest, error copying") 1591 return false, nil 1592 } 1593 fs.Debugf(src, "Destination found in --copy-dest, using server-side copy") 1594 return true, nil 1595 } 1596 fs.Debugf(src, "Unchanged skipping") 1597 return true, nil 1598 } 1599 fs.Debugf(src, "Destination not found in --copy-dest") 1600 return false, nil 1601 } 1602 1603 // CompareOrCopyDest checks --compare-dest and --copy-dest to see if src 1604 // does not need to be copied 1605 // 1606 // Returns True if src does not need to be copied 1607 func CompareOrCopyDest(ctx context.Context, fdst fs.Fs, dst, src fs.Object, CompareOrCopyDest []fs.Fs, backupDir fs.Fs) (NoNeedTransfer bool, err error) { 1608 ci := fs.GetConfig(ctx) 1609 if len(ci.CompareDest) > 0 { 1610 for _, compareF := range CompareOrCopyDest { 1611 NoNeedTransfer, err := compareDest(ctx, dst, src, compareF) 1612 if NoNeedTransfer || err != nil { 1613 return NoNeedTransfer, err 1614 } 1615 } 1616 } else if len(ci.CopyDest) > 0 { 1617 for _, copyF := range CompareOrCopyDest { 1618 NoNeedTransfer, err := copyDest(ctx, fdst, dst, src, copyF, backupDir) 1619 if NoNeedTransfer || err != nil { 1620 return NoNeedTransfer, err 1621 } 1622 } 1623 } 1624 return false, nil 1625 } 1626 1627 // NeedTransfer checks to see if src needs to be copied to dst using 1628 // the current config. 1629 // 1630 // Returns a flag which indicates whether the file needs to be 1631 // transferred or not. 1632 func NeedTransfer(ctx context.Context, dst, src fs.Object) bool { 1633 ci := fs.GetConfig(ctx) 1634 logger, _ := GetLogger(ctx) 1635 if dst == nil { 1636 fs.Debugf(src, "Need to transfer - File not found at Destination") 1637 logger(ctx, MissingOnDst, src, nil, nil) 1638 return true 1639 } 1640 // If we should ignore existing files, don't transfer 1641 if ci.IgnoreExisting { 1642 fs.Debugf(src, "Destination exists, skipping") 1643 logger(ctx, Match, src, dst, nil) 1644 return false 1645 } 1646 // If we should upload unconditionally 1647 if ci.IgnoreTimes { 1648 fs.Debugf(src, "Transferring unconditionally as --ignore-times is in use") 1649 logger(ctx, Differ, src, dst, nil) 1650 return true 1651 } 1652 // If UpdateOlder is in effect, skip if dst is newer than src 1653 if ci.UpdateOlder { 1654 srcModTime := src.ModTime(ctx) 1655 dstModTime := dst.ModTime(ctx) 1656 dt := dstModTime.Sub(srcModTime) 1657 // If have a mutually agreed precision then use that 1658 modifyWindow := fs.GetModifyWindow(ctx, dst.Fs(), src.Fs()) 1659 if modifyWindow == fs.ModTimeNotSupported { 1660 // Otherwise use 1 second as a safe default as 1661 // the resolution of the time a file was 1662 // uploaded. 1663 modifyWindow = time.Second 1664 } 1665 switch { 1666 case dt >= modifyWindow: 1667 fs.Debugf(src, "Destination is newer than source, skipping") 1668 logger(ctx, Match, src, dst, nil) 1669 return false 1670 case dt <= -modifyWindow: 1671 // force --checksum on for the check and do update modtimes by default 1672 opt := defaultEqualOpt(ctx) 1673 opt.forceModTimeMatch = true 1674 if equal(ctx, src, dst, opt) { 1675 fs.Debugf(src, "Unchanged skipping") 1676 return false 1677 } 1678 default: 1679 // Do a size only compare unless --checksum is set 1680 opt := defaultEqualOpt(ctx) 1681 opt.sizeOnly = !ci.CheckSum 1682 if equal(ctx, src, dst, opt) { 1683 fs.Debugf(src, "Destination mod time is within %v of source and files identical, skipping", modifyWindow) 1684 return false 1685 } 1686 fs.Debugf(src, "Destination mod time is within %v of source but files differ, transferring", modifyWindow) 1687 } 1688 } else { 1689 // Check to see if changed or not 1690 equalFn, ok := ctx.Value(equalFnKey).(EqualFn) 1691 if ok { 1692 return !equalFn(ctx, src, dst) 1693 } 1694 if Equal(ctx, src, dst) && !SameObject(src, dst) { 1695 fs.Debugf(src, "Unchanged skipping") 1696 return false 1697 } 1698 } 1699 return true 1700 } 1701 1702 // RcatSize reads data from the Reader until EOF and uploads it to a file on remote. 1703 // Pass in size >=0 if known, <0 if not known 1704 func RcatSize(ctx context.Context, fdst fs.Fs, dstFileName string, in io.ReadCloser, size int64, modTime time.Time, meta fs.Metadata) (dst fs.Object, err error) { 1705 var obj fs.Object 1706 1707 if size >= 0 { 1708 var err error 1709 // Size known use Put 1710 tr := accounting.Stats(ctx).NewTransferRemoteSize(dstFileName, size, nil, fdst) 1711 defer func() { 1712 tr.Done(ctx, err) 1713 }() 1714 body := io.NopCloser(in) // we let the server close the body 1715 in := tr.Account(ctx, body) // account the transfer (no buffering) 1716 1717 if SkipDestructive(ctx, dstFileName, "upload from pipe") { 1718 // prevents "broken pipe" errors 1719 _, err = io.Copy(io.Discard, in) 1720 return nil, err 1721 } 1722 1723 info := object.NewStaticObjectInfo(dstFileName, modTime, size, true, nil, fdst).WithMetadata(meta) 1724 obj, err = fdst.Put(ctx, in, info) 1725 if err != nil { 1726 fs.Errorf(dstFileName, "Post request put error: %v", err) 1727 1728 return nil, err 1729 } 1730 } else { 1731 // Size unknown use Rcat 1732 obj, err = Rcat(ctx, fdst, dstFileName, in, modTime, meta) 1733 if err != nil { 1734 fs.Errorf(dstFileName, "Post request rcat error: %v", err) 1735 1736 return nil, err 1737 } 1738 } 1739 1740 return obj, nil 1741 } 1742 1743 // copyURLFunc is called from CopyURLFn 1744 type copyURLFunc func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error) 1745 1746 // copyURLFn copies the data from the url to the function supplied 1747 func copyURLFn(ctx context.Context, dstFileName string, url string, autoFilename, dstFileNameFromHeader bool, fn copyURLFunc) (err error) { 1748 client := fshttp.NewClient(ctx) 1749 resp, err := client.Get(url) 1750 if err != nil { 1751 return err 1752 } 1753 defer fs.CheckClose(resp.Body, &err) 1754 if resp.StatusCode < 200 || resp.StatusCode >= 300 { 1755 return fmt.Errorf("CopyURL failed: %s", resp.Status) 1756 } 1757 modTime, err := http.ParseTime(resp.Header.Get("Last-Modified")) 1758 if err != nil { 1759 modTime = time.Now() 1760 } 1761 if autoFilename { 1762 if dstFileNameFromHeader { 1763 _, params, err := mime.ParseMediaType(resp.Header.Get("Content-Disposition")) 1764 headerFilename := path.Base(strings.Replace(params["filename"], "\\", "/", -1)) 1765 if err != nil || headerFilename == "" { 1766 return fmt.Errorf("CopyURL failed: filename not found in the Content-Disposition header") 1767 } 1768 fs.Debugf(headerFilename, "filename found in Content-Disposition header.") 1769 return fn(ctx, headerFilename, resp.Body, resp.ContentLength, modTime) 1770 } 1771 1772 dstFileName = path.Base(resp.Request.URL.Path) 1773 if dstFileName == "." || dstFileName == "/" { 1774 return fmt.Errorf("CopyURL failed: file name wasn't found in url") 1775 } 1776 fs.Debugf(dstFileName, "File name found in url") 1777 } 1778 return fn(ctx, dstFileName, resp.Body, resp.ContentLength, modTime) 1779 } 1780 1781 // CopyURL copies the data from the url to (fdst, dstFileName) 1782 func CopyURL(ctx context.Context, fdst fs.Fs, dstFileName string, url string, autoFilename, dstFileNameFromHeader bool, noClobber bool) (dst fs.Object, err error) { 1783 err = copyURLFn(ctx, dstFileName, url, autoFilename, dstFileNameFromHeader, func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error) { 1784 if noClobber { 1785 _, err = fdst.NewObject(ctx, dstFileName) 1786 if err == nil { 1787 return errors.New("CopyURL failed: file already exist") 1788 } 1789 } 1790 dst, err = RcatSize(ctx, fdst, dstFileName, in, size, modTime, nil) 1791 return err 1792 }) 1793 return dst, err 1794 } 1795 1796 // CopyURLToWriter copies the data from the url to the io.Writer supplied 1797 func CopyURLToWriter(ctx context.Context, url string, out io.Writer) (err error) { 1798 return copyURLFn(ctx, "", url, false, false, func(ctx context.Context, dstFileName string, in io.ReadCloser, size int64, modTime time.Time) (err error) { 1799 _, err = io.Copy(out, in) 1800 return err 1801 }) 1802 } 1803 1804 // BackupDir returns the correctly configured --backup-dir 1805 func BackupDir(ctx context.Context, fdst fs.Fs, fsrc fs.Fs, srcFileName string) (backupDir fs.Fs, err error) { 1806 ci := fs.GetConfig(ctx) 1807 if ci.BackupDir != "" { 1808 backupDir, err = cache.Get(ctx, ci.BackupDir) 1809 if err != nil { 1810 return nil, fserrors.FatalError(fmt.Errorf("failed to make fs for --backup-dir %q: %w", ci.BackupDir, err)) 1811 } 1812 if !SameConfig(fdst, backupDir) { 1813 return nil, fserrors.FatalError(errors.New("parameter to --backup-dir has to be on the same remote as destination")) 1814 } 1815 if srcFileName == "" { 1816 if OverlappingFilterCheck(ctx, backupDir, fdst) { 1817 return nil, fserrors.FatalError(errors.New("destination and parameter to --backup-dir mustn't overlap")) 1818 } 1819 if OverlappingFilterCheck(ctx, backupDir, fsrc) { 1820 return nil, fserrors.FatalError(errors.New("source and parameter to --backup-dir mustn't overlap")) 1821 } 1822 } else { 1823 if ci.Suffix == "" { 1824 if SameDir(fdst, backupDir) { 1825 return nil, fserrors.FatalError(errors.New("destination and parameter to --backup-dir mustn't be the same")) 1826 } 1827 if SameDir(fsrc, backupDir) { 1828 return nil, fserrors.FatalError(errors.New("source and parameter to --backup-dir mustn't be the same")) 1829 } 1830 } 1831 } 1832 } else if ci.Suffix != "" { 1833 // --backup-dir is not set but --suffix is - use the destination as the backupDir 1834 backupDir = fdst 1835 } else { 1836 return nil, fserrors.FatalError(errors.New("internal error: BackupDir called when --backup-dir and --suffix both empty")) 1837 } 1838 if !CanServerSideMove(backupDir) { 1839 return nil, fserrors.FatalError(errors.New("can't use --backup-dir on a remote which doesn't support server-side move or copy")) 1840 } 1841 return backupDir, nil 1842 } 1843 1844 // MoveBackupDir moves a file to the backup dir 1845 func MoveBackupDir(ctx context.Context, backupDir fs.Fs, dst fs.Object) (err error) { 1846 remoteWithSuffix := SuffixName(ctx, dst.Remote()) 1847 overwritten, _ := backupDir.NewObject(ctx, remoteWithSuffix) 1848 _, err = Move(ctx, backupDir, overwritten, remoteWithSuffix, dst) 1849 return err 1850 } 1851 1852 // needsMoveCaseInsensitive returns true if moveCaseInsensitive is needed 1853 func needsMoveCaseInsensitive(fdst fs.Fs, fsrc fs.Fs, dstFileName string, srcFileName string, cp bool) bool { 1854 dstFilePath := path.Join(fdst.Root(), dstFileName) 1855 srcFilePath := path.Join(fsrc.Root(), srcFileName) 1856 return !cp && fdst.Name() == fsrc.Name() && fdst.Features().CaseInsensitive && dstFileName != srcFileName && strings.EqualFold(dstFilePath, srcFilePath) 1857 } 1858 1859 // MoveCaseInsensitive handles changing case of a file on a case insensitive remote. 1860 // This will move the file to a temporary name then 1861 // move it back to the intended destination. This is required 1862 // to avoid issues with certain remotes and avoid file deletion. 1863 // returns nil, nil if !needsMoveCaseInsensitive. 1864 // this does not account a transfer -- the caller should do that if desired. 1865 func MoveCaseInsensitive(ctx context.Context, fdst fs.Fs, fsrc fs.Fs, dstFileName string, srcFileName string, cp bool, srcObj fs.Object) (newDst fs.Object, err error) { 1866 logger, _ := GetLogger(ctx) 1867 1868 // Choose operations 1869 Op := MoveTransfer 1870 if cp { 1871 Op = Copy 1872 } 1873 1874 if SkipDestructive(ctx, srcFileName, "rename to "+dstFileName) { 1875 // avoid fatalpanic on --dry-run (trying to access non-existent tmpObj) 1876 return nil, nil 1877 } 1878 // Create random name to temporarily move file to 1879 tmpObjName := dstFileName + "-rclone-move-" + random.String(8) 1880 tmpObjFail, err := fdst.NewObject(ctx, tmpObjName) 1881 if err != fs.ErrorObjectNotFound { 1882 if err == nil { 1883 logger(ctx, TransferError, nil, tmpObjFail, err) 1884 return nil, errors.New("found an already existing file with a randomly generated name. Try the operation again") 1885 } 1886 logger(ctx, TransferError, nil, tmpObjFail, err) 1887 return nil, fmt.Errorf("error while attempting to move file to a temporary location: %w", err) 1888 } 1889 fs.Debugf(srcObj, "moving to %v", tmpObjName) 1890 tmpObj, err := Op(ctx, fdst, nil, tmpObjName, srcObj) 1891 if err != nil { 1892 logger(ctx, TransferError, srcObj, tmpObj, err) 1893 return nil, fmt.Errorf("error while moving file to temporary location: %w", err) 1894 } 1895 fs.Debugf(srcObj, "moving to %v", dstFileName) 1896 newDst, err = Op(ctx, fdst, nil, dstFileName, tmpObj) 1897 logger(ctx, MissingOnDst, tmpObj, nil, err) 1898 return newDst, err 1899 } 1900 1901 // moveOrCopyFile moves or copies a single file possibly to a new name 1902 func moveOrCopyFile(ctx context.Context, fdst fs.Fs, fsrc fs.Fs, dstFileName string, srcFileName string, cp bool) (err error) { 1903 ci := fs.GetConfig(ctx) 1904 logger, usingLogger := GetLogger(ctx) 1905 dstFilePath := path.Join(fdst.Root(), dstFileName) 1906 srcFilePath := path.Join(fsrc.Root(), srcFileName) 1907 if fdst.Name() == fsrc.Name() && dstFilePath == srcFilePath { 1908 fs.Debugf(fdst, "don't need to copy/move %s, it is already at target location", dstFileName) 1909 if usingLogger { 1910 srcObj, _ := fsrc.NewObject(ctx, srcFileName) 1911 dstObj, _ := fsrc.NewObject(ctx, dstFileName) 1912 logger(ctx, Match, srcObj, dstObj, nil) 1913 } 1914 return nil 1915 } 1916 1917 // Choose operations 1918 Op := MoveTransfer 1919 if cp { 1920 Op = Copy 1921 } 1922 1923 // Find src object 1924 srcObj, err := fsrc.NewObject(ctx, srcFileName) 1925 if err != nil { 1926 logger(ctx, TransferError, srcObj, nil, err) 1927 return err 1928 } 1929 1930 // Find dst object if it exists 1931 var dstObj fs.Object 1932 if !ci.NoCheckDest { 1933 dstObj, err = fdst.NewObject(ctx, dstFileName) 1934 if errors.Is(err, fs.ErrorObjectNotFound) { 1935 dstObj = nil 1936 } else if err != nil { 1937 logger(ctx, TransferError, nil, dstObj, err) 1938 return err 1939 } 1940 } 1941 1942 // Special case for changing case of a file on a case insensitive remote 1943 // This will move the file to a temporary name then 1944 // move it back to the intended destination. This is required 1945 // to avoid issues with certain remotes and avoid file deletion. 1946 if needsMoveCaseInsensitive(fdst, fsrc, dstFileName, srcFileName, cp) { 1947 tr := accounting.Stats(ctx).NewTransfer(srcObj, fdst) 1948 defer func() { 1949 tr.Done(ctx, err) 1950 }() 1951 _, err = MoveCaseInsensitive(ctx, fdst, fsrc, dstFileName, srcFileName, cp, srcObj) 1952 return err 1953 } 1954 1955 var backupDir fs.Fs 1956 var copyDestDir []fs.Fs 1957 if ci.BackupDir != "" || ci.Suffix != "" { 1958 backupDir, err = BackupDir(ctx, fdst, fsrc, srcFileName) 1959 if err != nil { 1960 return fmt.Errorf("creating Fs for --backup-dir failed: %w", err) 1961 } 1962 } 1963 if len(ci.CompareDest) > 0 { 1964 copyDestDir, err = GetCompareDest(ctx) 1965 if err != nil { 1966 return err 1967 } 1968 } else if len(ci.CopyDest) > 0 { 1969 copyDestDir, err = GetCopyDest(ctx, fdst) 1970 if err != nil { 1971 return err 1972 } 1973 } 1974 needTransfer := NeedTransfer(ctx, dstObj, srcObj) 1975 if needTransfer { 1976 NoNeedTransfer, err := CompareOrCopyDest(ctx, fdst, dstObj, srcObj, copyDestDir, backupDir) 1977 if err != nil { 1978 return err 1979 } 1980 if NoNeedTransfer { 1981 needTransfer = false 1982 } 1983 } 1984 if needTransfer { 1985 // If destination already exists, then we must move it into --backup-dir if required 1986 if dstObj != nil && backupDir != nil { 1987 err = MoveBackupDir(ctx, backupDir, dstObj) 1988 if err != nil { 1989 logger(ctx, TransferError, dstObj, nil, err) 1990 return fmt.Errorf("moving to --backup-dir failed: %w", err) 1991 } 1992 // If successful zero out the dstObj as it is no longer there 1993 logger(ctx, MissingOnDst, dstObj, nil, nil) 1994 dstObj = nil 1995 } 1996 1997 _, err = Op(ctx, fdst, dstObj, dstFileName, srcObj) 1998 } else { 1999 if !cp { 2000 if ci.IgnoreExisting { 2001 fs.Debugf(srcObj, "Not removing source file as destination file exists and --ignore-existing is set") 2002 logger(ctx, Match, srcObj, dstObj, nil) 2003 } else if !SameObject(srcObj, dstObj) { 2004 err = DeleteFile(ctx, srcObj) 2005 logger(ctx, Differ, srcObj, dstObj, nil) 2006 } 2007 } 2008 } 2009 return err 2010 } 2011 2012 // MoveFile moves a single file possibly to a new name 2013 // 2014 // This is treated as a transfer. 2015 func MoveFile(ctx context.Context, fdst fs.Fs, fsrc fs.Fs, dstFileName string, srcFileName string) (err error) { 2016 return moveOrCopyFile(ctx, fdst, fsrc, dstFileName, srcFileName, false) 2017 } 2018 2019 // SetTier changes tier of object in remote 2020 func SetTier(ctx context.Context, fsrc fs.Fs, tier string) error { 2021 return ListFn(ctx, fsrc, func(o fs.Object) { 2022 objImpl, ok := o.(fs.SetTierer) 2023 if !ok { 2024 fs.Errorf(fsrc, "Remote object does not implement SetTier") 2025 return 2026 } 2027 err := objImpl.SetTier(tier) 2028 if err != nil { 2029 fs.Errorf(fsrc, "Failed to do SetTier, %v", err) 2030 } 2031 }) 2032 } 2033 2034 // SetTierFile changes tier of a single file in remote 2035 func SetTierFile(ctx context.Context, o fs.Object, tier string) error { 2036 do, ok := o.(fs.SetTierer) 2037 if !ok { 2038 return errors.New("remote object does not implement SetTier") 2039 } 2040 err := do.SetTier(tier) 2041 if err != nil { 2042 fs.Errorf(o, "Failed to do SetTier, %v", err) 2043 return err 2044 } 2045 return nil 2046 } 2047 2048 // TouchDir touches every file in directory with time t 2049 func TouchDir(ctx context.Context, f fs.Fs, remote string, t time.Time, recursive bool) error { 2050 return walk.ListR(ctx, f, remote, false, ConfigMaxDepth(ctx, recursive), walk.ListObjects, func(entries fs.DirEntries) error { 2051 entries.ForObject(func(o fs.Object) { 2052 if !SkipDestructive(ctx, o, "touch") { 2053 fs.Debugf(f, "Touching %q", o.Remote()) 2054 err := o.SetModTime(ctx, t) 2055 if err != nil { 2056 err = fmt.Errorf("failed to touch: %w", err) 2057 err = fs.CountError(err) 2058 fs.Errorf(o, "%v", err) 2059 } 2060 } 2061 }) 2062 return nil 2063 }) 2064 } 2065 2066 // ListFormat defines files information print format 2067 type ListFormat struct { 2068 separator string 2069 dirSlash bool 2070 absolute bool 2071 output []func(entry *ListJSONItem) string 2072 csv *csv.Writer 2073 buf bytes.Buffer 2074 } 2075 2076 // SetSeparator changes separator in struct 2077 func (l *ListFormat) SetSeparator(separator string) { 2078 l.separator = separator 2079 } 2080 2081 // SetDirSlash defines if slash should be printed 2082 func (l *ListFormat) SetDirSlash(dirSlash bool) { 2083 l.dirSlash = dirSlash 2084 } 2085 2086 // SetAbsolute prints a leading slash in front of path names 2087 func (l *ListFormat) SetAbsolute(absolute bool) { 2088 l.absolute = absolute 2089 } 2090 2091 // SetCSV defines if the output should be csv 2092 // 2093 // Note that you should call SetSeparator before this if you want a 2094 // custom separator 2095 func (l *ListFormat) SetCSV(useCSV bool) { 2096 if useCSV { 2097 l.csv = csv.NewWriter(&l.buf) 2098 if l.separator != "" { 2099 l.csv.Comma = []rune(l.separator)[0] 2100 } 2101 } else { 2102 l.csv = nil 2103 } 2104 } 2105 2106 // SetOutput sets functions used to create files information 2107 func (l *ListFormat) SetOutput(output []func(entry *ListJSONItem) string) { 2108 l.output = output 2109 } 2110 2111 // AddModTime adds file's Mod Time to output 2112 func (l *ListFormat) AddModTime(timeFormat string) { 2113 switch timeFormat { 2114 case "": 2115 timeFormat = "2006-01-02 15:04:05" 2116 case "Layout": 2117 timeFormat = time.Layout 2118 case "ANSIC": 2119 timeFormat = time.ANSIC 2120 case "UnixDate": 2121 timeFormat = time.UnixDate 2122 case "RubyDate": 2123 timeFormat = time.RubyDate 2124 case "RFC822": 2125 timeFormat = time.RFC822 2126 case "RFC822Z": 2127 timeFormat = time.RFC822Z 2128 case "RFC850": 2129 timeFormat = time.RFC850 2130 case "RFC1123": 2131 timeFormat = time.RFC1123 2132 case "RFC1123Z": 2133 timeFormat = time.RFC1123Z 2134 case "RFC3339": 2135 timeFormat = time.RFC3339 2136 case "RFC3339Nano": 2137 timeFormat = time.RFC3339Nano 2138 case "Kitchen": 2139 timeFormat = time.Kitchen 2140 case "Stamp": 2141 timeFormat = time.Stamp 2142 case "StampMilli": 2143 timeFormat = time.StampMilli 2144 case "StampMicro": 2145 timeFormat = time.StampMicro 2146 case "StampNano": 2147 timeFormat = time.StampNano 2148 case "DateTime": 2149 // timeFormat = time.DateTime // missing in go1.19 2150 timeFormat = "2006-01-02 15:04:05" 2151 case "DateOnly": 2152 // timeFormat = time.DateOnly // missing in go1.19 2153 timeFormat = "2006-01-02" 2154 case "TimeOnly": 2155 // timeFormat = time.TimeOnly // missing in go1.19 2156 timeFormat = "15:04:05" 2157 } 2158 l.AppendOutput(func(entry *ListJSONItem) string { 2159 return entry.ModTime.When.Local().Format(timeFormat) 2160 }) 2161 } 2162 2163 // AddSize adds file's size to output 2164 func (l *ListFormat) AddSize() { 2165 l.AppendOutput(func(entry *ListJSONItem) string { 2166 return strconv.FormatInt(entry.Size, 10) 2167 }) 2168 } 2169 2170 // normalisePath makes sure the path has the correct slashes for the current mode 2171 func (l *ListFormat) normalisePath(entry *ListJSONItem, remote string) string { 2172 if l.absolute && !strings.HasPrefix(remote, "/") { 2173 remote = "/" + remote 2174 } 2175 if entry.IsDir && l.dirSlash { 2176 remote += "/" 2177 } 2178 return remote 2179 } 2180 2181 // AddPath adds path to file to output 2182 func (l *ListFormat) AddPath() { 2183 l.AppendOutput(func(entry *ListJSONItem) string { 2184 return l.normalisePath(entry, entry.Path) 2185 }) 2186 } 2187 2188 // AddEncrypted adds the encrypted path to file to output 2189 func (l *ListFormat) AddEncrypted() { 2190 l.AppendOutput(func(entry *ListJSONItem) string { 2191 return l.normalisePath(entry, entry.Encrypted) 2192 }) 2193 } 2194 2195 // AddHash adds the hash of the type given to the output 2196 func (l *ListFormat) AddHash(ht hash.Type) { 2197 hashName := ht.String() 2198 l.AppendOutput(func(entry *ListJSONItem) string { 2199 if entry.IsDir { 2200 return "" 2201 } 2202 return entry.Hashes[hashName] 2203 }) 2204 } 2205 2206 // AddID adds file's ID to the output if known 2207 func (l *ListFormat) AddID() { 2208 l.AppendOutput(func(entry *ListJSONItem) string { 2209 return entry.ID 2210 }) 2211 } 2212 2213 // AddOrigID adds file's Original ID to the output if known 2214 func (l *ListFormat) AddOrigID() { 2215 l.AppendOutput(func(entry *ListJSONItem) string { 2216 return entry.OrigID 2217 }) 2218 } 2219 2220 // AddTier adds file's Tier to the output if known 2221 func (l *ListFormat) AddTier() { 2222 l.AppendOutput(func(entry *ListJSONItem) string { 2223 return entry.Tier 2224 }) 2225 } 2226 2227 // AddMimeType adds file's MimeType to the output if known 2228 func (l *ListFormat) AddMimeType() { 2229 l.AppendOutput(func(entry *ListJSONItem) string { 2230 return entry.MimeType 2231 }) 2232 } 2233 2234 // AddMetadata adds file's Metadata to the output if known 2235 func (l *ListFormat) AddMetadata() { 2236 l.AppendOutput(func(entry *ListJSONItem) string { 2237 metadata := entry.Metadata 2238 if metadata == nil { 2239 metadata = make(fs.Metadata) 2240 } 2241 out, err := json.Marshal(metadata) 2242 if err != nil { 2243 return fmt.Sprintf("Failed to read metadata: %v", err.Error()) 2244 } 2245 return string(out) 2246 }) 2247 } 2248 2249 // AppendOutput adds string generated by specific function to printed output 2250 func (l *ListFormat) AppendOutput(functionToAppend func(item *ListJSONItem) string) { 2251 l.output = append(l.output, functionToAppend) 2252 } 2253 2254 // Format prints information about the DirEntry in the format defined 2255 func (l *ListFormat) Format(entry *ListJSONItem) (result string) { 2256 var out []string 2257 for _, fun := range l.output { 2258 out = append(out, fun(entry)) 2259 } 2260 if l.csv != nil { 2261 l.buf.Reset() 2262 _ = l.csv.Write(out) // can't fail writing to bytes.Buffer 2263 l.csv.Flush() 2264 result = strings.TrimRight(l.buf.String(), "\n") 2265 } else { 2266 result = strings.Join(out, l.separator) 2267 } 2268 return result 2269 } 2270 2271 // FormatForLSFPrecision Returns a time format for the given precision 2272 func FormatForLSFPrecision(precision time.Duration) string { 2273 switch { 2274 case precision <= time.Nanosecond: 2275 return "2006-01-02 15:04:05.000000000" 2276 case precision <= 10*time.Nanosecond: 2277 return "2006-01-02 15:04:05.00000000" 2278 case precision <= 100*time.Nanosecond: 2279 return "2006-01-02 15:04:05.0000000" 2280 case precision <= time.Microsecond: 2281 return "2006-01-02 15:04:05.000000" 2282 case precision <= 10*time.Microsecond: 2283 return "2006-01-02 15:04:05.00000" 2284 case precision <= 100*time.Microsecond: 2285 return "2006-01-02 15:04:05.0000" 2286 case precision <= time.Millisecond: 2287 return "2006-01-02 15:04:05.000" 2288 case precision <= 10*time.Millisecond: 2289 return "2006-01-02 15:04:05.00" 2290 case precision <= 100*time.Millisecond: 2291 return "2006-01-02 15:04:05.0" 2292 } 2293 return "2006-01-02 15:04:05" 2294 } 2295 2296 // DirMove renames srcRemote to dstRemote 2297 // 2298 // It does this by loading the directory tree into memory (using ListR 2299 // if available) and doing renames in parallel. 2300 func DirMove(ctx context.Context, f fs.Fs, srcRemote, dstRemote string) (err error) { 2301 ci := fs.GetConfig(ctx) 2302 2303 if SkipDestructive(ctx, srcRemote, "dirMove") { 2304 accounting.Stats(ctx).Renames(1) 2305 return nil 2306 } 2307 2308 // Use DirMove if possible 2309 if doDirMove := f.Features().DirMove; doDirMove != nil { 2310 err = doDirMove(ctx, f, srcRemote, dstRemote) 2311 if err == nil { 2312 accounting.Stats(ctx).Renames(1) 2313 } 2314 if err != fs.ErrorCantDirMove && err != fs.ErrorDirExists { 2315 return err 2316 } 2317 fs.Infof(f, "Can't DirMove - falling back to file moves: %v", err) 2318 } 2319 2320 // Load the directory tree into memory 2321 tree, err := walk.NewDirTree(ctx, f, srcRemote, true, -1) 2322 if err != nil { 2323 return fmt.Errorf("RenameDir tree walk: %w", err) 2324 } 2325 2326 // Get the directories in sorted order 2327 dirs := tree.Dirs() 2328 2329 // Make the destination directories - must be done in order not in parallel 2330 for _, dir := range dirs { 2331 dstPath := dstRemote + dir[len(srcRemote):] 2332 err := f.Mkdir(ctx, dstPath) 2333 if err != nil { 2334 return fmt.Errorf("RenameDir mkdir: %w", err) 2335 } 2336 } 2337 2338 // Rename the files in parallel 2339 type rename struct { 2340 o fs.Object 2341 newPath string 2342 } 2343 renames := make(chan rename, ci.Checkers) 2344 g, gCtx := errgroup.WithContext(context.Background()) 2345 for i := 0; i < ci.Checkers; i++ { 2346 g.Go(func() error { 2347 for job := range renames { 2348 dstOverwritten, _ := f.NewObject(gCtx, job.newPath) 2349 _, err := Move(gCtx, f, dstOverwritten, job.newPath, job.o) 2350 if err != nil { 2351 return err 2352 } 2353 select { 2354 case <-gCtx.Done(): 2355 return gCtx.Err() 2356 default: 2357 } 2358 2359 } 2360 return nil 2361 }) 2362 } 2363 for dir, entries := range tree { 2364 dstPath := dstRemote + dir[len(srcRemote):] 2365 for _, entry := range entries { 2366 if o, ok := entry.(fs.Object); ok { 2367 renames <- rename{o, path.Join(dstPath, path.Base(o.Remote()))} 2368 } 2369 } 2370 } 2371 close(renames) 2372 err = g.Wait() 2373 if err != nil { 2374 return fmt.Errorf("RenameDir renames: %w", err) 2375 } 2376 2377 // Remove the source directories in reverse order 2378 for i := len(dirs) - 1; i >= 0; i-- { 2379 err := f.Rmdir(ctx, dirs[i]) 2380 if err != nil { 2381 return fmt.Errorf("RenameDir rmdir: %w", err) 2382 } 2383 } 2384 2385 return nil 2386 } 2387 2388 // DirMoveCaseInsensitive does DirMove in two steps (to temp name, then real name) 2389 // which is necessary for some case-insensitive backends 2390 func DirMoveCaseInsensitive(ctx context.Context, f fs.Fs, srcRemote, dstRemote string) (err error) { 2391 tmpDstRemote := dstRemote + "-rclone-move-" + random.String(8) 2392 err = DirMove(ctx, f, srcRemote, tmpDstRemote) 2393 if err != nil { 2394 return err 2395 } 2396 return DirMove(ctx, f, tmpDstRemote, dstRemote) 2397 } 2398 2399 // FsInfo provides information about a remote 2400 type FsInfo struct { 2401 // Name of the remote (as passed into NewFs) 2402 Name string 2403 2404 // Root of the remote (as passed into NewFs) 2405 Root string 2406 2407 // String returns a description of the FS 2408 String string 2409 2410 // Precision of the ModTimes in this Fs in Nanoseconds 2411 Precision time.Duration 2412 2413 // Returns the supported hash types of the filesystem 2414 Hashes []string 2415 2416 // Features returns the optional features of this Fs 2417 Features map[string]bool 2418 2419 // MetadataInfo returns info about the metadata for this backend 2420 MetadataInfo *fs.MetadataInfo 2421 } 2422 2423 // GetFsInfo gets the information (FsInfo) about a given Fs 2424 func GetFsInfo(f fs.Fs) *FsInfo { 2425 features := f.Features() 2426 info := &FsInfo{ 2427 Name: f.Name(), 2428 Root: f.Root(), 2429 String: f.String(), 2430 Precision: f.Precision(), 2431 Hashes: make([]string, 0, 4), 2432 Features: features.Enabled(), 2433 MetadataInfo: nil, 2434 } 2435 for _, hashType := range f.Hashes().Array() { 2436 info.Hashes = append(info.Hashes, hashType.String()) 2437 } 2438 fsInfo, _, _, _, err := fs.ParseRemote(fs.ConfigString(f)) 2439 if err == nil && fsInfo != nil && fsInfo.MetadataInfo != nil { 2440 info.MetadataInfo = fsInfo.MetadataInfo 2441 } 2442 return info 2443 } 2444 2445 var ( 2446 interactiveMu sync.Mutex // protects the following variables 2447 skipped = map[string]bool{} 2448 ) 2449 2450 // skipDestructiveChoose asks the user which action to take 2451 // 2452 // Call with interactiveMu held 2453 func skipDestructiveChoose(ctx context.Context, subject interface{}, action string) (skip bool) { 2454 // Lock the StdoutMutex - must not call fs.Log anything 2455 // otherwise it will deadlock with --interactive --progress 2456 StdoutMutex.Lock() 2457 2458 fmt.Printf("\nrclone: %s \"%v\"?\n", action, subject) 2459 i := config.CommandDefault([]string{ 2460 "yYes, this is OK", 2461 "nNo, skip this", 2462 fmt.Sprintf("sSkip all %s operations with no more questions", action), 2463 fmt.Sprintf("!Do all %s operations with no more questions", action), 2464 "qExit rclone now.", 2465 }, 0) 2466 2467 StdoutMutex.Unlock() 2468 2469 switch i { 2470 case 'y': 2471 skip = false 2472 case 'n': 2473 skip = true 2474 case 's': 2475 skip = true 2476 skipped[action] = true 2477 fs.Logf(nil, "Skipping all %s operations from now on without asking", action) 2478 case '!': 2479 skip = false 2480 skipped[action] = false 2481 fs.Logf(nil, "Doing all %s operations from now on without asking", action) 2482 case 'q': 2483 fs.Logf(nil, "Quitting rclone now") 2484 atexit.Run() 2485 os.Exit(0) 2486 default: 2487 skip = true 2488 fs.Errorf(nil, "Bad choice %c", i) 2489 } 2490 return skip 2491 } 2492 2493 // SkipDestructive should be called whenever rclone is about to do an destructive operation. 2494 // 2495 // It will check the --dry-run flag and it will ask the user if the --interactive flag is set. 2496 // 2497 // subject should be the object or directory in use 2498 // 2499 // action should be a descriptive word or short phrase 2500 // 2501 // Together they should make sense in this sentence: "Rclone is about 2502 // to action subject". 2503 func SkipDestructive(ctx context.Context, subject interface{}, action string) (skip bool) { 2504 var flag string 2505 ci := fs.GetConfig(ctx) 2506 switch { 2507 case ci.DryRun: 2508 flag = "--dry-run" 2509 skip = true 2510 case ci.Interactive: 2511 flag = "--interactive" 2512 interactiveMu.Lock() 2513 defer interactiveMu.Unlock() 2514 var found bool 2515 skip, found = skipped[action] 2516 if !found { 2517 skip = skipDestructiveChoose(ctx, subject, action) 2518 } 2519 default: 2520 return false 2521 } 2522 if skip { 2523 size := int64(-1) 2524 if do, ok := subject.(interface{ Size() int64 }); ok { 2525 size = do.Size() 2526 } 2527 if size >= 0 { 2528 fs.Logf(subject, "Skipped %s as %s is set (size %v)", fs.LogValue("skipped", action), flag, fs.LogValue("size", fs.SizeSuffix(size))) 2529 } else { 2530 fs.Logf(subject, "Skipped %s as %s is set", fs.LogValue("skipped", action), flag) 2531 } 2532 } 2533 return skip 2534 } 2535 2536 // Return the best way of describing the directory for the logs 2537 func dirName(f fs.Fs, dst fs.Directory, dir string) any { 2538 if dst != nil { 2539 if dst.Remote() != "" { 2540 return dst 2541 } 2542 // Root is described as the Fs 2543 return f 2544 } 2545 if dir != "" { 2546 return dir 2547 } 2548 // Root is described as the Fs 2549 return f 2550 } 2551 2552 // CopyDirMetadata copies the src directory to dst or f if nil. If dst is nil then it uses 2553 // dir as the name of the new directory. 2554 // 2555 // It returns the destination directory if possible. Note that this may 2556 // be nil. 2557 func CopyDirMetadata(ctx context.Context, f fs.Fs, dst fs.Directory, dir string, src fs.Directory) (newDst fs.Directory, err error) { 2558 ci := fs.GetConfig(ctx) 2559 logName := dirName(f, dst, dir) 2560 if SkipDestructive(ctx, logName, "update directory metadata") { 2561 return nil, nil 2562 } 2563 2564 // Options for the directory metadata 2565 options := []fs.OpenOption{} 2566 if ci.MetadataSet != nil { 2567 options = append(options, fs.MetadataOption(ci.MetadataSet)) 2568 } 2569 2570 // Read metadata from src and add options and use metadata mapper 2571 metadata, err := fs.GetMetadataOptions(ctx, f, src, options) 2572 if err != nil { 2573 return nil, err 2574 } 2575 2576 // Fall back to ModTime if metadata not available 2577 if metadata == nil { 2578 metadata = fs.Metadata{} 2579 } 2580 if metadata["mtime"] == "" { 2581 metadata["mtime"] = src.ModTime(ctx).Format(time.RFC3339Nano) 2582 } 2583 2584 // Now set the metadata 2585 if dst == nil { 2586 do := f.Features().MkdirMetadata 2587 if do == nil { 2588 return nil, fmt.Errorf("internal error: expecting %v to have MkdirMetadata method: %w", f, fs.ErrorNotImplemented) 2589 } 2590 newDst, err = do(ctx, dir, metadata) 2591 } else { 2592 do, ok := dst.(fs.SetMetadataer) 2593 if !ok { 2594 return nil, fmt.Errorf("internal error: expecting directory %s (%T) from %v to have SetMetadata method: %w", logName, dst, f, fs.ErrorNotImplemented) 2595 } 2596 err = do.SetMetadata(ctx, metadata) 2597 newDst = dst 2598 } 2599 if err != nil { 2600 return nil, err 2601 } 2602 fs.Infof(logName, "Updated directory metadata") 2603 return newDst, nil 2604 } 2605 2606 // SetDirModTime sets the modtime on dst or dir 2607 // 2608 // If dst is nil then it uses dir as the name of the directory. 2609 // 2610 // It returns the destination directory if possible. Note that this 2611 // may be nil. 2612 // 2613 // It does not create the directory. 2614 func SetDirModTime(ctx context.Context, f fs.Fs, dst fs.Directory, dir string, modTime time.Time) (newDst fs.Directory, err error) { 2615 logName := dirName(f, dst, dir) 2616 ci := fs.GetConfig(ctx) 2617 if ci.NoUpdateDirModTime { 2618 fs.Debugf(logName, "Skipping set directory modification time as --no-update-dir-modtime is set") 2619 return nil, nil 2620 } 2621 if SkipDestructive(ctx, logName, "set directory modification time") { 2622 return nil, nil 2623 } 2624 if dst != nil { 2625 dir = dst.Remote() 2626 } 2627 2628 // Try to set the ModTime with the Directory.SetModTime method first as this is the most efficient 2629 if dst != nil { 2630 if do, ok := dst.(fs.SetModTimer); ok { 2631 err := do.SetModTime(ctx, modTime) 2632 if errors.Is(err, fs.ErrorNotImplemented) { 2633 // Fall through and run the code below if not implemented 2634 // This can happen for fs.DirWrapper instances 2635 } else if err != nil { 2636 return dst, err 2637 } else { 2638 fs.Infof(logName, "Set directory modification time (using SetModTime)") 2639 return dst, nil 2640 } 2641 } 2642 } 2643 2644 // Next try to set the ModTime with the Fs.DirSetModTime method as this works for non-metadata backends 2645 if do := f.Features().DirSetModTime; do != nil { 2646 err := do(ctx, dir, modTime) 2647 if err != nil { 2648 return dst, err 2649 } 2650 fs.Infof(logName, "Set directory modification time (using DirSetModTime)") 2651 return dst, nil 2652 } 2653 2654 // Something should have worked so return an error 2655 return nil, fmt.Errorf("no method to set directory modtime found for %v (%T): %w", f, dst, fs.ErrorNotImplemented) 2656 }