Pārlūkot izejas kodu

moved `CryptoFolder.contains(Node)` to `Folder.isAncestorOf(Node)`, clarified a few javadocs

Sebastian Stenzel 9 gadi atpakaļ
vecāks
revīzija
35bb042430

+ 1 - 22
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<? extends Folder> 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

+ 46 - 9
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 <code>true</code>
+	 * Creates the directory, if it doesn't exist yet. No effect, if folder already exists. After successful invocation {@link #exists()} will return <code>true</code>.
 	 * 
 	 * @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 <code>true</code> 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());
+		}
+	}
+
 }

+ 6 - 0
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}.
+ * <p>
+ * 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<? extends Folder> parent() throws UncheckedIOException;
 
 	boolean exists() throws UncheckedIOException;

+ 3 - 5
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());