Browse Source

Added new filesystem layer for read/write alignment with boundary of encrypted chunks.

Sebastian Stenzel 9 năm trước cách đây
mục cha
commit
44d1250986
15 tập tin đã thay đổi với 283 bổ sung117 xóa
  1. 15 18
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFile.java
  2. 0 35
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFileSystem.java
  3. 22 21
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFolder.java
  4. 1 9
      main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingNode.java
  5. 15 15
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileTest.java
  6. 20 19
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFolderTest.java
  7. 23 0
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFile.java
  8. 16 0
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFileSystem.java
  9. 22 0
      main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFolder.java
  10. 27 0
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedFile.java
  11. 12 0
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedFileSystem.java
  12. 26 0
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedFolder.java
  13. 27 0
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedReadableFile.java
  14. 27 0
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedWritableFile.java
  15. 30 0
      main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/package-info.java

+ 15 - 18
main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFile.java

@@ -9,37 +9,34 @@
 package org.cryptomator.filesystem.delegating;
 
 import java.io.UncheckedIOException;
-import java.util.function.Function;
+import java.util.Optional;
 
 import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.ReadableFile;
-import org.cryptomator.filesystem.WritableFile;
 
-public class DelegatingFile extends DelegatingNode<File>implements File {
+public abstract class DelegatingFile<R extends DelegatingReadableFile, W extends DelegatingWritableFile, D extends DelegatingFolder<R, W, D, ?>> extends DelegatingNode<File>implements File {
 
-	private final Function<ReadableFile, DelegatingReadableFile> readableFileFactory;
-	private final Function<WritableFile, DelegatingWritableFile> writableFileFactory;
+	private final D parent;
 
-	public DelegatingFile(DelegatingFolder parent, File delegate, Function<ReadableFile, DelegatingReadableFile> readableFileFactory, Function<WritableFile, DelegatingWritableFile> writableFileFactory) {
-		super(parent, delegate);
-		this.readableFileFactory = readableFileFactory;
-		this.writableFileFactory = writableFileFactory;
+	public DelegatingFile(D parent, File delegate) {
+		super(delegate);
+		this.parent = parent;
 	}
 
 	@Override
-	public DelegatingReadableFile openReadable() throws UncheckedIOException {
-		return readableFileFactory.apply(delegate.openReadable());
+	public Optional<D> parent() throws UncheckedIOException {
+		return Optional.of(parent);
 	}
 
 	@Override
-	public DelegatingWritableFile openWritable() throws UncheckedIOException {
-		return writableFileFactory.apply(delegate.openWritable());
-	}
+	public abstract R openReadable() throws UncheckedIOException;
+
+	@Override
+	public abstract W openWritable() throws UncheckedIOException;
 
 	@Override
 	public void copyTo(File destination) {
 		if (getClass().equals(destination.getClass())) {
-			final File delegateDest = ((DelegatingFile) destination).delegate;
+			final File delegateDest = ((DelegatingFile<?, ?, ?>) destination).delegate;
 			delegate.copyTo(delegateDest);
 		} else {
 			delegate.copyTo(destination);
@@ -49,7 +46,7 @@ public class DelegatingFile extends DelegatingNode<File>implements File {
 	@Override
 	public void moveTo(File destination) {
 		if (getClass().equals(destination.getClass())) {
-			final File delegateDest = ((DelegatingFile) destination).delegate;
+			final File delegateDest = ((DelegatingFile<?, ?, ?>) destination).delegate;
 			delegate.moveTo(delegateDest);
 		} else {
 			throw new IllegalArgumentException("Can only move DelegatingFile to other DelegatingFile.");
@@ -59,7 +56,7 @@ public class DelegatingFile extends DelegatingNode<File>implements File {
 	@Override
 	public int compareTo(File o) {
 		if (getClass().equals(o.getClass())) {
-			final File delegateOther = ((DelegatingFile) o).delegate;
+			final File delegateOther = ((DelegatingFile<?, ?, ?>) o).delegate;
 			return delegate.compareTo(delegateOther);
 		} else {
 			return delegate.compareTo(o);

+ 0 - 35
main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFileSystem.java

@@ -1,35 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2015 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- *     Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.filesystem.delegating;
-
-import java.util.function.BiFunction;
-
-import org.cryptomator.filesystem.File;
-import org.cryptomator.filesystem.FileSystem;
-import org.cryptomator.filesystem.Folder;
-
-public class DelegatingFileSystem extends DelegatingFolder implements FileSystem {
-
-	private DelegatingFileSystem(Folder delegate, BiFunction<DelegatingFolder, Folder, DelegatingFolder> folderFactory, BiFunction<DelegatingFolder, File, DelegatingFile> fileFactory) {
-		super(null, delegate, folderFactory, fileFactory);
-	}
-
-	public static DelegatingFileSystem withDelegate(Folder delegate) {
-		return new DelegatingFileSystem(delegate, DelegatingFileSystem::subFolder, DelegatingFileSystem::subFile);
-	}
-
-	private static DelegatingFolder subFolder(DelegatingFolder parent, Folder delegateSubFolder) {
-		return new DelegatingFolder(parent, delegateSubFolder, DelegatingFileSystem::subFolder, DelegatingFileSystem::subFile);
-	}
-
-	private static DelegatingFile subFile(DelegatingFolder parent, File delegateSubFile) {
-		return new DelegatingFile(parent, delegateSubFile, DelegatingReadableFile::new, DelegatingWritableFile::new);
-	}
-
-}

+ 22 - 21
main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingFolder.java

@@ -9,55 +9,56 @@
 package org.cryptomator.filesystem.delegating;
 
 import java.io.UncheckedIOException;
-import java.util.function.BiFunction;
+import java.util.Optional;
 import java.util.stream.Stream;
 
 import org.cryptomator.filesystem.File;
 import org.cryptomator.filesystem.Folder;
+import org.cryptomator.filesystem.Node;
 
-public class DelegatingFolder extends DelegatingNode<Folder>implements Folder {
+public abstract class DelegatingFolder<R extends DelegatingReadableFile, W extends DelegatingWritableFile, D extends DelegatingFolder<R, W, D, F>, F extends DelegatingFile<R, W, D>> extends DelegatingNode<Folder>
+		implements Folder {
 
-	private final BiFunction<DelegatingFolder, Folder, DelegatingFolder> folderFactory;
-	private final BiFunction<DelegatingFolder, File, DelegatingFile> fileFactory;
+	private final D parent;
 
-	public DelegatingFolder(DelegatingFolder parent, Folder delegate, BiFunction<DelegatingFolder, Folder, DelegatingFolder> folderFactory, BiFunction<DelegatingFolder, File, DelegatingFile> fileFactory) {
-		super(parent, delegate);
-		this.folderFactory = folderFactory;
-		this.fileFactory = fileFactory;
+	public DelegatingFolder(D parent, Folder delegate) {
+		super(delegate);
+		this.parent = parent;
 	}
 
 	@Override
-	public Stream<? extends DelegatingNode<?>> children() throws UncheckedIOException {
+	public Optional<D> parent() throws UncheckedIOException {
+		return Optional.ofNullable(parent);
+	}
+
+	@Override
+	public Stream<? extends Node> children() throws UncheckedIOException {
 		return Stream.concat(folders(), files());
 	}
 
 	@Override
-	public Stream<DelegatingFolder> folders() {
+	public Stream<D> folders() {
 		return delegate.folders().map(this::folder);
 	}
 
 	@Override
-	public Stream<DelegatingFile> files() throws UncheckedIOException {
+	public Stream<F> files() throws UncheckedIOException {
 		return delegate.files().map(this::file);
 	}
 
 	@Override
-	public DelegatingFile file(String name) throws UncheckedIOException {
+	public F file(String name) throws UncheckedIOException {
 		return file(delegate.file(name));
 	}
 
-	private DelegatingFile file(File delegate) {
-		return fileFactory.apply(this, delegate);
-	}
+	protected abstract F file(File delegate);
 
 	@Override
-	public DelegatingFolder folder(String name) throws UncheckedIOException {
+	public D folder(String name) throws UncheckedIOException {
 		return folder(delegate.folder(name));
 	}
 
-	private DelegatingFolder folder(Folder delegate) {
-		return folderFactory.apply(this, delegate);
-	}
+	protected abstract D folder(Folder delegate);
 
 	@Override
 	public void create() throws UncheckedIOException {
@@ -72,7 +73,7 @@ public class DelegatingFolder extends DelegatingNode<Folder>implements Folder {
 	@Override
 	public void copyTo(Folder destination) throws UncheckedIOException {
 		if (destination instanceof DelegatingFolder) {
-			final Folder delegateDest = ((DelegatingFolder) destination).delegate;
+			final Folder delegateDest = ((DelegatingFolder<?, ?, ?, ?>) destination).delegate;
 			delegate.copyTo(delegateDest);
 		} else {
 			delegate.copyTo(destination);
@@ -82,7 +83,7 @@ public class DelegatingFolder extends DelegatingNode<Folder>implements Folder {
 	@Override
 	public void moveTo(Folder destination) {
 		if (getClass().equals(destination.getClass())) {
-			final Folder delegateDest = ((DelegatingFolder) destination).delegate;
+			final Folder delegateDest = ((DelegatingFolder<?, ?, ?, ?>) destination).delegate;
 			delegate.moveTo(delegateDest);
 		} else {
 			throw new IllegalArgumentException("Can only move DelegatingFolder to other DelegatingFolder.");

+ 1 - 9
main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingNode.java

@@ -10,28 +10,20 @@ package org.cryptomator.filesystem.delegating;
 
 import java.io.UncheckedIOException;
 import java.time.Instant;
-import java.util.Optional;
 
 import org.cryptomator.filesystem.Node;
 
 abstract class DelegatingNode<T extends Node> implements Node {
 
-	private final DelegatingFolder parent;
 	protected final T delegate;
 
-	public DelegatingNode(DelegatingFolder parent, T delegate) {
+	public DelegatingNode(T delegate) {
 		if (delegate == null) {
 			throw new IllegalArgumentException("Delegate must not be null");
 		}
-		this.parent = parent;
 		this.delegate = delegate;
 	}
 
-	@Override
-	public Optional<DelegatingFolder> parent() throws UncheckedIOException {
-		return Optional.ofNullable(parent);
-	}
-
 	@Override
 	public String name() throws UncheckedIOException {
 		return delegate.name();

+ 15 - 15
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFileTest.java

@@ -23,7 +23,7 @@ public class DelegatingFileTest {
 	@Test
 	public void testName() {
 		File mockFile = Mockito.mock(File.class);
-		DelegatingFile delegatingFile = new DelegatingFile(null, mockFile, null, null);
+		DelegatingFile<?, ?, ?> delegatingFile = new TestDelegatingFile(null, mockFile);
 
 		Mockito.when(mockFile.name()).thenReturn("Test");
 		Assert.assertEquals(mockFile.name(), delegatingFile.name());
@@ -34,15 +34,15 @@ public class DelegatingFileTest {
 		Folder mockFolder = Mockito.mock(Folder.class);
 		File mockFile = Mockito.mock(File.class);
 
-		DelegatingFolder delegatingParent = DelegatingFileSystem.withDelegate(mockFolder);
-		DelegatingFile delegatingFile = new DelegatingFile(delegatingParent, mockFile, null, null);
+		TestDelegatingFileSystem delegatingParent = TestDelegatingFileSystem.withRoot(mockFolder);
+		DelegatingFile<?, ?, ?> delegatingFile = new TestDelegatingFile(delegatingParent, mockFile);
 		Assert.assertEquals(delegatingParent, delegatingFile.parent().get());
 	}
 
 	@Test
 	public void testExists() {
 		File mockFile = Mockito.mock(File.class);
-		DelegatingFile delegatingFile = new DelegatingFile(null, mockFile, null, null);
+		DelegatingFile<?, ?, ?> delegatingFile = new TestDelegatingFile(null, mockFile);
 
 		Mockito.when(mockFile.exists()).thenReturn(true);
 		Assert.assertTrue(delegatingFile.exists());
@@ -57,7 +57,7 @@ public class DelegatingFileTest {
 		Instant now = Instant.now();
 
 		Mockito.when(mockFile.lastModified()).thenReturn(now);
-		DelegatingFile delegatingFile = new DelegatingFile(null, mockFile, null, null);
+		DelegatingFile<?, ?, ?> delegatingFile = new TestDelegatingFile(null, mockFile);
 		Assert.assertEquals(now, delegatingFile.lastModified());
 	}
 
@@ -67,7 +67,7 @@ public class DelegatingFileTest {
 		ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
 
 		Mockito.when(mockFile.openReadable()).thenReturn(mockReadableFile);
-		DelegatingFile delegatingFile = new DelegatingFile(null, mockFile, DelegatingReadableFile::new, null);
+		DelegatingFile<?, ?, ?> delegatingFile = new TestDelegatingFile(null, mockFile);
 		Assert.assertNotNull(delegatingFile.openReadable());
 	}
 
@@ -77,7 +77,7 @@ public class DelegatingFileTest {
 		WritableFile mockWritableFile = Mockito.mock(WritableFile.class);
 
 		Mockito.when(mockFile.openWritable()).thenReturn(mockWritableFile);
-		DelegatingFile delegatingFile = new DelegatingFile(null, mockFile, null, DelegatingWritableFile::new);
+		DelegatingFile<?, ?, ?> delegatingFile = new TestDelegatingFile(null, mockFile);
 		Assert.assertNotNull(delegatingFile.openWritable());
 	}
 
@@ -85,8 +85,8 @@ public class DelegatingFileTest {
 	public void testMoveTo() {
 		File mockFile1 = Mockito.mock(File.class);
 		File mockFile2 = Mockito.mock(File.class);
-		DelegatingFile delegatingFile1 = new DelegatingFile(null, mockFile1, null, null);
-		DelegatingFile delegatingFile2 = new DelegatingFile(null, mockFile2, null, null);
+		DelegatingFile<?, ?, ?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
+		DelegatingFile<?, ?, ?> delegatingFile2 = new TestDelegatingFile(null, mockFile2);
 
 		delegatingFile1.moveTo(delegatingFile2);
 		Mockito.verify(mockFile1).moveTo(mockFile2);
@@ -96,7 +96,7 @@ public class DelegatingFileTest {
 	public void testMoveToDestinationFromDifferentLayer() {
 		File mockFile1 = Mockito.mock(File.class);
 		File mockFile2 = Mockito.mock(File.class);
-		DelegatingFile delegatingFile1 = new DelegatingFile(null, mockFile1, null, null);
+		DelegatingFile<?, ?, ?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
 
 		delegatingFile1.moveTo(mockFile2);
 	}
@@ -105,8 +105,8 @@ public class DelegatingFileTest {
 	public void testCopyTo() {
 		File mockFile1 = Mockito.mock(File.class);
 		File mockFile2 = Mockito.mock(File.class);
-		DelegatingFile delegatingFile1 = new DelegatingFile(null, mockFile1, null, null);
-		DelegatingFile delegatingFile2 = new DelegatingFile(null, mockFile2, null, null);
+		DelegatingFile<?, ?, ?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
+		DelegatingFile<?, ?, ?> delegatingFile2 = new TestDelegatingFile(null, mockFile2);
 
 		delegatingFile1.copyTo(delegatingFile2);
 		Mockito.verify(mockFile1).copyTo(mockFile2);
@@ -116,7 +116,7 @@ public class DelegatingFileTest {
 	public void testCopyToDestinationFromDifferentLayer() {
 		File mockFile1 = Mockito.mock(File.class);
 		File mockFile2 = Mockito.mock(File.class);
-		DelegatingFile delegatingFile1 = new DelegatingFile(null, mockFile1, null, null);
+		DelegatingFile<?, ?, ?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
 
 		delegatingFile1.copyTo(mockFile2);
 		Mockito.verify(mockFile1).copyTo(mockFile2);
@@ -128,8 +128,8 @@ public class DelegatingFileTest {
 		File mockFile2 = Mockito.mock(File.class);
 
 		Mockito.when(mockFile1.compareTo(mockFile2)).thenReturn(-1);
-		DelegatingFile delegatingFile1 = new DelegatingFile(null, mockFile1, null, null);
-		DelegatingFile delegatingFile2 = new DelegatingFile(null, mockFile2, null, null);
+		DelegatingFile<?, ?, ?> delegatingFile1 = new TestDelegatingFile(null, mockFile1);
+		DelegatingFile<?, ?, ?> delegatingFile2 = new TestDelegatingFile(null, mockFile2);
 		Assert.assertEquals(-1, delegatingFile1.compareTo(delegatingFile2));
 	}
 

+ 20 - 19
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingFolderTest.java

@@ -16,6 +16,7 @@ import java.util.stream.Stream;
 
 import org.cryptomator.filesystem.File;
 import org.cryptomator.filesystem.Folder;
+import org.cryptomator.filesystem.Node;
 import org.hamcrest.Matchers;
 import org.junit.Assert;
 import org.junit.Test;
@@ -26,7 +27,7 @@ public class DelegatingFolderTest {
 	@Test
 	public void testName() {
 		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder delegatingFolder = new DelegatingFolder(null, mockFolder, null, null);
+		DelegatingFolder<?, ?, ?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
 
 		Mockito.when(mockFolder.name()).thenReturn("Test");
 		Assert.assertEquals(mockFolder.name(), delegatingFolder.name());
@@ -37,15 +38,15 @@ public class DelegatingFolderTest {
 		Folder mockFolder1 = Mockito.mock(Folder.class);
 		Folder mockFolder2 = Mockito.mock(Folder.class);
 
-		DelegatingFolder delegatingParent = DelegatingFileSystem.withDelegate(mockFolder1);
-		DelegatingFolder delegatingFolder = new DelegatingFolder(delegatingParent, mockFolder2, null, null);
+		TestDelegatingFileSystem delegatingParent = TestDelegatingFileSystem.withRoot(mockFolder1);
+		DelegatingFolder<?, ?, ?, ?> delegatingFolder = new TestDelegatingFolder(delegatingParent, mockFolder2);
 		Assert.assertEquals(delegatingParent, delegatingFolder.parent().get());
 	}
 
 	@Test
 	public void testExists() {
 		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder delegatingFolder = new DelegatingFolder(null, mockFolder, null, null);
+		DelegatingFolder<?, ?, ?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
 
 		Mockito.when(mockFolder.exists()).thenReturn(true);
 		Assert.assertTrue(delegatingFolder.exists());
@@ -60,19 +61,19 @@ public class DelegatingFolderTest {
 		Instant now = Instant.now();
 
 		Mockito.when(mockFolder.lastModified()).thenReturn(now);
-		DelegatingFolder delegatingFolder = new DelegatingFolder(null, mockFolder, null, null);
+		DelegatingFolder<?, ?, ?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
 		Assert.assertEquals(now, delegatingFolder.lastModified());
 	}
 
 	@Test
 	public void testChildren() {
 		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder delegatingFolder = DelegatingFileSystem.withDelegate(mockFolder);
+		TestDelegatingFileSystem delegatingFolder = TestDelegatingFileSystem.withRoot(mockFolder);
 
 		Folder subFolder1 = Mockito.mock(Folder.class);
-		DelegatingFolder delegatingSubFolder1 = new DelegatingFolder(delegatingFolder, subFolder1, null, null);
+		TestDelegatingFolder delegatingSubFolder1 = new TestDelegatingFolder(delegatingFolder, subFolder1);
 		File subFile1 = Mockito.mock(File.class);
-		DelegatingFile delegatingSubFile1 = new DelegatingFile(delegatingFolder, subFile1, null, null);
+		TestDelegatingFile delegatingSubFile1 = new TestDelegatingFile(delegatingFolder, subFile1);
 
 		/* folders */
 		Mockito.when(mockFolder.folder("subFolder1")).thenReturn(subFolder1);
@@ -81,7 +82,7 @@ public class DelegatingFolderTest {
 		Mockito.<Stream<? extends Folder>>when(mockFolder.folders()).thenAnswer((invocation) -> {
 			return Arrays.stream(new Folder[] {subFolder1});
 		});
-		List<DelegatingFolder> subFolders = delegatingFolder.folders().collect(Collectors.toList());
+		List<TestDelegatingFolder> subFolders = delegatingFolder.folders().collect(Collectors.toList());
 		Assert.assertThat(subFolders, Matchers.containsInAnyOrder(delegatingSubFolder1));
 
 		/* files */
@@ -91,11 +92,11 @@ public class DelegatingFolderTest {
 		Mockito.<Stream<? extends File>>when(mockFolder.files()).thenAnswer((invocation) -> {
 			return Arrays.stream(new File[] {subFile1});
 		});
-		List<DelegatingFile> subFiles = delegatingFolder.files().collect(Collectors.toList());
+		List<TestDelegatingFile> subFiles = delegatingFolder.files().collect(Collectors.toList());
 		Assert.assertThat(subFiles, Matchers.containsInAnyOrder(delegatingSubFile1));
 
 		/* files and folders */
-		List<DelegatingNode<?>> children = delegatingFolder.children().collect(Collectors.toList());
+		List<Node> children = delegatingFolder.children().collect(Collectors.toList());
 		DelegatingNode<?>[] expectedChildren = new DelegatingNode[] {delegatingSubFolder1, delegatingSubFile1};
 		Assert.assertThat(children, Matchers.containsInAnyOrder(expectedChildren));
 
@@ -105,8 +106,8 @@ public class DelegatingFolderTest {
 	public void testMoveTo() {
 		Folder mockFolder1 = Mockito.mock(Folder.class);
 		Folder mockFolder2 = Mockito.mock(Folder.class);
-		DelegatingFolder delegatingFolder1 = new DelegatingFolder(null, mockFolder1, null, null);
-		DelegatingFolder delegatingFolder2 = new DelegatingFolder(null, mockFolder2, null, null);
+		DelegatingFolder<?, ?, ?, ?> delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1);
+		DelegatingFolder<?, ?, ?, ?> delegatingFolder2 = new TestDelegatingFolder(null, mockFolder2);
 
 		delegatingFolder1.moveTo(delegatingFolder2);
 		Mockito.verify(mockFolder1).moveTo(mockFolder2);
@@ -116,7 +117,7 @@ public class DelegatingFolderTest {
 	public void testMoveToDestinationFromDifferentLayer() {
 		Folder mockFolder1 = Mockito.mock(Folder.class);
 		Folder mockFolder2 = Mockito.mock(Folder.class);
-		DelegatingFolder delegatingFolder1 = new DelegatingFolder(null, mockFolder1, null, null);
+		DelegatingFolder<?, ?, ?, ?> delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1);
 
 		delegatingFolder1.moveTo(mockFolder2);
 	}
@@ -125,8 +126,8 @@ public class DelegatingFolderTest {
 	public void testCopyTo() {
 		Folder mockFolder1 = Mockito.mock(Folder.class);
 		Folder mockFolder2 = Mockito.mock(Folder.class);
-		DelegatingFolder delegatingFolder1 = new DelegatingFolder(null, mockFolder1, null, null);
-		DelegatingFolder delegatingFolder2 = new DelegatingFolder(null, mockFolder2, null, null);
+		DelegatingFolder<?, ?, ?, ?> delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1);
+		DelegatingFolder<?, ?, ?, ?> delegatingFolder2 = new TestDelegatingFolder(null, mockFolder2);
 
 		delegatingFolder1.copyTo(delegatingFolder2);
 		Mockito.verify(mockFolder1).copyTo(mockFolder2);
@@ -136,7 +137,7 @@ public class DelegatingFolderTest {
 	public void testCopyToDestinationFromDifferentLayer() {
 		Folder mockFolder1 = Mockito.mock(Folder.class);
 		Folder mockFolder2 = Mockito.mock(Folder.class);
-		DelegatingFolder delegatingFolder1 = new DelegatingFolder(null, mockFolder1, null, null);
+		DelegatingFolder<?, ?, ?, ?> delegatingFolder1 = new TestDelegatingFolder(null, mockFolder1);
 
 		delegatingFolder1.copyTo(mockFolder2);
 		Mockito.verify(mockFolder1).copyTo(mockFolder2);
@@ -145,7 +146,7 @@ public class DelegatingFolderTest {
 	@Test
 	public void testCreate() {
 		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder delegatingFolder = new DelegatingFolder(null, mockFolder, null, null);
+		DelegatingFolder<?, ?, ?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
 
 		delegatingFolder.create();
 		Mockito.verify(mockFolder).create();
@@ -154,7 +155,7 @@ public class DelegatingFolderTest {
 	@Test
 	public void testDelete() {
 		Folder mockFolder = Mockito.mock(Folder.class);
-		DelegatingFolder delegatingFolder = new DelegatingFolder(null, mockFolder, null, null);
+		DelegatingFolder<?, ?, ?, ?> delegatingFolder = new TestDelegatingFolder(null, mockFolder);
 
 		delegatingFolder.delete();
 		Mockito.verify(mockFolder).delete();

+ 23 - 0
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFile.java

@@ -0,0 +1,23 @@
+package org.cryptomator.filesystem.delegating;
+
+import java.io.UncheckedIOException;
+
+import org.cryptomator.filesystem.File;
+
+class TestDelegatingFile extends DelegatingFile<DelegatingReadableFile, DelegatingWritableFile, TestDelegatingFolder> {
+
+	public TestDelegatingFile(TestDelegatingFolder parent, File delegate) {
+		super(parent, delegate);
+	}
+
+	@Override
+	public DelegatingReadableFile openReadable() throws UncheckedIOException {
+		return new DelegatingReadableFile(delegate.openReadable());
+	}
+
+	@Override
+	public DelegatingWritableFile openWritable() throws UncheckedIOException {
+		return new DelegatingWritableFile(delegate.openWritable());
+	}
+
+}

+ 16 - 0
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFileSystem.java

@@ -0,0 +1,16 @@
+package org.cryptomator.filesystem.delegating;
+
+import org.cryptomator.filesystem.FileSystem;
+import org.cryptomator.filesystem.Folder;
+
+class TestDelegatingFileSystem extends TestDelegatingFolder implements FileSystem {
+
+	private TestDelegatingFileSystem(Folder delegate) {
+		super(null, delegate);
+	}
+
+	public static TestDelegatingFileSystem withRoot(Folder delegate) {
+		return new TestDelegatingFileSystem(delegate);
+	}
+
+}

+ 22 - 0
main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/TestDelegatingFolder.java

@@ -0,0 +1,22 @@
+package org.cryptomator.filesystem.delegating;
+
+import org.cryptomator.filesystem.File;
+import org.cryptomator.filesystem.Folder;
+
+class TestDelegatingFolder extends DelegatingFolder<DelegatingReadableFile, DelegatingWritableFile, TestDelegatingFolder, TestDelegatingFile> {
+
+	public TestDelegatingFolder(TestDelegatingFolder parent, Folder delegate) {
+		super(parent, delegate);
+	}
+
+	@Override
+	protected TestDelegatingFile file(File delegate) {
+		return new TestDelegatingFile(this, delegate);
+	}
+
+	@Override
+	protected TestDelegatingFolder folder(Folder delegate) {
+		return new TestDelegatingFolder(this, delegate);
+	}
+
+}

+ 27 - 0
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedFile.java

@@ -0,0 +1,27 @@
+package org.cryptomator.filesystem.blockaligned;
+
+import java.io.UncheckedIOException;
+
+import org.cryptomator.filesystem.File;
+import org.cryptomator.filesystem.delegating.DelegatingFile;
+
+class BlockAlignedFile extends DelegatingFile<BlockAlignedReadableFile, BlockAlignedWritableFile, BlockAlignedFolder> {
+
+	private final int blockSize;
+
+	public BlockAlignedFile(BlockAlignedFolder parent, File delegate, int blockSize) {
+		super(parent, delegate);
+		this.blockSize = blockSize;
+	}
+
+	@Override
+	public BlockAlignedReadableFile openReadable() throws UncheckedIOException {
+		return new BlockAlignedReadableFile(delegate.openReadable(), blockSize);
+	}
+
+	@Override
+	public BlockAlignedWritableFile openWritable() throws UncheckedIOException {
+		return new BlockAlignedWritableFile(delegate.openWritable(), blockSize);
+	}
+
+}

+ 12 - 0
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedFileSystem.java

@@ -0,0 +1,12 @@
+package org.cryptomator.filesystem.blockaligned;
+
+import org.cryptomator.filesystem.FileSystem;
+import org.cryptomator.filesystem.Folder;
+
+public class BlockAlignedFileSystem extends BlockAlignedFolder implements FileSystem {
+
+	public BlockAlignedFileSystem(Folder delegate, int blockSize) {
+		super(null, delegate, blockSize);
+	}
+
+}

+ 26 - 0
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedFolder.java

@@ -0,0 +1,26 @@
+package org.cryptomator.filesystem.blockaligned;
+
+import org.cryptomator.filesystem.File;
+import org.cryptomator.filesystem.Folder;
+import org.cryptomator.filesystem.delegating.DelegatingFolder;
+
+class BlockAlignedFolder extends DelegatingFolder<BlockAlignedReadableFile, BlockAlignedWritableFile, BlockAlignedFolder, BlockAlignedFile> {
+
+	private final int blockSize;
+
+	public BlockAlignedFolder(BlockAlignedFolder parent, Folder delegate, int blockSize) {
+		super(parent, delegate);
+		this.blockSize = blockSize;
+	}
+
+	@Override
+	protected BlockAlignedFile file(File delegate) {
+		return new BlockAlignedFile(this, delegate, blockSize);
+	}
+
+	@Override
+	protected BlockAlignedFolder folder(Folder delegate) {
+		return new BlockAlignedFolder(this, delegate, blockSize);
+	}
+
+}

+ 27 - 0
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedReadableFile.java

@@ -0,0 +1,27 @@
+package org.cryptomator.filesystem.blockaligned;
+
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+
+import org.cryptomator.filesystem.ReadableFile;
+import org.cryptomator.filesystem.delegating.DelegatingReadableFile;
+
+class BlockAlignedReadableFile extends DelegatingReadableFile {
+
+	public BlockAlignedReadableFile(ReadableFile delegate, int blockSize) {
+		super(delegate);
+	}
+
+	@Override
+	public void position(long position) throws UncheckedIOException {
+		// TODO Auto-generated method stub
+		super.position(position);
+	}
+
+	@Override
+	public int read(ByteBuffer target) throws UncheckedIOException {
+		// TODO Auto-generated method stub
+		return super.read(target);
+	}
+
+}

+ 27 - 0
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/BlockAlignedWritableFile.java

@@ -0,0 +1,27 @@
+package org.cryptomator.filesystem.blockaligned;
+
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+
+import org.cryptomator.filesystem.WritableFile;
+import org.cryptomator.filesystem.delegating.DelegatingWritableFile;
+
+class BlockAlignedWritableFile extends DelegatingWritableFile {
+
+	public BlockAlignedWritableFile(WritableFile delegate, int blockSize) {
+		super(delegate);
+	}
+
+	@Override
+	public void position(long position) throws UncheckedIOException {
+		// TODO Auto-generated method stub
+		super.position(position);
+	}
+
+	@Override
+	public int write(ByteBuffer source) throws UncheckedIOException {
+		// TODO Auto-generated method stub
+		return super.write(source);
+	}
+
+}

+ 30 - 0
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/blockaligned/package-info.java

@@ -0,0 +1,30 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ *     Sebastian Stenzel - initial API and implementation
+ *******************************************************************************/
+/**
+ * Provides a decoration layer for the {@link org.cryptomator.filesystem Filesystem API}, which guarantees, that all read/write attempts to underlying files always begin at a block start position.
+ * Block start positions are integer multiples of a block size + a fixed block shift.
+ * <p>
+ * In general the formula to align a requested read with a physical read is <code>floor(x / blockSize) * blockSize</code><br/>
+ * For example <code>blockSize=10</code> result in the following block-aligned read/write attempts:
+ * 
+ * <table>
+ * <thead>
+ * 	<tr><th>Requested Read</th><th>Physical Read</th></tr>
+ * </thead>
+ * <tbody>
+ * 	<tr><td>0</td><td>0</td></td>
+ * 	<tr><td>5</td><td>0</td></td>
+ * 	<tr><td>9</td><td>0</td></td>
+ * 	<tr><td>10</td><td>10</td></td>
+ * 	<tr><td>11</td><td>10</td></td>
+ * 	<tr><td>35</td><td>30</td></td>
+ * </tbody>
+ * </table>
+ */
+package org.cryptomator.filesystem.blockaligned;