--- /dev/null
+package org.argeo.cms.acr.fs;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+
+/** Synchronises two directory structures. */
+public class BasicSyncFileVisitor extends SimpleFileVisitor<Path> {
+ // TODO make it configurable
+ 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, 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;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path sourceDir, IOException exc) throws IOException {
+ if (delete) {
+ Path targetDir = toTargetPath(sourceDir);
+ for (Path targetPath : Files.newDirectoryStream(targetDir)) {
+ Path sourcePath = sourceDir.resolve(targetPath.getFileName());
+ if (!Files.exists(sourcePath)) {
+ try {
+ FsSyncUtils.delete(targetPath);
+ deleted(targetPath);
+ } catch (Exception e) {
+ deleteFailed(targetPath, exc);
+ }
+ }
+ }
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path sourceFile, BasicFileAttributes attrs) throws IOException {
+ Path targetFile = toTargetPath(sourceFile);
+ try {
+ if (!Files.exists(targetFile)) {
+ Files.copy(sourceFile, targetFile);
+ added(sourceFile, targetFile);
+ } else {
+ if (shouldOverwrite(sourceFile, targetFile)) {
+ Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ } catch (Exception e) {
+ copyFailed(sourceFile, targetFile, e);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ protected boolean shouldOverwrite(Path sourceFile, Path targetFile) throws IOException {
+ long sourceSize = Files.size(sourceFile);
+ long targetSize = Files.size(targetFile);
+ if (sourceSize != targetSize) {
+ return true;
+ }
+ FileTime sourceLastModif = Files.getLastModifiedTime(sourceFile);
+ FileTime targetLastModif = Files.getLastModifiedTime(targetFile);
+ if (sourceLastModif.compareTo(targetLastModif) > 0)
+ return true;
+ return shouldOverwriteLaterSameSize(sourceFile, targetFile);
+ }
+
+ protected boolean shouldOverwriteLaterSameSize(Path sourceFile, Path targetFile) {
+ return false;
+ }
+
+// @Override
+// public FileVisitResult visitFileFailed(Path sourceFile, IOException exc) throws IOException {
+// error("Cannot sync " + sourceFile, exc);
+// return FileVisitResult.CONTINUE;
+// }
+
+ private Path toTargetPath(Path sourcePath) {
+ Path relativePath = sourceBasePath.relativize(sourcePath);
+ Path targetPath = targetBasePath.resolve(relativePath.toString());
+ return targetPath;
+ }
+
+ public Path getSourceBasePath() {
+ return sourceBasePath;
+ }
+
+ public Path getTargetBasePath() {
+ return targetBasePath;
+ }
+
+ 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) {
+ syncResult.addError(sourcePath, targetPath, e);
+ if (isTraceEnabled())
+ error("Cannot copy " + sourcePath + " to " + targetPath, e);
+ }
+
+ protected void deleted(Path targetPath) {
+ syncResult.getDeleted().add(targetPath);
+ if (isTraceEnabled())
+ trace("Deleted " + targetPath);
+ }
+
+ protected void deleteFailed(Path targetPath, Exception e) {
+ syncResult.addError(null, targetPath, e);
+ if (isTraceEnabled())
+ error("Cannot delete " + targetPath, e);
+ }
+
+ /** Log error. */
+ protected void error(Object obj, Throwable e) {
+ System.err.println(obj);
+ e.printStackTrace();
+ }
+
+ protected boolean isTraceEnabled() {
+ return trace;
+ }
+
+ protected void trace(Object obj) {
+ System.out.println(obj);
+ }
+
+ public SyncResult<Path> getSyncResult() {
+ return syncResult;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.acr.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.cms.cli.CommandArgsException;
+import org.argeo.cms.cli.DescribedCommand;
+
+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.cms.acr.fs;
+
+import org.argeo.cms.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.cms.acr.fs;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+public class FsSyncUtils {
+ /** Sync a source path with a target path. */
+ public static void sync(Path sourceBasePath, Path targetBasePath) {
+ sync(sourceBasePath, targetBasePath, false);
+ }
+
+ /** Sync a source path with a target path. */
+ public static void sync(Path sourceBasePath, Path targetBasePath, boolean delete) {
+ sync(new BasicSyncFileVisitor(sourceBasePath, targetBasePath, delete, true));
+ }
+
+ public static void sync(BasicSyncFileVisitor syncFileVisitor) {
+ try {
+ Files.walkFileTree(syncFileVisitor.getSourceBasePath(), syncFileVisitor);
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot sync " + syncFileVisitor.getSourceBasePath() + " with "
+ + syncFileVisitor.getTargetBasePath(), e);
+ }
+ }
+
+ /**
+ * Deletes this path, recursively if needed. Does nothing if the path does not
+ * exist.
+ */
+ public static void delete(Path path) {
+ try {
+ if (!Files.exists(path))
+ return;
+ Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
+ if (e != null)
+ throw e;
+ Files.delete(directory);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot delete " + path, e);
+ }
+ }
+
+ /** Singleton. */
+ private FsSyncUtils() {
+
+ }
+}
--- /dev/null
+package org.argeo.cms.acr.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.cms.acr.fs.SyncFileVisitor;
+import org.argeo.cms.acr.fs.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 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")) {
+ throw new UnsupportedOperationException();
+// 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.cms.acr.fs;
+
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.nio.file.Path;
+import java.util.Objects;
+
+/** Synchronises two directory structures. */
+public class SyncFileVisitor extends BasicSyncFileVisitor {
+ private final static Logger logger = System.getLogger(SyncFileVisitor.class.getName());
+
+ public SyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
+ super(sourceBasePath, targetBasePath, delete, recursive);
+ }
+
+ @Override
+ protected void error(Object obj, Throwable e) {
+ logger.log(Level.ERROR, Objects.toString(obj), e);
+ }
+
+ @Override
+ protected boolean isTraceEnabled() {
+ return logger.isLoggable(Level.TRACE);
+ }
+
+ @Override
+ protected void trace(Object obj) {
+ logger.log(Level.TRACE, Objects.toString(obj));
+ }
+}
--- /dev/null
+package org.argeo.cms.acr.fs;
+
+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();
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.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.cms.cli;
+
+import java.util.List;
+
+/** {@link RuntimeException} referring during a command run. */
+public class CommandRuntimeException extends RuntimeException {
+ private static final long serialVersionUID = 5595999301269377128L;
+
+ private final DescribedCommand<?> command;
+ private final List<String> arguments;
+
+ public CommandRuntimeException(Throwable e, DescribedCommand<?> command, List<String> arguments) {
+ this(null, e, command, arguments);
+ }
+
+ public CommandRuntimeException(String message, DescribedCommand<?> command, List<String> arguments) {
+ this(message, null, command, arguments);
+ }
+
+ public CommandRuntimeException(String message, Throwable e, DescribedCommand<?> command, List<String> arguments) {
+ super(message == null ? "(" + command.getClass().getName() + " " + arguments.toString() + ")"
+ : message + " (" + command.getClass().getName() + " " + arguments.toString() + ")", e);
+ this.command = command;
+ this.arguments = arguments;
+ }
+
+ public DescribedCommand<?> getCommand() {
+ return command;
+ }
+
+ public List<String> getArguments() {
+ return arguments;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.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.cms.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.cms.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 = spaces(helpLeftPad);
+ for (String cmd : commandsCli.getSubCommands()) {
+ Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+ assert function != null;
+ out.append(leftPad);
+ out.append(cmd);
+ // TODO deal with long commands
+ out.append(spaces(helpDescPad - cmd.length()));
+ out.append(getShortDescription(function));
+ out.append('\n');
+ }
+ }
+
+ private static String spaces(int count) {
+ // Java 11
+ // return " ".repeat(count);
+ if (count <= 0)
+ return "";
+ else {
+ StringBuilder sb = new StringBuilder(count);
+ for (int i = 0; i < count; i++)
+ sb.append(' ');
+ return sb.toString();
+ }
+ }
+}
--- /dev/null
+/** Command line API. */
+package org.argeo.cms.cli;
\ No newline at end of file
import org.argeo.util.LangUtils;
public class DeployedContentRepository extends CmsContentRepository {
- private final static String ROOT_XML = "root.xml";
+ private final static String ROOT_XML = "cr:root.xml";
private final static String ACR_MOUNT_PATH = "acr.mount.path";
private CmsState cmsState;
@Override
public void start() {
super.start();
- Path rootXml = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).resolve(ROOT_XML);
+ Path rootXml = KernelUtils.getOsgiInstancePath(ROOT_XML);
initRootContentProvider(rootXml);
- Path srvPath = KernelUtils.getOsgiInstancePath(CmsConstants.SRV_WORKSPACE);
- FsContentProvider srvContentProvider = new FsContentProvider(srvPath, false);
- addProvider("/" + CmsConstants.SRV_WORKSPACE, srvContentProvider);
+// Path srvPath = KernelUtils.getOsgiInstancePath(CmsConstants.SRV_WORKSPACE);
+// FsContentProvider srvContentProvider = new FsContentProvider(srvPath, false);
+// addProvider("/" + CmsConstants.SRV_WORKSPACE, srvContentProvider);
}
@Override