Skip to content

Commit

Permalink
Merge pull request #15871 from iterate-ch/feature/GH-15869
Browse files Browse the repository at this point in the history
Add command line option for moving files.
  • Loading branch information
dkocher committed May 14, 2024
2 parents fb98497 + d3d64f4 commit 02185e1
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 91 deletions.
38 changes: 29 additions & 9 deletions cli/src/main/java/ch/cyberduck/cli/Terminal.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
import ch.cyberduck.core.worker.DistributionPurgeWorker;
import ch.cyberduck.core.worker.HomeFinderWorker;
import ch.cyberduck.core.worker.LoadVaultWorker;
import ch.cyberduck.core.worker.MoveWorker;
import ch.cyberduck.core.worker.SessionListWorker;
import ch.cyberduck.core.worker.Worker;

Expand All @@ -100,6 +101,7 @@
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;

Expand Down Expand Up @@ -329,6 +331,7 @@ public void uncaughtException(final Thread t, final Throwable e) {
case edit:
case download:
case copy:
case move:
case synchronize:
case delete:
// We expect file on server to exist
Expand Down Expand Up @@ -362,18 +365,23 @@ public void uncaughtException(final Thread t, final Throwable e) {
switch(action) {
case download:
case upload:
case synchronize:
return this.transfer(login, new TerminalTransferFactory().create(input, host, remote,
new ArrayList<>(new SingleTransferItemFinder().find(input, action, remote))),
source, SessionPool.DISCONNECTED);
case copy:
case synchronize: {
final Transfer transfer = new TerminalTransferFactory().create(input, host, remote,
new ArrayList<>(new SingleTransferItemFinder().find(input, action, remote)));
return this.transfer(login, transfer, source, SessionPool.DISCONNECTED);
}
case copy: {
final Host target = new CommandLineUriParser(input).parse(input.getOptionValues(action.name())[1]);
destination = SessionPoolFactory.create(connect, transcript, target,
new CertificateStoreX509TrustManager(new DisabledCertificateTrustCallback(), new DefaultTrustManagerHostnameCallback(target), new TerminalCertificateStore(reader)),
new PreferencesX509KeyManager(target, new TerminalCertificateStore(reader)), registry);
return this.transfer(login, new CopyTransfer(
host, target, Collections.singletonMap(remote, new CommandLinePathParser(input, protocols).parse(input.getOptionValues(action.name())[1]))).withCache(cache),
source, destination);
final CopyTransfer transfer = new CopyTransfer(host, target,
Collections.singletonMap(remote, new CommandLinePathParser(input, protocols).parse(input.getOptionValues(action.name())[1])));
return this.transfer(login, transfer.withCache(cache), source, destination);
}
case move:
final Path target = new CommandLinePathParser(input, protocols).parse(input.getOptionValues(action.name())[1]);
return this.move(source, remote, target);
default:
throw new BackgroundException(LocaleFactory.localizedString("Unknown"),
String.format("Unknown transfer type %s", action.name()));
Expand Down Expand Up @@ -506,6 +514,18 @@ protected Exit delete(final SessionPool session, final Path remote) {
return Exit.success;
}

protected Exit move(final SessionPool session, final Path remote, final Path target) {
final MoveWorker worker = new MoveWorker(Collections.singletonMap(remote, target), session, cache, progress, new TerminalLoginCallback(reader));
final SessionBackgroundAction<Map<Path, Path>> action = new TerminalBackgroundAction<>(controller, session, worker);
try {
this.execute(action);
}
catch(TerminalBackgroundException e) {
return Exit.failure;
}
return Exit.success;
}

protected Exit purge(final SessionPool session, final Path remote) {
final DistributionPurgeWorker purge = new DistributionPurgeWorker(Collections.singletonList(remote),
new TerminalLoginCallback(reader), Distribution.DOWNLOAD, Distribution.WEBSITE_CDN, Distribution.CUSTOM);
Expand Down Expand Up @@ -606,7 +626,7 @@ protected <T> T execute(final SessionBackgroundAction<T> action) throws Terminal
}
}

private static final class TerminalBackgroundException extends BackgroundException {
public static final class TerminalBackgroundException extends BackgroundException {
public TerminalBackgroundException(final Throwable cause) {
super(cause);
}
Expand Down
1 change: 1 addition & 0 deletions cli/src/main/java/ch/cyberduck/cli/TerminalAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public enum TerminalAction {
download,
upload,
copy,
move,
synchronize,
delete,
mkdir,
Expand Down
168 changes: 86 additions & 82 deletions cli/src/main/java/ch/cyberduck/cli/TerminalOptionsBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,89 +39,93 @@ public static Options options() {

final OptionGroup actionGroup = new OptionGroup();
actionGroup.addOption(Option.builder()
.longOpt(TerminalAction.upload.name())
.desc("Upload file or folder recursively")
.hasArgs().numberOfArgs(2).argName("url> <file").build());
.longOpt(TerminalAction.upload.name())
.desc("Upload file or folder recursively")
.hasArgs().numberOfArgs(2).argName("url> <file").build());
actionGroup.addOption(Option.builder("d")
.longOpt(TerminalAction.download.name())
.desc("Download file or folder. Denote a folder with a trailing '/'")
.hasArgs().numberOfArgs(2).argName("url> <file").build());
.longOpt(TerminalAction.download.name())
.desc("Download file or folder. Denote a folder with a trailing '/'")
.hasArgs().numberOfArgs(2).argName("url> <file").build());
actionGroup.addOption(Option.builder()
.longOpt(TerminalAction.copy.name())
.desc("Copy from origin server to target server")
.hasArgs().numberOfArgs(2).argName("url> <url").build());
.longOpt(TerminalAction.copy.name())
.desc("Copy from origin server to target server")
.hasArgs().numberOfArgs(2).argName("url> <url").build());
actionGroup.addOption(Option.builder()
.longOpt(TerminalAction.move.name())
.desc("Move or rename file on server")
.hasArgs().numberOfArgs(2).argName("url> <file").build());
actionGroup.addOption(Option.builder("l")
.longOpt(TerminalAction.list.name())
.desc("List files in remote folder")
.hasArg().argName("url").build());
.longOpt(TerminalAction.list.name())
.desc("List files in remote folder")
.hasArg().argName("url").build());
actionGroup.addOption(Option.builder("L")
.longOpt(TerminalAction.longlist.name())
.desc("Long list format with modification date and permission mask")
.hasArg().argName("url").build());
.longOpt(TerminalAction.longlist.name())
.desc("Long list format with modification date and permission mask")
.hasArg().argName("url").build());
actionGroup.addOption(Option.builder("D")
.longOpt(TerminalAction.delete.name())
.desc("Delete")
.hasArg().argName("url").build());
.longOpt(TerminalAction.delete.name())
.desc("Delete")
.hasArg().argName("url").build());
actionGroup.addOption(Option.builder("c")
.longOpt(TerminalAction.mkdir.name())
.desc("Make directory")
.hasArg().argName("url").build());
.longOpt(TerminalAction.mkdir.name())
.desc("Make directory")
.hasArg().argName("url").build());
actionGroup.addOption(Option.builder()
.longOpt(TerminalAction.synchronize.name())
.desc("Synchronize folders")
.hasArgs().numberOfArgs(2).argName("url> <directory").build());
.longOpt(TerminalAction.synchronize.name())
.desc("Synchronize folders")
.hasArgs().numberOfArgs(2).argName("url> <directory").build());
actionGroup.addOption(Option.builder()
.longOpt(TerminalAction.edit.name())
.desc("Edit file in external editor")
.hasArg().argName("url").build());
.longOpt(TerminalAction.edit.name())
.desc("Edit file in external editor")
.hasArg().argName("url").build());
actionGroup.addOption(Option.builder()
.longOpt(TerminalAction.purge.name())
.desc("Invalidate file in CDN")
.hasArg().argName("url").build());
.longOpt(TerminalAction.purge.name())
.desc("Invalidate file in CDN")
.hasArg().argName("url").build());
actionGroup.addOption(Option.builder("V")
.longOpt(TerminalAction.version.name())
.desc("Show version number and quit.").build());
.longOpt(TerminalAction.version.name())
.desc("Show version number and quit.").build());
actionGroup.addOption(Option.builder("h")
.longOpt(TerminalAction.help.name())
.desc("Print this help").build());
.longOpt(TerminalAction.help.name())
.desc("Print this help").build());

