Repository: rclone/rclone
File: fs/operations/operations.go (Go)

Code before:
				}
				return nil, accounting.ErrorMaxTransferLimitReachedGraceful
			}
		}
		if doCopy := f.Features().Copy; doCopy != nil && (SameConfig(src.Fs(), f) || (SameRemoteType(src.Fs(), f) && (f.Features().ServerSideAcrossConfigs || ci.ServerSideAcrossConfigs))) {
			in := tr.Account(ctx, nil) // account the transfer
			in.ServerSideCopyStart()
			newDst, err = doCopy(ctx, src, remote)
			if err == nil {
				dst = newDst
				in.ServerSideCopyEnd(dst.Size()) // account the bytes for the server-side transfer
				_ = in.Close()
			} else {
				_ = in.Close()
			}
			if err == fs.ErrorCantCopy {
				tr.Reset(ctx) // skip incomplete accounting - will be overwritten by the manual copy below
			}
		} else {
			err = fs.ErrorCantCopy
		}
		// If can't server-side copy, do it manually
		if err == fs.ErrorCantCopy {
			copyRename := false
			remoteDst := remote
			if CanServerSideMove(f) && f.Features().PartialUploads {
				copyRename = true
				remoteDst = remote + ".partial-" + random.String(8)
			}

			if doMultiThreadCopy(ctx, f, src) {
				// Number of streams proportional to size
				streams := src.Size() / int64(ci.MultiThreadCutoff)
				// With maximum
				if streams > int64(ci.MultiThreadStreams) {
					streams = int64(ci.MultiThreadStreams)
				}
				if streams < 2 {
					streams = 2
				}
				dst, err = multiThreadCopy(ctx, f, remoteDst, src, int(streams), tr)
				if doUpdate {
					actionTaken = "Multi-thread Copied (replaced existing)"
				} else {
					actionTaken = "Multi-thread Copied (new)"
				}
			} else {
				var in0 io.ReadCloser
				options := []fs.OpenOption{hashOption}
				for _, option := range ci.DownloadHeaders {
					options = append(options, option)

Review comment (suggestion):
I think testing `f.Features().Move != nil` is better than `CanServerSideMove(f)` so we don't complicate the Move too much. Don't really want to have to recurse into `Copy` - see below.

Code after:
		} else {
			remotePartial += suffix
		}
		inplace = false
	}

	var actionTaken string
	for {
		// Try server-side copy first - if has optional interface and
		// is same underlying remote
		actionTaken = "Copied (server-side copy)"
		if ci.MaxTransfer >= 0 {
			var bytesSoFar int64
			if ci.CutoffMode == fs.CutoffModeCautious {
				bytesSoFar = accounting.Stats(ctx).GetBytesWithPending() + src.Size()
			} else {
				bytesSoFar = accounting.Stats(ctx).GetBytes()
			}
			if bytesSoFar >= int64(ci.MaxTransfer) {
				if ci.CutoffMode == fs.CutoffModeHard {
					return nil, accounting.ErrorMaxTransferLimitReachedFatal
				}
				return nil, accounting.ErrorMaxTransferLimitReachedGraceful
			}
		}
		if doCopy := f.Features().Copy; doCopy != nil && (SameConfig(src.Fs(), f) || (SameRemoteType(src.Fs(), f) && (f.Features().ServerSideAcrossConfigs || ci.ServerSideAcrossConfigs))) {
			in := tr.Account(ctx, nil) // account the transfer
			in.ServerSideCopyStart()
			newDst, err = doCopy(ctx, src, remote)
			if err == nil {
				dst = newDst
				in.ServerSideCopyEnd(dst.Size()) // account the bytes for the server-side transfer
				_ = in.Close()
				inplace = true
			} else {
				_ = in.Close()
			}
			if err == fs.ErrorCantCopy {
				tr.Reset(ctx) // skip incomplete accounting - will be overwritten by the manual copy below
			}
		} else {
			err = fs.ErrorCantCopy
		}
		// If can't server-side copy, do it manually
		if err == fs.ErrorCantCopy {
			if doMultiThreadCopy(ctx, f, src) {
				// Number of streams proportional to size
				streams := src.Size() / int64(ci.MultiThreadCutoff)
				// With maximum
				if streams > int64(ci.MultiThreadStreams) {
					streams = int64(ci.MultiThreadStreams)