import org.apache.commons.cli.Options;
import org.argeo.api.cli.CommandArgsException;
import org.argeo.api.cli.DescribedCommand;
-import org.argeo.cms.acr.fs.PathSync;
-import org.argeo.cms.acr.fs.SyncResult;
+import org.argeo.cms.file.PathSync;
+import org.argeo.cms.file.SyncResult;
public class FileSync implements DescribedCommand<SyncResult<Path>> {
final static Option deleteOption = Option.builder().longOpt("delete").desc("delete from target").build();
+++ /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.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;
-
-/** 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.nio.file.Path;
-import java.util.Objects;
-
-import org.argeo.api.cms.CmsLog;
-
-/** Synchronises two directory structures. */
-public class SyncFileVisitor extends BasicSyncFileVisitor {
- private final static CmsLog log = CmsLog.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(Objects.toString(obj), e);
- }
-
- @Override
- protected boolean isTraceEnabled() {
- return log.isTraceEnabled();
- }
-
- @Override
- protected void trace(Object obj) {
- log.error(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.file;
+
+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.file;
+
+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.file;
+
+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;
+
+/** 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.file;
+
+import java.nio.file.Path;
+import java.util.Objects;
+
+import org.argeo.api.cms.CmsLog;
+
+/** Synchronises two directory structures. */
+public class SyncFileVisitor extends BasicSyncFileVisitor {
+ private final static CmsLog log = CmsLog.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(Objects.toString(obj), e);
+ }
+
+ @Override
+ protected boolean isTraceEnabled() {
+ return log.isTraceEnabled();
+ }
+
+ @Override
+ protected void trace(Object obj) {
+ log.error(Objects.toString(obj));
+ }
+}
--- /dev/null
+package org.argeo.cms.file;
+
+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();
+ }
+
+ }
+}