actionGroup.setRequired(true);
options.addOptionGroup(actionGroup);

options.addOption(Option.builder("u")
.longOpt(Params.username.name())
.desc("Username")
.hasArg().argName("username or access key").build());
.longOpt(Params.username.name())
.desc("Username")
.hasArg().argName("username or access key").build());
options.addOption(Option.builder("p")
.longOpt(Params.password.name())
.desc("Password")
.hasArg().argName("password or secret key").build());
.longOpt(Params.password.name())
.desc("Password")
.hasArg().argName("password or secret key").build());
options.addOption(Option.builder()
.longOpt(Params.anonymous.name())
.desc("No login").build());
options.addOption(Option.builder()
.longOpt(Params.profile.name())
.desc("Use connection profile")
.hasArg().argName("profile").build());
.longOpt(Params.profile.name())
.desc("Use connection profile")
.hasArg().argName("profile").build());
options.addOption(Option.builder("i")
.longOpt(Params.identity.name())
.desc("Selects a file from which the identity (private key) for public key authentication is read.")
.hasArg().argName("private key file").build());
.longOpt(Params.identity.name())
.desc("Selects a file from which the identity (private key) for public key authentication is read.")
.hasArg().argName("private key file").build());

options.addOption(Option.builder()
.longOpt(Params.chmod.name())
.desc("Set explicit permission from octal mode value for uploaded file")
.hasArg().argName("mode").build());
.longOpt(Params.chmod.name())
.desc("Set explicit permission from octal mode value for uploaded file")
.hasArg().argName("mode").build());

