From 762f3627846ce54e478dfb4a01aece5f450502e5 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 15 Dec 2015 02:27:41 +0100 Subject: [PATCH] adjusted to updated API, restored Folder.copy and Folder.move --- .../org/cryptomator/crypto/fs/CryptoFile.java | 10 ++- .../crypto/fs/CryptoFileSystem.java | 17 ++-- .../cryptomator/crypto/fs/CryptoFolder.java | 81 ++++++++++++++----- .../org/cryptomator/crypto/fs/CryptoNode.java | 8 +- .../crypto/fs/CryptoFileSystemTest.java | 73 ++++++++++++++--- .../crypto/fs/DirectoryWalker.java | 23 +++--- .../org/cryptomator/filesystem/Folder.java | 37 +++++++-- .../filesystem/inmem/InMemoryFile.java | 70 ++++++++-------- .../filesystem/inmem/InMemoryFolder.java | 42 ++++++---- .../inmem/InMemoryFileSystemTest.java | 34 +++++++- 10 files changed, 273 insertions(+), 122 deletions(-) diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFile.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFile.java index 769ed35ef..b79691c13 100644 --- a/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFile.java +++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFile.java @@ -8,7 +8,6 @@ *******************************************************************************/ package org.cryptomator.crypto.fs; -import java.io.IOException; import java.io.UncheckedIOException; import java.time.Instant; import java.util.concurrent.TimeUnit; @@ -39,15 +38,20 @@ public class CryptoFile extends CryptoNode implements File { } @Override - public ReadableFile openReadable(long timeout, TimeUnit unit) throws IOException, TimeoutException { + public ReadableFile openReadable(long timeout, TimeUnit unit) throws TimeoutException { // TODO Auto-generated method stub return null; } @Override - public WritableFile openWritable(long timeout, TimeUnit unit) throws IOException, TimeoutException { + public WritableFile openWritable(long timeout, TimeUnit unit) throws TimeoutException { // TODO Auto-generated method stub return null; } + @Override + public String toString() { + return parent.toString() + name; + } + } diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFileSystem.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFileSystem.java index 59c7f2e4b..af41f917c 100644 --- a/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFileSystem.java +++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFileSystem.java @@ -9,6 +9,7 @@ package org.cryptomator.crypto.fs; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -37,7 +38,7 @@ public class CryptoFileSystem extends CryptoFolder implements FileSystem { } @Override - File physicalFile() throws IOException { + File physicalFile() { return physicalDataRoot().file(ROOT_DIR_FILE); } @@ -67,12 +68,7 @@ public class CryptoFileSystem extends CryptoFolder implements FileSystem { } @Override - public String toString() { - return "/"; - } - - @Override - public void create(FolderCreateMode mode) throws IOException { + public void create(FolderCreateMode mode) { physicalDataRoot().create(mode); physicalMetadataRoot().create(mode); final File dirFile = physicalFile(); @@ -81,9 +77,14 @@ public class CryptoFileSystem extends CryptoFolder implements FileSystem { final ByteBuffer buf = ByteBuffer.wrap(directoryId.getBytes()); writable.write(buf); } catch (TimeoutException e) { - throw new IOException("Failed to lock directory file in time." + dirFile, e); + throw new UncheckedIOException(new IOException("Failed to lock directory file in time." + dirFile, e)); } physicalFolder().create(FolderCreateMode.INCLUDING_PARENTS); } + @Override + public String toString() { + return physicalRoot + ":::/"; + } + } 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 d5f5db866..61859f645 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,6 +15,7 @@ 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; @@ -46,7 +47,7 @@ class CryptoFolder extends CryptoNode implements Folder { return name() + FILE_EXT; } - protected String getDirectoryId() throws IOException { + protected String getDirectoryId() { if (directoryId.get() == null) { File dirFile = physicalFile(); if (dirFile.exists()) { @@ -58,9 +59,7 @@ class CryptoFolder extends CryptoNode implements Folder { buf.get(bytes); directoryId.set(new String(bytes)); } catch (TimeoutException e) { - throw new IOException("Failed to lock directory file in time." + dirFile, e); - } catch (IOException e) { - throw new UncheckedIOException(e); + throw new UncheckedIOException(new IOException("Failed to lock directory file in time." + dirFile, e)); } } else { directoryId.compareAndSet(null, UUID.randomUUID().toString()); @@ -69,11 +68,11 @@ class CryptoFolder extends CryptoNode implements Folder { return directoryId.get(); } - File physicalFile() throws IOException { + File physicalFile() { return parent.physicalFolder().file(encryptedName()); } - Folder physicalFolder() throws IOException { + Folder physicalFolder() { final String encryptedThenHashedDirId; try { final byte[] hash = MessageDigest.getInstance("SHA-1").digest(getDirectoryId().getBytes()); @@ -87,20 +86,16 @@ class CryptoFolder extends CryptoNode implements Folder { @Override public Instant lastModified() { - try { - return physicalFile().lastModified(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + return physicalFile().lastModified(); } @Override - public Stream children() throws IOException { + public Stream children() { return Stream.concat(files(), folders()); } @Override - public Stream files() throws IOException { + public Stream files() { return physicalFolder().files().map(File::name).filter(s -> s.endsWith(CryptoFile.FILE_EXT)).map(this::decryptFileName).map(this::file); } @@ -115,7 +110,7 @@ class CryptoFolder extends CryptoNode implements Folder { } @Override - public Stream folders() throws IOException { + public Stream folders() { return physicalFolder().files().map(File::name).filter(s -> s.endsWith(CryptoFolder.FILE_EXT)).map(this::decryptFolderName).map(this::folder); } @@ -130,13 +125,13 @@ class CryptoFolder extends CryptoNode implements Folder { } @Override - public void create(FolderCreateMode mode) throws IOException { + public void create(FolderCreateMode mode) { final File dirFile = physicalFile(); if (dirFile.exists()) { return; } if (!parent.exists() && FolderCreateMode.FAIL_IF_PARENT_IS_MISSING.equals(mode)) { - throw new FileNotFoundException(parent.name); + throw new UncheckedIOException(new FileNotFoundException(parent.name)); } else if (!parent.exists() && FolderCreateMode.INCLUDING_PARENTS.equals(mode)) { parent.create(mode); } @@ -146,15 +141,65 @@ class CryptoFolder extends CryptoNode implements Folder { final ByteBuffer buf = ByteBuffer.wrap(directoryId.getBytes()); writable.write(buf); } catch (TimeoutException e) { - throw new IOException("Failed to lock directory file in time." + dirFile, e); + throw new UncheckedIOException(new IOException("Failed to lock directory file in time." + dirFile, e)); } physicalFolder().create(FolderCreateMode.INCLUDING_PARENTS); } @Override - public void delete() throws IOException { + 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) { + moveToInternal((CryptoFolder) target); + } else { + throw new UnsupportedOperationException("Can not move CryptoFolder to conventional folder."); + } + } + + private void moveToInternal(CryptoFolder target) { + if (this.contains(target) || target.contains(this)) { + throw new IllegalArgumentException("Can not move directories containing one another (src: " + this + ", dst: " + target + ")"); + } + + target.physicalFile().parent().get().create(FolderCreateMode.INCLUDING_PARENTS); + assert target.physicalFile().parent().get().exists(); + try (WritableFile src = this.physicalFile().openWritable(1, TimeUnit.SECONDS); WritableFile dst = target.physicalFile().openWritable(1, TimeUnit.SECONDS)) { + src.moveTo(dst); + } catch (TimeoutException e) { + throw new UncheckedIOException(new IOException("Failed to lock file for moving (src: " + this + ", dst: " + target + ")", e)); + } + // directoryId is now used by target, we must no longer use the same id (we'll generate a new one when needed) + 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 } + @Override + public String toString() { + return parent.toString() + name + "/"; + } + } diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoNode.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoNode.java index 1fd6ca6ca..de7ec1c65 100644 --- a/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoNode.java +++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoNode.java @@ -8,8 +8,6 @@ *******************************************************************************/ package org.cryptomator.crypto.fs; -import java.io.IOException; -import java.io.UncheckedIOException; import java.util.Optional; import org.cryptomator.crypto.engine.Cryptor; @@ -52,11 +50,7 @@ abstract class CryptoNode implements Node { @Override public boolean exists() { - try { - return parent.children().anyMatch(node -> node.equals(this)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + return parent.children().anyMatch(node -> node.equals(this)); } @Override diff --git a/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/CryptoFileSystemTest.java b/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/CryptoFileSystemTest.java index 2a805fb1b..55b887681 100644 --- a/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/CryptoFileSystemTest.java +++ b/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/CryptoFileSystemTest.java @@ -28,35 +28,84 @@ public class CryptoFileSystemTest { private static final Logger LOG = LoggerFactory.getLogger(CryptoFileSystemTest.class); @Test - public void testFilenameEncryption() throws UncheckedIOException, IOException { + public void testVaultStructureInitialization() throws UncheckedIOException, IOException { // mock cryptor: - Cryptor cryptor = new NoCryptor(); + final Cryptor cryptor = new NoCryptor(); // some mock fs: - FileSystem physicalFs = new InMemoryFileSystem(); - Folder physicalDataRoot = physicalFs.folder("d"); + final FileSystem physicalFs = new InMemoryFileSystem(); + final Folder physicalDataRoot = physicalFs.folder("d"); Assert.assertFalse(physicalDataRoot.exists()); // init crypto fs: - FileSystem fs = new CryptoFileSystem(physicalFs, cryptor); + final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor); fs.create(FolderCreateMode.INCLUDING_PARENTS); Assert.assertTrue(physicalDataRoot.exists()); Assert.assertEquals(physicalFs.children().count(), 2); Assert.assertEquals(1, physicalDataRoot.files().count()); // ROOT file Assert.assertEquals(1, physicalDataRoot.folders().count()); // ROOT directory + LOG.debug(DirectoryPrinter.print(physicalFs)); + } + + @Test + public void testDirectoryCreation() throws UncheckedIOException, IOException { + // mock stuff and prepare crypto FS: + final Cryptor cryptor = new NoCryptor(); + final FileSystem physicalFs = new InMemoryFileSystem(); + final Folder physicalDataRoot = physicalFs.folder("d"); + final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor); + fs.create(FolderCreateMode.INCLUDING_PARENTS); + // add another encrypted folder: - Folder fooFolder = fs.folder("foo"); - Folder barFolder = fooFolder.folder("bar"); + final Folder fooFolder = fs.folder("foo"); + final Folder fooBarFolder = fooFolder.folder("bar"); Assert.assertFalse(fooFolder.exists()); - Assert.assertFalse(barFolder.exists()); - barFolder.create(FolderCreateMode.INCLUDING_PARENTS); + Assert.assertFalse(fooBarFolder.exists()); + fooBarFolder.create(FolderCreateMode.INCLUDING_PARENTS); Assert.assertTrue(fooFolder.exists()); - Assert.assertTrue(barFolder.exists()); + Assert.assertTrue(fooBarFolder.exists()); Assert.assertEquals(3, countDataFolders(physicalDataRoot)); // parent + foo + bar - LOG.info(DirectoryPrinter.print(fs)); - LOG.info(DirectoryPrinter.print(physicalFs)); + LOG.debug(DirectoryPrinter.print(fs)); + } + + @Test + public void testDirectoryMoving() throws UncheckedIOException, IOException { + // mock stuff and prepare crypto FS: + final Cryptor cryptor = new NoCryptor(); + final FileSystem physicalFs = new InMemoryFileSystem(); + final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor); + fs.create(FolderCreateMode.INCLUDING_PARENTS); + + // create foo/bar/ and then move foo/ to baz/: + final Folder fooFolder = fs.folder("foo"); + final Folder fooBarFolder = fooFolder.folder("bar"); + final Folder bazFolder = fs.folder("baz"); + final Folder bazBarFolder = bazFolder.folder("bar"); + fooBarFolder.create(FolderCreateMode.INCLUDING_PARENTS); + Assert.assertTrue(fooBarFolder.exists()); + Assert.assertFalse(bazFolder.exists()); + fooFolder.moveTo(bazFolder); + // foo/bar/ should no longer exist, but baz/bar/ should: + Assert.assertFalse(fooBarFolder.exists()); + Assert.assertTrue(bazFolder.exists()); + Assert.assertTrue(bazBarFolder.exists()); + } + + @Test(expected = IllegalArgumentException.class) + public void testDirectoryMovingWithinBloodline() throws UncheckedIOException, IOException { + // mock stuff and prepare crypto FS: + final Cryptor cryptor = new NoCryptor(); + final FileSystem physicalFs = new InMemoryFileSystem(); + final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor); + fs.create(FolderCreateMode.INCLUDING_PARENTS); + + // create foo/bar/ and then try to move foo/bar/ to foo/ + final Folder fooFolder = fs.folder("foo"); + final Folder fooBarFolder = fooFolder.folder("bar"); + fooBarFolder.create(FolderCreateMode.INCLUDING_PARENTS); + fooBarFolder.moveTo(fooFolder); } /** diff --git a/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/DirectoryWalker.java b/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/DirectoryWalker.java index 497468718..df5a802b5 100644 --- a/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/DirectoryWalker.java +++ b/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/DirectoryWalker.java @@ -8,8 +8,6 @@ *******************************************************************************/ package org.cryptomator.crypto.fs; -import java.io.IOException; -import java.io.UncheckedIOException; import java.util.function.Consumer; import org.cryptomator.filesystem.Folder; @@ -25,19 +23,16 @@ final class DirectoryWalker { } public static void walk(Folder folder, int depth, int maxDepth, Consumer visitor) { - try { - folder.files().forEach(visitor); - if (depth == maxDepth) { - return; - } else { - folder.folders().forEach(childFolder -> { - visitor.accept(childFolder); - walk(childFolder, depth + 1, maxDepth, visitor); - }); - } - } catch (IOException e) { - throw new UncheckedIOException(e); + folder.files().forEach(visitor); + if (depth == maxDepth) { + return; + } else { + folder.folders().forEach(childFolder -> { + visitor.accept(childFolder); + walk(childFolder, depth + 1, maxDepth, visitor); + }); } + } } 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 5e79cfef0..fa8215246 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 @@ -5,8 +5,11 @@ ******************************************************************************/ package org.cryptomator.filesystem; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.UncheckedIOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.stream.Stream; /** @@ -55,14 +58,38 @@ public interface Folder extends Node { Folder folder(String name) throws UncheckedIOException; /** - * Copies this directory and its contents to the given destination. If the - * target exists it is deleted before performing the copy. + * Creates the directory, if it doesn't exist yet. 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. */ - void copyTo(Folder target); + void create(FolderCreateMode mode) throws UncheckedIOException; /** - * Moves this directory and its contents to the given destination. If the - * target exists it is deleted before performing the move. + * Copies this directory and its contents to the given destination. + */ + 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)); + 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); + } catch (TimeoutException e) { + throw new UncheckedIOException(new IOException("Failed to lock file in time.", e)); + } + }); + } + + /** + * Deletes the directory including all child elements. Afterwards {@link #exists()} will return false. + */ + void delete() throws UncheckedIOException; + + /** + * Moves this directory and its contents to the given destination. If the target exists it is deleted before performing the move. + * Afterwards {@link #exists()} will return false for this folder and any child nodes. */ void moveTo(Folder target); diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java index 90963682a..94714824a 100644 --- a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java +++ b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java @@ -9,7 +9,6 @@ package org.cryptomator.filesystem.inmem; import java.io.FileNotFoundException; -import java.io.IOException; import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.time.Instant; @@ -30,9 +29,9 @@ class InMemoryFile extends InMemoryNode implements ReadableFile, WritableFile { } @Override - public ReadableFile openReadable(long timeout, TimeUnit unit) throws IOException, TimeoutException { + public ReadableFile openReadable(long timeout, TimeUnit unit) throws TimeoutException { if (!exists()) { - throw new FileNotFoundException(this.name() + " does not exist"); + throw new UncheckedIOException(new FileNotFoundException(this.name() + " does not exist")); } try { if (!lock.readLock().tryLock(timeout, unit)) { @@ -45,7 +44,7 @@ class InMemoryFile extends InMemoryNode implements ReadableFile, WritableFile { } @Override - public WritableFile openWritable(long timeout, TimeUnit unit) throws IOException, TimeoutException { + public WritableFile openWritable(long timeout, TimeUnit unit) throws TimeoutException { try { if (!lock.writeLock().tryLock(timeout, unit)) { throw new TimeoutException("Failed to open " + name() + " for writing within time limit."); @@ -54,38 +53,34 @@ class InMemoryFile extends InMemoryNode implements ReadableFile, WritableFile { Thread.currentThread().interrupt(); } final InMemoryFolder parent = parent().get(); - try { - parent.children.compute(this.name(), (k, v) -> { - if (v != null && v != this) { - throw new IllegalStateException("More than one representation of same file"); - } - return this; - }); - } catch (UncheckedIOException e) { - throw e.getCause(); - } + parent.children.compute(this.name(), (k, v) -> { + if (v != null && v != this) { + throw new IllegalStateException("More than one representation of same file"); + } + return this; + }); return this; } @Override - public void read(ByteBuffer target) throws IOException { + public void read(ByteBuffer target) { this.read(target, 0); } @Override - public void read(ByteBuffer target, int position) throws IOException { + public void read(ByteBuffer target, int position) { content.rewind(); content.position(position); target.put(content); } @Override - public void write(ByteBuffer source) throws IOException { + public void write(ByteBuffer source) { this.write(source, content.position()); } @Override - public void write(ByteBuffer source, int position) throws IOException { + public void write(ByteBuffer source, int position) { assert content != null; if (position + source.remaining() > content.remaining()) { // create bigger buffer @@ -98,15 +93,26 @@ class InMemoryFile extends InMemoryNode implements ReadableFile, WritableFile { } @Override - public WritableFile moveTo(WritableFile other) throws IOException { - this.copyTo(other); - this.delete(); - return other; + public void setLastModified(Instant instant) { + this.lastModified = instant; } @Override - public void setLastModified(Instant instant) { - this.lastModified = instant; + public void truncate() { + content = ByteBuffer.wrap(new byte[0]); + } + + @Override + public void copyTo(WritableFile other) { + content.rewind(); + other.truncate(); + other.write(content); + } + + @Override + public void moveTo(WritableFile other) { + this.copyTo(other); + this.delete(); } @Override @@ -117,23 +123,11 @@ class InMemoryFile extends InMemoryNode implements ReadableFile, WritableFile { // returning null removes the entry. return null; }); + assert!this.exists(); } @Override - public void truncate() { - content = ByteBuffer.wrap(new byte[0]); - } - - @Override - public WritableFile copyTo(WritableFile other) throws IOException { - content.rewind(); - other.truncate(); - other.write(content); - return other; - } - - @Override - public void close() throws IOException { + public void close() { if (lock.isWriteLockedByCurrentThread()) { lock.writeLock().unlock(); } else if (lock.getReadHoldCount() > 0) { diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java index 114b667f2..2b26245bd 100644 --- a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java +++ b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java @@ -9,7 +9,6 @@ package org.cryptomator.filesystem.inmem; import java.io.FileNotFoundException; -import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.FileAlreadyExistsException; import java.time.Instant; @@ -67,36 +66,51 @@ class InMemoryFolder extends InMemoryNode implements Folder { } @Override - public void create(FolderCreateMode mode) throws IOException { + public void create(FolderCreateMode mode) { if (exists()) { return; } if (!parent.exists() && FolderCreateMode.FAIL_IF_PARENT_IS_MISSING.equals(mode)) { - throw new FileNotFoundException(parent.name); + throw new UncheckedIOException(new FileNotFoundException(parent.name)); } else if (!parent.exists() && FolderCreateMode.INCLUDING_PARENTS.equals(mode)) { parent.create(mode); } assert parent.exists(); - try { - parent.children.compute(this.name(), (k, v) -> { - if (v == null) { - this.lastModified = Instant.now(); - return this; - } else { - throw new UncheckedIOException(new FileExistsException(k)); - } - }); - } catch (UncheckedIOException e) { - throw e.getCause(); + parent.children.compute(this.name(), (k, v) -> { + if (v == null) { + this.lastModified = Instant.now(); + return this; + } else { + throw new UncheckedIOException(new FileExistsException(k)); + } + }); + assert this.exists(); + } + + @Override + public void moveTo(Folder target) { + if (target.exists()) { + target.delete(); } + assert!target.exists(); + target.create(FolderCreateMode.INCLUDING_PARENTS); + this.copyTo(target); + this.delete(); + assert!this.exists(); } @Override public void delete() { + // delete subfolder recursively: + folders().forEach(Folder::delete); + // delete direct children (this deletes files): + this.children.clear(); + // remove ourself from parent: parent.children.computeIfPresent(name, (k, v) -> { // returning null removes the entry. return null; }); + assert!this.exists(); } @Override 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 563a64180..6ee313bed 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 @@ -8,7 +8,6 @@ *******************************************************************************/ package org.cryptomator.filesystem.inmem; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -25,7 +24,7 @@ import org.junit.Test; public class InMemoryFileSystemTest { @Test - public void testFolderCreation() throws IOException { + public void testFolderCreation() { final FileSystem fs = new InMemoryFileSystem(); Folder fooFolder = fs.folder("foo"); @@ -54,7 +53,7 @@ public class InMemoryFileSystemTest { } @Test - public void testFileReadCopyMoveWrite() throws IOException, TimeoutException { + public void testFileReadCopyMoveWrite() throws TimeoutException { final FileSystem fs = new InMemoryFileSystem(); File fooFile = fs.file("foo.txt"); @@ -96,7 +95,36 @@ public class InMemoryFileSystemTest { readable.read(readBuf, 6); } Assert.assertEquals("world", new String(readBuf.array())); + } + @Test + public void testFolderCopy() throws TimeoutException { + 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); + + // create some files inside foo/bar/ + try (WritableFile writable1 = test1File.openWritable(1, TimeUnit.SECONDS); // + WritableFile writable2 = test2File.openWritable(1, TimeUnit.SECONDS)) { + writable1.write(ByteBuffer.wrap("hello".getBytes())); + writable2.write(ByteBuffer.wrap("world".getBytes())); + } + 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) + fooBarFolder.copyTo(qweAsdFolder); + Assert.assertTrue(qweAsdBarFolder.exists()); + Assert.assertEquals(1, qweAsdFolder.folders().count()); + Assert.assertEquals(2, qweAsdBarFolder.files().count()); + + // make sure original files still exist: + Assert.assertTrue(test1File.exists()); + Assert.assertTrue(test2File.exists()); } }