adjusted to updated API, restored Folder.copy and Folder.move

This commit is contained in:
Sebastian Stenzel
2015-12-15 02:27:41 +01:00
parent 3c7651a78a
commit 762f362784
10 changed files with 273 additions and 122 deletions

View File

@@ -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;
}
}

View File

@@ -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 + ":::/";
}
}

View File

@@ -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<? extends Node> children() throws IOException {
public Stream<? extends Node> children() {
return Stream.concat(files(), folders());
}
@Override
public Stream<CryptoFile> files() throws IOException {
public Stream<CryptoFile> 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<CryptoFolder> folders() throws IOException {
public Stream<CryptoFolder> 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<? 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
}
@Override
public String toString() {
return parent.toString() + name + "/";
}
}

View File

@@ -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

View File

@@ -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);
}
/**

View File

@@ -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<Node> 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);
});
}
}
}

View File

@@ -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 <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.
*/
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 <code>false</code>.
*/
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 <code>false</code> for this folder and any child nodes.
*/
void moveTo(Folder target);

View File

@@ -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) {

View File

@@ -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

View File

@@ -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());
}
}