options.addOption(Option.builder()
.longOpt(Params.application.name())
.desc("External editor application")
.hasArg().argName("path").build());
.longOpt(Params.application.name())
.desc("External editor application")
.hasArg().argName("path").build());

options.addOption(Option.builder()
.longOpt(Params.region.name())
.desc("Location of bucket or container")
.hasArg().argName("location").build());
.longOpt(Params.region.name())
.desc("Location of bucket or container")
.hasArg().argName("location").build());

options.addOption(Option.builder("P")
.longOpt(Params.preserve.name())
Expand All @@ -131,26 +135,26 @@ public static Options options() {
.desc("Retry failed connection attempts")
.optionalArg(true).argName("count").build());
options.addOption(Option.builder()
.longOpt(Params.udt.name())
.desc("Use UDT protocol if applicable").build());
.longOpt(Params.udt.name())
.desc("Use UDT protocol if applicable").build());
options.addOption(Option.builder()
.longOpt(Params.parallel.name())
.desc("Number of concurrent connections to use for transfers")
.hasArg().optionalArg(true).argName("connections").build());
.longOpt(Params.parallel.name())
.desc("Number of concurrent connections to use for transfers")
.hasArg().optionalArg(true).argName("connections").build());
options.addOption(Option.builder()
.longOpt(Params.throttle.name())
.desc("Throttle bandwidth")
.hasArg().argName("bytes per second").build());
.longOpt(Params.throttle.name())
.desc("Throttle bandwidth")
.hasArg().argName("bytes per second").build());
options.addOption(Option.builder()
.longOpt(Params.nochecksum.name())
.desc("Skip verifying checksum").build());
.longOpt(Params.nochecksum.name())
.desc("Skip verifying checksum").build());
options.addOption(Option.builder()
.longOpt(Params.nokeychain.name())
.desc("Do not save passwords in keychain").build());
.longOpt(Params.nokeychain.name())
.desc("Do not save passwords in keychain").build());
options.addOption(Option.builder()
.longOpt(Params.vault.name())
.desc("Unlock vault")
.hasArg().argName("path").build());
.longOpt(Params.vault.name())
.desc("Unlock vault")
.hasArg().argName("path").build());

final StringBuilder actions = new StringBuilder("Transfer actions for existing files").append(StringUtils.LF);
actions.append("Downloads and uploads:").append(StringUtils.LF);
Expand All @@ -169,28 +173,28 @@ public static Options options() {
}

options.addOption(Option.builder("e")
.longOpt(Params.existing.name())
.desc(actions.toString())
.hasArg().argName("action").build());
.longOpt(Params.existing.name())
.desc(actions.toString())
.hasArg().argName("action").build());
options.addOption(Option.builder("v")
.longOpt(Params.verbose.name())
.desc("Print transcript").build());
.longOpt(Params.verbose.name())
.desc("Print transcript").build());
options.addOption(Option.builder()
.longOpt(Params.debug.name())
.desc("Print debug output").build());
.longOpt(Params.debug.name())
.desc("Print debug output").build());
options.addOption(Option.builder("q")
.longOpt(Params.quiet.name())
.desc("Suppress progress messages").build());
.longOpt(Params.quiet.name())
.desc("Suppress progress messages").build());
options.addOption(Option.builder("y")
.longOpt(Params.assumeyes.name())
.desc("Assume yes for all prompts").build());
.longOpt(Params.assumeyes.name())
.desc("Assume yes for all prompts").build());

return options;
}

private static void append(final StringBuilder builder, final TransferAction action) {
builder.append(String.format(" %s %s (%s)\n",
StringUtils.leftPad(action.name(), 16), action.getTitle(), action.getDescription()));
StringUtils.leftPad(action.name(), 16), action.getTitle(), action.getDescription()));
}

public enum Params {
Expand Down

0 comments on commit 02185e1

Please sign in to comment.