--- /dev/null
+package org.argeo.cms.cli;
+
+import org.apache.commons.cli.Option;
+import org.argeo.cli.CommandsCli;
+import org.argeo.cli.fs.FsCommands;
+import org.argeo.cli.posix.PosixCommands;
+
+/** Argeo command line tools. */
+public class ArgeoCli extends CommandsCli {
+
+ public ArgeoCli(String commandName) {
+ super(commandName);
+ // Common options
+ options.addOption(Option.builder("v").hasArg().argName("verbose").desc("verbosity").build());
+ options.addOption(
+ Option.builder("D").hasArgs().argName("property=value").desc("use value for given property").build());
+
+ addCommandsCli(new PosixCommands("posix"));
+ addCommandsCli(new FsCommands("fs"));
+ }
+
+ @Override
+ public String getDescription() {
+ return "Argeo command line utilities";
+ }
+
+ public static void main(String[] args) {
+ mainImpl(new ArgeoCli("argeo"), args);
+ }
+
+}
--- /dev/null
+package org.argeo.cli;
+
+public class CommandArgsException extends IllegalArgumentException {
+ private static final long serialVersionUID = -7271050747105253935L;
+ private String commandName;
+ private volatile CommandsCli commandsCli;
+
+ public CommandArgsException(Exception cause) {
+ super(cause.getMessage(), cause);
+ }
+
+ public CommandArgsException(String message) {
+ super(message);
+ }
+
+ public String getCommandName() {
+ return commandName;
+ }
+
+ public void setCommandName(String commandName) {
+ this.commandName = commandName;
+ }
+
+ public CommandsCli getCommandsCli() {
+ return commandsCli;
+ }
+
+ public void setCommandsCli(CommandsCli commandsCli) {
+ this.commandsCli = commandsCli;
+ }
+
+}
--- /dev/null
+package org.argeo.cli;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Function;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/** Base class for a CLI managing sub commands. */
+public abstract class CommandsCli implements DescribedCommand<Object> {
+ public final static String HELP = "help";
+
+ private final String commandName;
+ private Map<String, Function<List<String>, ?>> commands = new TreeMap<>();
+
+ protected final Options options = new Options();
+
+ public CommandsCli(String commandName) {
+ this.commandName = commandName;
+ }
+
+ @Override
+ public Object apply(List<String> args) {
+ String cmd = null;
+ List<String> newArgs = new ArrayList<>();
+ try {
+ CommandLineParser clParser = new DefaultParser();
+ CommandLine commonCl = clParser.parse(getOptions(), args.toArray(new String[args.size()]), true);
+ List<String> leftOvers = commonCl.getArgList();
+ for (String arg : leftOvers) {
+ if (!arg.startsWith("-") && cmd == null) {
+ cmd = arg;
+ } else {
+ newArgs.add(arg);
+ }
+ }
+ } catch (ParseException e) {
+ CommandArgsException cae = new CommandArgsException(e);
+ throw cae;
+ }
+
+ Function<List<String>, ?> function = cmd != null ? getCommand(cmd) : getDefaultCommand();
+ if (function == null)
+ throw new IllegalArgumentException("Uknown command " + cmd);
+ try {
+ return function.apply(newArgs).toString();
+ } catch (CommandArgsException e) {
+ if (e.getCommandName() == null) {
+ e.setCommandName(cmd);
+ e.setCommandsCli(this);
+ }
+ throw e;
+ } catch (IllegalArgumentException e) {
+ CommandArgsException cae = new CommandArgsException(e);
+ cae.setCommandName(cmd);
+ throw cae;
+ }
+ }
+
+ @Override
+ public Options getOptions() {
+ return options;
+ }
+
+ protected void addCommand(String cmd, Function<List<String>, ?> function) {
+ commands.put(cmd, function);
+
+ }
+
+ @Override
+ public String getUsage() {
+ return "[command]";
+ }
+
+ protected void addCommandsCli(CommandsCli commandsCli) {
+ addCommand(commandsCli.getCommandName(), commandsCli);
+ commandsCli.addCommand(HELP, new HelpCommand(this, commandsCli));
+ }
+
+ public String getCommandName() {
+ return commandName;
+ }
+
+ public Set<String> getSubCommands() {
+ return commands.keySet();
+ }
+
+ public Function<List<String>, ?> getCommand(String command) {
+ return commands.get(command);
+ }
+
+ public HelpCommand getHelpCommand() {
+ return (HelpCommand) getCommand(HELP);
+ }
+
+ public Function<List<String>, String> getDefaultCommand() {
+ return getHelpCommand();
+ }
+
+ /** In order to implement quickly a main method. */
+ public static void mainImpl(CommandsCli cli, String[] args) {
+ try {
+ cli.addCommand(CommandsCli.HELP, new HelpCommand(null, cli));
+ Object output = cli.apply(Arrays.asList(args));
+ System.out.println(output);
+ System.exit(0);
+ } catch (CommandArgsException e) {
+ System.err.println("Wrong arguments " + Arrays.toString(args) + ": " + e.getMessage());
+ if (e.getCommandName() != null) {
+ StringWriter out = new StringWriter();
+ HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
+ System.err.println(out.toString());
+ } else {
+ e.printStackTrace();
+ }
+ System.exit(1);
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cli;
+
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/** A command that can be described. */
+public interface DescribedCommand<T> extends Function<List<String>, T> {
+ default Options getOptions() {
+ return new Options();
+ }
+
+ String getDescription();
+
+ default String getUsage() {
+ return null;
+ }
+
+ default String getExamples() {
+ return null;
+ }
+
+ default CommandLine toCommandLine(List<String> args) {
+ try {
+ DefaultParser parser = new DefaultParser();
+ return parser.parse(getOptions(), args.toArray(new String[args.size()]));
+ } catch (ParseException e) {
+ throw new CommandArgsException(e);
+ }
+ }
+
+ /** In order to implement quickly a main method. */
+ public static void mainImpl(DescribedCommand<?> command, String[] args) {
+ try {
+ Object output = command.apply(Arrays.asList(args));
+ System.out.println(output);
+ System.exit(0);
+ } catch (IllegalArgumentException e) {
+ StringWriter out = new StringWriter();
+ HelpCommand.printHelp(command, out);
+ System.err.println(out.toString());
+ System.exit(1);
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cli;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+
+/** A special command that can describe {@link DescribedCommand}. */
+public class HelpCommand implements DescribedCommand<String> {
+ private CommandsCli commandsCli;
+ private CommandsCli parentCommandsCli;
+
+ // Help formatting
+ private static int helpWidth = 80;
+ private static int helpLeftPad = 4;
+ private static int helpDescPad = 20;
+
+ public HelpCommand(CommandsCli parentCommandsCli, CommandsCli commandsCli) {
+ super();
+ this.parentCommandsCli = parentCommandsCli;
+ this.commandsCli = commandsCli;
+ }
+
+ @Override
+ public String apply(List<String> args) {
+ StringWriter out = new StringWriter();
+
+ if (args.size() == 0) {// overview
+ printHelp(commandsCli, out);
+ } else {
+ String cmd = args.get(0);
+ Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+ if (function == null)
+ return "Command " + cmd + " not found.";
+ Options options;
+ String examples;
+ DescribedCommand<?> command = null;
+ if (function instanceof DescribedCommand) {
+ command = (DescribedCommand<?>) function;
+ options = command.getOptions();
+ examples = command.getExamples();
+ } else {
+ options = new Options();
+ examples = null;
+ }
+ String description = getShortDescription(function);
+ String commandCall = getCommandUsage(cmd, command);
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp(new PrintWriter(out), helpWidth, commandCall, description, options, helpLeftPad,
+ helpDescPad, examples, false);
+ }
+ return out.toString();
+ }
+
+ private static String getShortDescription(Function<List<String>, ?> function) {
+ if (function instanceof DescribedCommand) {
+ return ((DescribedCommand<?>) function).getDescription();
+ } else {
+ return function.toString();
+ }
+ }
+
+ public String getCommandUsage(String cmd, DescribedCommand<?> command) {
+ String commandCall = getCommandCall(commandsCli) + " " + cmd;
+ assert command != null;
+ if (command != null && command.getUsage() != null) {
+ commandCall = commandCall + " " + command.getUsage();
+ }
+ return commandCall;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Shows this help or describes a command";
+ }
+
+ @Override
+ public String getUsage() {
+ return "[command]";
+ }
+
+ public CommandsCli getParentCommandsCli() {
+ return parentCommandsCli;
+ }
+
+ protected String getCommandCall(CommandsCli commandsCli) {
+ HelpCommand hc = commandsCli.getHelpCommand();
+ if (hc.getParentCommandsCli() != null) {
+ return getCommandCall(hc.getParentCommandsCli()) + " " + commandsCli.getCommandName();
+ } else {
+ return commandsCli.getCommandName();
+ }
+ }
+
+ public static void printHelp(DescribedCommand<?> command, StringWriter out) {
+ String usage = "java " + command.getClass().getName() + (command.getUsage() != null ? " " + command.getUsage() : "");
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), command.getOptions(), helpLeftPad,
+ helpDescPad, command.getExamples(), false);
+
+ }
+
+ public static void printHelp(CommandsCli commandsCli, String commandName, StringWriter out) {
+ DescribedCommand<?> command = (DescribedCommand<?>) commandsCli.getCommand(commandName);
+ String usage = commandsCli.getHelpCommand().getCommandUsage(commandName, command);
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), command.getOptions(), helpLeftPad,
+ helpDescPad, command.getExamples(), false);
+
+ }
+
+ public static void printHelp(CommandsCli commandsCli, StringWriter out) {
+ out.append(commandsCli.getDescription()).append('\n');
+ String leftPad = " ".repeat(helpLeftPad);
+ for (String cmd : commandsCli.getSubCommands()) {
+ Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+ assert function != null;
+ out.append(leftPad);
+ out.append(cmd);
+ // FIXME deal with long commands
+ out.append(" ".repeat(helpDescPad - cmd.length()));
+ out.append(getShortDescription(function));
+ out.append('\n');
+ }
+ }
+}
--- /dev/null
+package org.argeo.cli.fs;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.cli.CommandArgsException;
+import org.argeo.cli.DescribedCommand;
+import org.argeo.sync.SyncResult;
+
+public class FileSync implements DescribedCommand<SyncResult<Path>> {
+ final static Option deleteOption = Option.builder().longOpt("delete").desc("delete from target").build();
+ final static Option recursiveOption = Option.builder("r").longOpt("recursive").desc("recurse into directories")
+ .build();
+ final static Option progressOption = Option.builder().longOpt("progress").hasArg(false).desc("show progress")
+ .build();
+
+ @Override
+ public SyncResult<Path> apply(List<String> t) {
+ try {
+ CommandLine line = toCommandLine(t);
+ List<String> remaining = line.getArgList();
+ if (remaining.size() == 0) {
+ throw new CommandArgsException("There must be at least one argument");
+ }
+ URI sourceUri = new URI(remaining.get(0));
+ URI targetUri;
+ if (remaining.size() == 1) {
+ targetUri = Paths.get(System.getProperty("user.dir")).toUri();
+ } else {
+ targetUri = new URI(remaining.get(1));
+ }
+ boolean delete = line.hasOption(deleteOption.getLongOpt());
+ boolean recursive = line.hasOption(recursiveOption.getLongOpt());
+ PathSync pathSync = new PathSync(sourceUri, targetUri, delete, recursive);
+ return pathSync.call();
+ } catch (URISyntaxException e) {
+ throw new CommandArgsException(e);
+ }
+ }
+
+ @Override
+ public Options getOptions() {
+ Options options = new Options();
+ options.addOption(recursiveOption);
+ options.addOption(deleteOption);
+ options.addOption(progressOption);
+ return options;
+ }
+
+ @Override
+ public String getUsage() {
+ return "[source URI] [target URI]";
+ }
+
+ public static void main(String[] args) {
+ DescribedCommand.mainImpl(new FileSync(), args);
+// Options options = new Options();
+// options.addOption("r", "recursive", false, "recurse into directories");
+// options.addOption(Option.builder().longOpt("progress").hasArg(false).desc("show progress").build());
+//
+// CommandLineParser parser = new DefaultParser();
+// try {
+// CommandLine line = parser.parse(options, args);
+// List<String> remaining = line.getArgList();
+// if (remaining.size() == 0) {
+// System.err.println("There must be at least one argument");
+// printHelp(options);
+// System.exit(1);
+// }
+// URI sourceUri = new URI(remaining.get(0));
+// URI targetUri;
+// if (remaining.size() == 1) {
+// targetUri = Paths.get(System.getProperty("user.dir")).toUri();
+// } else {
+// targetUri = new URI(remaining.get(1));
+// }
+// PathSync pathSync = new PathSync(sourceUri, targetUri);
+// pathSync.run();
+// } catch (Exception exp) {
+// exp.printStackTrace();
+// printHelp(options);
+// System.exit(1);
+// }
+ }
+
+// public static void printHelp(Options options) {
+// HelpFormatter formatter = new HelpFormatter();
+// formatter.printHelp("sync SRC [DEST]", options, true);
+// }
+
+ @Override
+ public String getDescription() {
+ return "Synchronises files";
+ }
+
+}
--- /dev/null
+package org.argeo.cli.fs;
+
+import org.argeo.cli.CommandsCli;
+
+/** File utilities. */
+public class FsCommands extends CommandsCli {
+
+ public FsCommands(String commandName) {
+ super(commandName);
+ addCommand("sync", new FileSync());
+ }
+
+ @Override
+ public String getDescription() {
+ return "Utilities around files and file systems";
+ }
+
+}
--- /dev/null
+package org.argeo.cli.fs;
+
+import java.net.URI;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.concurrent.Callable;
+
+import org.argeo.jackrabbit.fs.DavexFsProvider;
+import org.argeo.ssh.Sftp;
+import org.argeo.sync.SyncResult;
+
+/** Synchronises two paths. */
+public class PathSync implements Callable<SyncResult<Path>> {
+ private final URI sourceUri, targetUri;
+ private final boolean delete;
+ private final boolean recursive;
+
+ public PathSync(URI sourceUri, URI targetUri) {
+ this(sourceUri, targetUri, false, false);
+ }
+
+ public PathSync(URI sourceUri, URI targetUri, boolean delete, boolean recursive) {
+ this.sourceUri = sourceUri;
+ this.targetUri = targetUri;
+ this.delete = delete;
+ this.recursive = recursive;
+ }
+
+ @Override
+ public SyncResult<Path> call() {
+ try {
+ Path sourceBasePath = createPath(sourceUri);
+ Path targetBasePath = createPath(targetUri);
+ SyncFileVisitor syncFileVisitor = new SyncFileVisitor(sourceBasePath, targetBasePath, delete, recursive);
+ Files.walkFileTree(sourceBasePath, syncFileVisitor);
+ return syncFileVisitor.getSyncResult();
+ } catch (Exception e) {
+ throw new IllegalStateException("Cannot sync " + sourceUri + " to " + targetUri, e);
+ }
+ }
+
+ private static Path createPath(URI uri) {
+ Path path;
+ if (uri.getScheme() == null) {
+ path = Paths.get(uri.getPath());
+ } else if (uri.getScheme().equals("file")) {
+ FileSystemProvider fsProvider = FileSystems.getDefault().provider();
+ path = fsProvider.getPath(uri);
+ } else if (uri.getScheme().equals("davex")) {
+ FileSystemProvider fsProvider = new DavexFsProvider();
+ path = fsProvider.getPath(uri);
+ } else if (uri.getScheme().equals("sftp")) {
+ Sftp sftp = new Sftp(uri);
+ path = sftp.getBasePath();
+ } else
+ throw new IllegalArgumentException("URI scheme not supported for " + uri);
+ return path;
+ }
+}
--- /dev/null
+package org.argeo.cli.fs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Scanner;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.SshAgentFactory;
+import org.apache.sshd.agent.local.LocalAgentFactory;
+import org.apache.sshd.agent.unix.UnixAgentFactory;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystem;
+import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider;
+
+public class SshSync {
+ private final static Log log = LogFactory.getLog(SshSync.class);
+
+ public static void main(String[] args) {
+
+ try (SshClient client = SshClient.setUpDefaultClient()) {
+ client.start();
+ boolean osAgent = true;
+ SshAgentFactory agentFactory = osAgent ? new UnixAgentFactory() : new LocalAgentFactory();
+ // SshAgentFactory agentFactory = new LocalAgentFactory();
+ client.setAgentFactory(agentFactory);
+ SshAgent sshAgent = agentFactory.createClient(client);
+
+ String login = System.getProperty("user.name");
+ String host = "localhost";
+ int port = 22;
+
+ if (!osAgent) {
+ String keyPath = "/home/" + login + "/.ssh/id_rsa";
+ System.out.print(keyPath + ": ");
+ Scanner s = new Scanner(System.in);
+ String password = s.next();
+// KeyPair keyPair = ClientIdentityLoader.DEFAULT.loadClientIdentity(keyPath,
+// FilePasswordProvider.of(password));
+// sshAgent.addIdentity(keyPair, "NO COMMENT");
+ }
+
+// List<? extends Map.Entry<PublicKey, String>> identities = sshAgent.getIdentities();
+// for (Map.Entry<PublicKey, String> entry : identities) {
+// System.out.println(entry.getValue() + " : " + entry.getKey());
+// }
+
+ ConnectFuture connectFuture = client.connect(login, host, port);
+ connectFuture.await();
+ ClientSession session = connectFuture.getSession();
+
+ try {
+
+// session.addPasswordIdentity(new String(password));
+ session.auth().verify(1000l);
+
+ SftpFileSystemProvider fsProvider = new SftpFileSystemProvider(client);
+
+ SftpFileSystem fs = fsProvider.newFileSystem(session);
+ Path testPath = fs.getPath("/home/" + login + "/tmp");
+ Files.list(testPath).forEach(System.out::println);
+ test(testPath);
+
+ } finally {
+ client.stop();
+ }
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ static void test(Path testBase) {
+ try {
+ Path testPath = testBase.resolve("ssh-test.txt");
+ Files.createFile(testPath);
+ log.debug("Created file " + testPath);
+ Files.delete(testPath);
+ log.debug("Deleted " + testPath);
+ String txt = "TEST\nTEST2\n";
+ byte[] arr = txt.getBytes();
+ Files.write(testPath, arr);
+ log.debug("Wrote " + testPath);
+ byte[] read = Files.readAllBytes(testPath);
+ log.debug("Read " + testPath);
+ Path testDir = testBase.resolve("testDir");
+ log.debug("Resolved " + testDir);
+ // Copy
+ Files.createDirectory(testDir);
+ log.debug("Created directory " + testDir);
+ Path subsubdir = Files.createDirectories(testDir.resolve("subdir/subsubdir"));
+ log.debug("Created sub directories " + subsubdir);
+ Path copiedFile = testDir.resolve("copiedFile.txt");
+ log.debug("Resolved " + copiedFile);
+ Path relativeCopiedFile = testDir.relativize(copiedFile);
+ log.debug("Relative copied file " + relativeCopiedFile);
+ try (OutputStream out = Files.newOutputStream(copiedFile);
+ InputStream in = Files.newInputStream(testPath)) {
+ IOUtils.copy(in, out);
+ }
+ log.debug("Copied " + testPath + " to " + copiedFile);
+ Files.delete(testPath);
+ log.debug("Deleted " + testPath);
+ byte[] copiedRead = Files.readAllBytes(copiedFile);
+ log.debug("Read " + copiedFile);
+ // Browse directories
+ DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
+ int fileCount = 0;
+ Path listedFile = null;
+ for (Path file : files) {
+ fileCount++;
+ if (!Files.isDirectory(file))
+ listedFile = file;
+ }
+ log.debug("Listed " + testDir);
+ // Generic attributes
+ Map<String, Object> attrs = Files.readAttributes(copiedFile, "*");
+ log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+}
--- /dev/null
+package org.argeo.cli.fs;
+
+import java.nio.file.Path;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.fs.BasicSyncFileVisitor;
+
+/** Synchronises two directory structures. */
+public class SyncFileVisitor extends BasicSyncFileVisitor {
+ private final static Log log = LogFactory.getLog(SyncFileVisitor.class);
+
+ public SyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
+ super(sourceBasePath, targetBasePath, delete, recursive);
+ }
+
+ @Override
+ protected void error(Object obj, Throwable e) {
+ log.error(obj, e);
+ }
+
+ @Override
+ protected boolean isTraceEnabled() {
+ return log.isTraceEnabled();
+ }
+
+ @Override
+ protected void trace(Object obj) {
+ log.trace(obj);
+ }
+}
--- /dev/null
+package org.argeo.cli.posix;
+
+import java.util.List;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.argeo.cli.DescribedCommand;
+
+public class Echo implements DescribedCommand<String> {
+
+ @Override
+ public Options getOptions() {
+ Options options = new Options();
+ options.addOption(Option.builder("n").desc("do not output the trailing newline").build());
+ return options;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Display a line of text";
+ }
+
+ @Override
+ public String getUsage() {
+ return "[STRING]...";
+ }
+
+ @Override
+ public String apply(List<String> args) {
+ CommandLine cl = toCommandLine(args);
+
+ StringBuffer sb = new StringBuffer();
+ for (String s : cl.getArgList()) {
+ sb.append(s).append(' ');
+ }
+
+ if (cl.hasOption('n')) {
+ sb.deleteCharAt(sb.length() - 1);
+ } else {
+ sb.setCharAt(sb.length() - 1, '\n');
+ }
+ return sb.toString();
+ }
+
+}
--- /dev/null
+package org.argeo.cli.posix;
+
+import org.argeo.cli.CommandsCli;
+
+/** POSIX commands. */
+public class PosixCommands extends CommandsCli {
+
+ public PosixCommands(String commandName) {
+ super(commandName);
+ addCommand("echo", new Echo());
+ }
+
+ @Override
+ public String getDescription() {
+ return "Reimplementation of some POSIX commands in plain Java";
+ }
+
+ public static void main(String[] args) {
+ mainImpl(new PosixCommands("argeo-posix"), args);
+ }
+}
+++ /dev/null
-package org.argeo.sync;
-
-/** Commons exception for sync */
-public class SyncException extends RuntimeException {
- private static final long serialVersionUID = -3371314343580218538L;
-
- public SyncException(String message) {
- super(message);
- }
-
- public SyncException(String message, Throwable cause) {
- super(message, cause);
- }
-
- public SyncException(Object source, Object target, Throwable cause) {
- super("Cannot sync from " + source + " to " + target, cause);
- }
-}
+++ /dev/null
-package org.argeo.sync.cli;
-
-import java.net.URI;
-import java.nio.file.Paths;
-import java.util.List;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-import org.argeo.sync.fs.PathSync;
-
-public class Sync {
-
- public static void main(String[] args) {
- Options options = new Options();
- options.addOption("r", "recursive", false, "recurse into directories");
- options.addOption(Option.builder().longOpt("progress").hasArg(false).desc("show progress").build());
-
- CommandLineParser parser = new DefaultParser();
- try {
- CommandLine line = parser.parse(options, args);
- List<String> remaining = line.getArgList();
- if (remaining.size() == 0) {
- System.err.println("There must be at least one argument");
- printHelp(options);
- System.exit(1);
- }
- URI sourceUri = new URI(remaining.get(0));
- URI targetUri;
- if (remaining.size() == 1) {
- targetUri = Paths.get(System.getProperty("user.dir")).toUri();
- } else {
- targetUri = new URI(remaining.get(1));
- }
- PathSync pathSync = new PathSync(sourceUri, targetUri);
- pathSync.run();
- } catch (Exception exp) {
- exp.printStackTrace();
- printHelp(options);
- System.exit(1);
- }
- }
-
- public static void printHelp(Options options) {
- HelpFormatter formatter = new HelpFormatter();
- formatter.printHelp("sync SRC [DEST]", options, true);
- }
-
-}
+++ /dev/null
-package org.argeo.sync.fs;
-
-import java.net.URI;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.spi.FileSystemProvider;
-
-import org.argeo.jackrabbit.fs.DavexFsProvider;
-import org.argeo.ssh.Sftp;
-import org.argeo.sync.SyncException;
-
-/** Synchronises two paths. */
-public class PathSync implements Runnable {
- private final URI sourceUri, targetUri;
- private boolean delete = false;
-
- public PathSync(URI sourceUri, URI targetUri) {
- this.sourceUri = sourceUri;
- this.targetUri = targetUri;
- }
-
- @Override
- public void run() {
- try {
- Path sourceBasePath = createPath(sourceUri);
- Path targetBasePath = createPath(targetUri);
- SyncFileVisitor syncFileVisitor = new SyncFileVisitor(sourceBasePath, targetBasePath, delete);
- Files.walkFileTree(sourceBasePath, syncFileVisitor);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- private static Path createPath(URI uri) {
- Path path;
- if (uri.getScheme() == null) {
- path = Paths.get(uri.getPath());
- } else if (uri.getScheme().equals("file")) {
- FileSystemProvider fsProvider = FileSystems.getDefault().provider();
- path = fsProvider.getPath(uri);
- } else if (uri.getScheme().equals("davex")) {
- FileSystemProvider fsProvider = new DavexFsProvider();
- path = fsProvider.getPath(uri);
- } else if (uri.getScheme().equals("sftp")) {
- Sftp sftp = new Sftp(uri);
- path = sftp.getBasePath();
- } else
- throw new SyncException("URI scheme not supported for " + uri);
- return path;
- }
-}
+++ /dev/null
-package org.argeo.sync.fs;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.DirectoryStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Map;
-import java.util.Scanner;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.sshd.agent.SshAgent;
-import org.apache.sshd.agent.SshAgentFactory;
-import org.apache.sshd.agent.local.LocalAgentFactory;
-import org.apache.sshd.agent.unix.UnixAgentFactory;
-import org.apache.sshd.client.SshClient;
-import org.apache.sshd.client.future.ConnectFuture;
-import org.apache.sshd.client.session.ClientSession;
-import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystem;
-import org.apache.sshd.client.subsystem.sftp.fs.SftpFileSystemProvider;
-
-public class SshSync {
- private final static Log log = LogFactory.getLog(SshSync.class);
-
- public static void main(String[] args) {
-
- try (SshClient client = SshClient.setUpDefaultClient()) {
- client.start();
- boolean osAgent = true;
- SshAgentFactory agentFactory = osAgent ? new UnixAgentFactory() : new LocalAgentFactory();
- // SshAgentFactory agentFactory = new LocalAgentFactory();
- client.setAgentFactory(agentFactory);
- SshAgent sshAgent = agentFactory.createClient(client);
-
- String login = System.getProperty("user.name");
- String host = "localhost";
- int port = 22;
-
- if (!osAgent) {
- String keyPath = "/home/" + login + "/.ssh/id_rsa";
- System.out.print(keyPath + ": ");
- Scanner s = new Scanner(System.in);
- String password = s.next();
-// KeyPair keyPair = ClientIdentityLoader.DEFAULT.loadClientIdentity(keyPath,
-// FilePasswordProvider.of(password));
-// sshAgent.addIdentity(keyPair, "NO COMMENT");
- }
-
-// List<? extends Map.Entry<PublicKey, String>> identities = sshAgent.getIdentities();
-// for (Map.Entry<PublicKey, String> entry : identities) {
-// System.out.println(entry.getValue() + " : " + entry.getKey());
-// }
-
- ConnectFuture connectFuture = client.connect(login, host, port);
- connectFuture.await();
- ClientSession session = connectFuture.getSession();
-
- try {
-
-// session.addPasswordIdentity(new String(password));
- session.auth().verify(1000l);
-
- SftpFileSystemProvider fsProvider = new SftpFileSystemProvider(client);
-
- SftpFileSystem fs = fsProvider.newFileSystem(session);
- Path testPath = fs.getPath("/home/" + login + "/tmp");
- Files.list(testPath).forEach(System.out::println);
- test(testPath);
-
- } finally {
- client.stop();
- }
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
-
- static void test(Path testBase) {
- try {
- Path testPath = testBase.resolve("ssh-test.txt");
- Files.createFile(testPath);
- log.debug("Created file " + testPath);
- Files.delete(testPath);
- log.debug("Deleted " + testPath);
- String txt = "TEST\nTEST2\n";
- byte[] arr = txt.getBytes();
- Files.write(testPath, arr);
- log.debug("Wrote " + testPath);
- byte[] read = Files.readAllBytes(testPath);
- log.debug("Read " + testPath);
- Path testDir = testBase.resolve("testDir");
- log.debug("Resolved " + testDir);
- // Copy
- Files.createDirectory(testDir);
- log.debug("Created directory " + testDir);
- Path subsubdir = Files.createDirectories(testDir.resolve("subdir/subsubdir"));
- log.debug("Created sub directories " + subsubdir);
- Path copiedFile = testDir.resolve("copiedFile.txt");
- log.debug("Resolved " + copiedFile);
- Path relativeCopiedFile = testDir.relativize(copiedFile);
- log.debug("Relative copied file " + relativeCopiedFile);
- try (OutputStream out = Files.newOutputStream(copiedFile);
- InputStream in = Files.newInputStream(testPath)) {
- IOUtils.copy(in, out);
- }
- log.debug("Copied " + testPath + " to " + copiedFile);
- Files.delete(testPath);
- log.debug("Deleted " + testPath);
- byte[] copiedRead = Files.readAllBytes(copiedFile);
- log.debug("Read " + copiedFile);
- // Browse directories
- DirectoryStream<Path> files = Files.newDirectoryStream(testDir);
- int fileCount = 0;
- Path listedFile = null;
- for (Path file : files) {
- fileCount++;
- if (!Files.isDirectory(file))
- listedFile = file;
- }
- log.debug("Listed " + testDir);
- // Generic attributes
- Map<String, Object> attrs = Files.readAttributes(copiedFile, "*");
- log.debug("Read attributes of " + copiedFile + ": " + attrs.keySet());
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
-
- }
-
-}
+++ /dev/null
-package org.argeo.sync.fs;
-
-import java.nio.file.Path;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.fs.BasicSyncFileVisitor;
-
-/** Synchronises two directory structures. */
-public class SyncFileVisitor extends BasicSyncFileVisitor {
- private final static Log log = LogFactory.getLog(SyncFileVisitor.class);
-
- public SyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete) {
- super(sourceBasePath, targetBasePath, delete);
- }
-
- @Override
- protected void error(Object obj, Throwable e) {
- log.error(obj, e);
- }
-
- @Override
- protected boolean isDebugEnabled() {
- return log.isDebugEnabled();
- }
-
- @Override
- protected void debug(Object obj) {
- log.debug(obj);
- }
-}
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
+import org.argeo.sync.SyncResult;
+
/** Synchronises two directory structures. */
public class BasicSyncFileVisitor extends SimpleFileVisitor<Path> {
// TODO make it configurable
- private boolean debug = false;
+ private boolean trace = false;
private final Path sourceBasePath;
private final Path targetBasePath;
private final boolean delete;
+ private final boolean recursive;
+
+ private SyncResult<Path> syncResult = new SyncResult<>();
- public BasicSyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete) {
+ public BasicSyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
this.sourceBasePath = sourceBasePath;
this.targetBasePath = targetBasePath;
this.delete = delete;
+ this.recursive = recursive;
}
@Override
public FileVisitResult preVisitDirectory(Path sourceDir, BasicFileAttributes attrs) throws IOException {
+ if (!recursive && !sourceDir.equals(sourceBasePath))
+ return FileVisitResult.SKIP_SUBTREE;
Path targetDir = toTargetPath(sourceDir);
Files.createDirectories(targetDir);
return FileVisitResult.CONTINUE;
try {
if (!Files.exists(targetFile)) {
Files.copy(sourceFile, targetFile);
- copied(sourceFile, targetFile);
+ added(sourceFile, targetFile);
} else {
if (shouldOverwrite(sourceFile, targetFile)) {
Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
return targetBasePath;
}
- protected void copied(Path sourcePath, Path targetPath) {
- if (isDebugEnabled())
- debug("Copied " + sourcePath + " to " + targetPath);
+ protected void added(Path sourcePath, Path targetPath) {
+ syncResult.getAdded().add(targetPath);
+ if (isTraceEnabled())
+ trace("Added " + sourcePath + " as " + targetPath);
+ }
+
+ protected void modified(Path sourcePath, Path targetPath) {
+ syncResult.getModified().add(targetPath);
+ if (isTraceEnabled())
+ trace("Overwritten from " + sourcePath + " to " + targetPath);
}
protected void copyFailed(Path sourcePath, Path targetPath, Exception e) {
- error("Cannot copy " + sourcePath + " to " + targetPath, e);
+ syncResult.addError(sourcePath, targetPath, e);
+ if (isTraceEnabled())
+ error("Cannot copy " + sourcePath + " to " + targetPath, e);
}
protected void deleted(Path targetPath) {
- if (isDebugEnabled())
- debug("Deleted " + targetPath);
+ syncResult.getDeleted().add(targetPath);
+ if (isTraceEnabled())
+ trace("Deleted " + targetPath);
}
protected void deleteFailed(Path targetPath, Exception e) {
- error("Cannot delete " + targetPath, e);
+ syncResult.addError(null, targetPath, e);
+ if (isTraceEnabled())
+ error("Cannot delete " + targetPath, e);
}
/** Log error. */
e.printStackTrace();
}
- protected boolean isDebugEnabled() {
- return debug;
+ protected boolean isTraceEnabled() {
+ return trace;
}
- protected void debug(Object obj) {
+ protected void trace(Object obj) {
System.out.println(obj);
}
+
+ public SyncResult<Path> getSyncResult() {
+ return syncResult;
+ }
+
}
/** Sync a source path with a target path. */
public static void sync(Path sourceBasePath, Path targetBasePath, boolean delete) {
- sync(new BasicSyncFileVisitor(sourceBasePath, targetBasePath, delete));
+ sync(new BasicSyncFileVisitor(sourceBasePath, targetBasePath, delete, true));
}
public static void sync(BasicSyncFileVisitor syncFileVisitor) {
--- /dev/null
+package org.argeo.sync;
+
+/** Commons exception for sync */
+public class SyncException extends RuntimeException {
+ private static final long serialVersionUID = -3371314343580218538L;
+
+ public SyncException(String message) {
+ super(message);
+ }
+
+ public SyncException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public SyncException(Object source, Object target, Throwable cause) {
+ super("Cannot sync from " + source + " to " + target, cause);
+ }
+}
--- /dev/null
+package org.argeo.sync;
+
+import java.time.Instant;
+import java.util.Set;
+import java.util.TreeSet;
+
+/** Describes what happendend during a sync operation. */
+public class SyncResult<T> {
+ private final Set<T> added = new TreeSet<>();
+ private final Set<T> modified = new TreeSet<>();
+ private final Set<T> deleted = new TreeSet<>();
+ private final Set<Error> errors = new TreeSet<>();
+
+ public Set<T> getAdded() {
+ return added;
+ }
+
+ public Set<T> getModified() {
+ return modified;
+ }
+
+ public Set<T> getDeleted() {
+ return deleted;
+ }
+
+ public Set<Error> getErrors() {
+ return errors;
+ }
+
+ public void addError(T sourcePath, T targetPath, Exception e) {
+ Error error = new Error(sourcePath, targetPath, e);
+ errors.add(error);
+ }
+
+ public boolean noModification() {
+ return modified.isEmpty() && deleted.isEmpty() && added.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ if (noModification())
+ return "No modification.";
+ StringBuffer sb = new StringBuffer();
+ for (T p : modified)
+ sb.append("MOD ").append(p).append('\n');
+ for (T p : deleted)
+ sb.append("DEL ").append(p).append('\n');
+ for (T p : added)
+ sb.append("ADD ").append(p).append('\n');
+ for (Error error : errors)
+ sb.append(error).append('\n');
+ return sb.toString();
+ }
+
+ public class Error implements Comparable<Error> {
+ private final T sourcePath;// if null this is a failed delete
+ private final T targetPath;
+ private final Exception exception;
+ private final Instant timestamp = Instant.now();
+
+ public Error(T sourcePath, T targetPath, Exception e) {
+ super();
+ this.sourcePath = sourcePath;
+ this.targetPath = targetPath;
+ this.exception = e;
+ }
+
+ public T getSourcePath() {
+ return sourcePath;
+ }
+
+ public T getTargetPath() {
+ return targetPath;
+ }
+
+ public Exception getException() {
+ return exception;
+ }
+
+ public Instant getTimestamp() {
+ return timestamp;
+ }
+
+ @Override
+ public int compareTo(Error o) {
+ return timestamp.compareTo(o.timestamp);
+ }
+
+ @Override
+ public int hashCode() {
+ return timestamp.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "ERR " + timestamp + (sourcePath == null ? "Deletion failed" : "Copy failed " + sourcePath) + " "
+ + targetPath + " " + exception.getMessage();
+ }
+
+ }
+}