diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFolder.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFolder.java index 61859f645..c86c89885 100644 --- a/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFolder.java +++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFolder.java @@ -15,7 +15,6 @@ import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Instant; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -146,15 +145,6 @@ class CryptoFolder extends CryptoNode implements Folder { physicalFolder().create(FolderCreateMode.INCLUDING_PARENTS); } - @Override - public void copyTo(Folder target) { - if (this.contains(target)) { - throw new IllegalArgumentException("Can not copy parent to child directory (src: " + this + ", dst: " + target + ")"); - } - - Folder.super.copyTo(target); - } - @Override public void moveTo(Folder target) { if (target instanceof CryptoFolder) { @@ -165,7 +155,7 @@ class CryptoFolder extends CryptoNode implements Folder { } private void moveToInternal(CryptoFolder target) { - if (this.contains(target) || target.contains(this)) { + if (this.isAncestorOf(target) || target.isAncestorOf(this)) { throw new IllegalArgumentException("Can not move directories containing one another (src: " + this + ", dst: " + target + ")"); } @@ -180,17 +170,6 @@ class CryptoFolder extends CryptoNode implements Folder { directoryId.set(null); } - private boolean contains(Node node) { - Optional nodeParent = node.parent(); - while (nodeParent.isPresent()) { - if (this.equals(nodeParent.get())) { - return true; - } - nodeParent = nodeParent.get().parent(); - } - return false; - } - @Override public void delete() { // TODO Auto-generated method stub diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java index fa8215246..967e3d682 100644 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java +++ b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Folder.java @@ -58,7 +58,7 @@ public interface Folder extends Node { Folder folder(String name) throws UncheckedIOException; /** - * Creates the directory, if it doesn't exist yet. After successful invocation {@link #exists()} will return true + * Creates the directory, if it doesn't exist yet. No effect, if folder already exists. After successful invocation {@link #exists()} will return true. * * @param mode Depending on this option either the attempt is made to recursively create all parent directories or an exception is thrown if the parent doesn't exist yet. * @throws UncheckedIOException wrapping an {@link FileNotFoundException}, if mode is {@link FolderCreateMode#FAIL_IF_PARENT_IS_MISSING FAIL_IF_PARENT_IS_MISSING} and parent doesn't exist. @@ -66,20 +66,41 @@ public interface Folder extends Node { void create(FolderCreateMode mode) throws UncheckedIOException; /** - * Copies this directory and its contents to the given destination. + * Recusively copies this directory and all its contents to (not into) the given destination, creating nonexisting parent directories. + * If the target exists it is deleted before performing the copy. + * + * @param target Destination folder. Must not be a descendant of this folder. */ default void copyTo(Folder target) throws UncheckedIOException { - final Folder copy = target.folder(this.name()); - copy.create(FolderCreateMode.INCLUDING_PARENTS); - folders().forEach(folder -> folder.copyTo(copy)); + if (this.isAncestorOf(target)) { + throw new IllegalArgumentException("Can not copy parent to child directory (src: " + this + ", dst: " + target + ")"); + } + + // remove previous contents: + if (target.exists()) { + target.delete(); + } + + // make sure target directory exists: + target.create(FolderCreateMode.INCLUDING_PARENTS); + assert target.exists(); + + // copy files: files().forEach(srcFile -> { - final File dstFile = copy.file(srcFile.name()); - try (ReadableFile src = srcFile.openReadable(1, TimeUnit.SECONDS); WritableFile dst = dstFile.openWritable(1, TimeUnit.SECONDS)) { - src.copyTo(dst); + try (ReadableFile src = srcFile.openReadable(1, TimeUnit.SECONDS)) { + final File dstFile = target.file(srcFile.name()); + try (WritableFile dst = dstFile.openWritable(1, TimeUnit.MILLISECONDS)) { + src.copyTo(dst); + } catch (TimeoutException e) { + throw new IllegalStateException("Destination file (" + dstFile + ") must not exist yet, thus can't be locked."); + } } catch (TimeoutException e) { - throw new UncheckedIOException(new IOException("Failed to lock file in time.", e)); + throw new UncheckedIOException(new IOException("Failed to lock source file (" + srcFile + ") in time.", e)); } }); + + // copy subdirectories: + folders().forEach(folder -> folder.copyTo(target.folder(folder.name()))); } /** @@ -113,4 +134,20 @@ public interface Folder extends Node { .map(Folder.class::cast); } + /** + * Recursively checks whether this folder or any subfolder contains the given node. + * + * @param node Potential child, grandchild, ... + * @return true if this folder is an ancestor of the node. + */ + default boolean isAncestorOf(Node node) { + if (!node.parent().isPresent()) { + return false; + } else if (node.parent().get().equals(this)) { + return true; + } else { + return this.isAncestorOf(node.parent().get()); + } + } + } diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java index f0fcb52cd..f86b52c77 100644 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java +++ b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Node.java @@ -12,6 +12,9 @@ import java.util.Optional; /** * Represents a node, namely a {@link File} or {@link Folder}, in a * {@link FileSystem}. + *

+ * A node's identity (i.e. {@link #hashCode()} and {@link #equals(Object)}) depends on its parent node and its name (forming the node's path). + * These properties are meant to be immutable. This means that e.g. moving a node doesn't modify the node's identity but rather transfers properties to the destination node. * * @author Markus Kreusch * @see Folder @@ -21,6 +24,9 @@ public interface Node { String name() throws UncheckedIOException; + /** + * @return Optional parent folder. No parent is present for the root node (see {@link FileSystem#parent()}). + */ Optional parent() throws UncheckedIOException; boolean exists() throws UncheckedIOException; diff --git a/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java b/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java index 6ee313bed..d51ec5091 100644 --- a/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java +++ b/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java @@ -102,7 +102,6 @@ public class InMemoryFileSystemTest { final FileSystem fs = new InMemoryFileSystem(); final Folder fooBarFolder = fs.folder("foo").folder("bar"); final Folder qweAsdFolder = fs.folder("qwe").folder("asd"); - final Folder qweAsdBarFolder = qweAsdFolder.folder("bar"); final File test1File = fooBarFolder.file("test1.txt"); final File test2File = fooBarFolder.file("test2.txt"); fooBarFolder.create(FolderCreateMode.INCLUDING_PARENTS); @@ -116,11 +115,10 @@ public class InMemoryFileSystemTest { Assert.assertTrue(test1File.exists()); Assert.assertTrue(test2File.exists()); - // copy foo/bar/ to qwe/asd/ (result is qwe/asd/bar/file1.txt & qwe/asd/bar/file2.txt) + // copy foo/bar/ to qwe/asd/ (result is qwe/asd/file1.txt & qwe/asd/file2.txt) fooBarFolder.copyTo(qweAsdFolder); - Assert.assertTrue(qweAsdBarFolder.exists()); - Assert.assertEquals(1, qweAsdFolder.folders().count()); - Assert.assertEquals(2, qweAsdBarFolder.files().count()); + Assert.assertTrue(qweAsdFolder.exists()); + Assert.assertEquals(2, qweAsdFolder.files().count()); // make sure original files still exist: Assert.assertTrue(test1File.exists());