diff --git a/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java b/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java new file mode 100644 index 000000000..eb7a9400f --- /dev/null +++ b/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java @@ -0,0 +1,33 @@ +package org.cryptomator.common; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public final class LazyInitializer { + + private LazyInitializer() { + } + + /** + * Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509 + * + * @param Type of the value + * @param reference A reference to a maybe not yet initialized value. + * @param factory A factory providing a value for the reference, if it doesn't exist yet. The factory may be invoked multiple times, but only one result will survive. + * @return The initialized value + */ + public static T initializeLazily(AtomicReference reference, Supplier factory) { + final T existingInstance = reference.get(); + if (existingInstance != null) { + return existingInstance; + } else { + final T newInstance = factory.get(); + if (reference.compareAndSet(null, newInstance)) { + return newInstance; + } else { + return reference.get(); + } + } + } + +} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java index 0b4c7bdba..22298a9ec 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java @@ -14,13 +14,13 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import javax.security.auth.DestroyFailedException; import javax.security.auth.Destroyable; +import org.cryptomator.common.LazyInitializer; import org.cryptomator.crypto.engine.Cryptor; import org.cryptomator.crypto.engine.FileContentCryptor; import org.cryptomator.crypto.engine.FilenameCryptor; @@ -67,7 +67,7 @@ public class CryptorImpl implements Cryptor { @Override public FilenameCryptor getFilenameCryptor() { assertKeysExist(); - return initializeLazily(filenameCryptor, () -> { + return LazyInitializer.initializeLazily(filenameCryptor, () -> { return new FilenameCryptorImpl(encryptionKey, macKey); }); } @@ -75,28 +75,11 @@ public class CryptorImpl implements Cryptor { @Override public FileContentCryptor getFileContentCryptor() { assertKeysExist(); - return initializeLazily(fileContentCryptor, () -> { + return LazyInitializer.initializeLazily(fileContentCryptor, () -> { return new FileContentCryptorImpl(encryptionKey, macKey, randomSource); }); } - /** - * threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509 - */ - private T initializeLazily(AtomicReference reference, Supplier factory) { - final T existingInstance = reference.get(); - if (existingInstance != null) { - return existingInstance; - } else { - final T newInstance = factory.get(); - if (reference.compareAndSet(null, newInstance)) { - return newInstance; - } else { - return reference.get(); - } - } - } - private void assertKeysExist() { if (encryptionKey == null || encryptionKey.isDestroyed()) { throw new IllegalStateException("No or invalid encryptionKey."); diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/EncryptAndShortenIntegrationTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/EncryptAndShortenIntegrationTest.java index 8bc2c38f8..0b2bd81df 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/EncryptAndShortenIntegrationTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/EncryptAndShortenIntegrationTest.java @@ -4,6 +4,7 @@ import static org.cryptomator.filesystem.FileSystemVisitor.fileSystemVisitor; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.function.Predicate; import org.cryptomator.crypto.engine.Cryptor; import org.cryptomator.crypto.engine.impl.TestCryptorImplFactory; @@ -13,20 +14,23 @@ import org.cryptomator.filesystem.Folder; import org.cryptomator.filesystem.Node; import org.cryptomator.filesystem.ReadableFile; import org.cryptomator.filesystem.WritableFile; +import org.cryptomator.filesystem.blacklisting.BlacklistingFileSystem; import org.cryptomator.filesystem.inmem.InMemoryFileSystem; -import org.cryptomator.shortening.ShorteningFileSystem; +import org.cryptomator.filesystem.shortening.ShorteningFileSystem; import org.junit.Assert; import org.junit.Test; public class EncryptAndShortenIntegrationTest { - // private static final Logger LOG = - // LoggerFactory.getLogger(EncryptAndShortenIntegrationTest.class); + // private static final Logger LOG = LoggerFactory.getLogger(EncryptAndShortenIntegrationTest.class); @Test public void testEncryptionOfLongFolderNames() { final FileSystem physicalFs = new InMemoryFileSystem(); - final FileSystem shorteningFs = new ShorteningFileSystem(physicalFs, physicalFs.folder("m"), 70); + final Predicate isMetadataFolder = (Node node) -> node.equals(physicalFs.folder("m")); + final FileSystem metadataHidingFs = new BlacklistingFileSystem(physicalFs, isMetadataFolder); + final FileSystem shorteningFs = new ShorteningFileSystem(metadataHidingFs, physicalFs.folder("m"), 70); + final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); cryptor.randomizeMasterkey(); final FileSystem fs = new CryptoFileSystem(shorteningFs, cryptor, "foo"); @@ -36,24 +40,27 @@ public class EncryptAndShortenIntegrationTest { final Folder longFolder = fs.folder("this will be a long filename after encryption"); longFolder.create(); + // on the first (physical) layer all files including metadata files are visible: // the long name will produce a metadata file on the physical layer: - // LOG.debug("Physical file system:\n" + - // DirectoryPrinter.print(physicalFs)); + // LOG.debug("Physical file system:\n" + DirectoryPrinter.print(physicalFs)); Assert.assertEquals(1, physicalFs.folder("m").folders().count()); + Assert.assertTrue(physicalFs.folder("m").exists()); - // on the second layer all .lng files are resolved to their actual - // names: - // LOG.debug("Unlimited filename length:\n" + - // DirectoryPrinter.print(shorteningFs)); + // on the second (blacklisting) layer we hide the metadata folder: + // LOG.debug("Filtered files:\n" + DirectoryPrinter.print(metadataHidingFs)); + Assert.assertEquals(1, metadataHidingFs.folders().count()); // only "d", no "m". + + // on the third layer all .lng files are resolved to their actual names: + // LOG.debug("Unlimited filename length:\n" + DirectoryPrinter.print(shorteningFs)); fileSystemVisitor() // .forEachNode(node -> { Assert.assertFalse(node.name().endsWith(".lng")); }) // .visit(shorteningFs); - // on the third (cleartext layer) we have cleartext names on the root - // level: + + // on the fourth (cleartext) layer we have cleartext names on the root level: // LOG.debug("Cleartext files:\n" + DirectoryPrinter.print(fs)); - Assert.assertArrayEquals(new String[] { "normal folder name", "this will be a long filename after encryption" }, fs.folders().map(Node::name).sorted().toArray()); + Assert.assertArrayEquals(new String[] {"normal folder name", "this will be a long filename after encryption"}, fs.folders().map(Node::name).sorted().toArray()); } @Test 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 531b5468a..24db8c564 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 @@ -58,7 +58,11 @@ class InMemoryFile extends InMemoryNode implements File, ReadableFile, WritableF @Override public int read(ByteBuffer target) { - return ByteBuffers.copy(content, target); + if (content.hasRemaining()) { + return ByteBuffers.copy(content, target); + } else { + return -1; + } } @Override @@ -114,7 +118,7 @@ class InMemoryFile extends InMemoryNode implements File, ReadableFile, WritableF // returning null removes the entry. return null; }); - assert !this.exists(); + assert!this.exists(); } @Override diff --git a/main/filesystem-nameshortening/pom.xml b/main/filesystem-nameshortening/pom.xml index 2b762fa23..f45096044 100644 --- a/main/filesystem-nameshortening/pom.xml +++ b/main/filesystem-nameshortening/pom.xml @@ -22,7 +22,11 @@ org.cryptomator filesystem-api - + + org.cryptomator + commons + + org.apache.commons diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFile.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFile.java new file mode 100644 index 000000000..69c7cfff3 --- /dev/null +++ b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFile.java @@ -0,0 +1,26 @@ +package org.cryptomator.filesystem.blacklisting; + +import java.io.UncheckedIOException; + +import org.cryptomator.filesystem.File; +import org.cryptomator.filesystem.delegating.DelegatingFile; +import org.cryptomator.filesystem.delegating.DelegatingReadableFile; +import org.cryptomator.filesystem.delegating.DelegatingWritableFile; + +class BlacklistingFile extends DelegatingFile { + + public BlacklistingFile(BlacklistingFolder 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()); + } + +} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFileSystem.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFileSystem.java new file mode 100644 index 000000000..1179605ed --- /dev/null +++ b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFileSystem.java @@ -0,0 +1,15 @@ +package org.cryptomator.filesystem.blacklisting; + +import java.util.function.Predicate; + +import org.cryptomator.filesystem.FileSystem; +import org.cryptomator.filesystem.Folder; +import org.cryptomator.filesystem.Node; + +public class BlacklistingFileSystem extends BlacklistingFolder implements FileSystem { + + public BlacklistingFileSystem(Folder root, Predicate hiddenNodes) { + super(null, root, hiddenNodes); + } + +} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFolder.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFolder.java new file mode 100644 index 000000000..2bfc0a782 --- /dev/null +++ b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/blacklisting/BlacklistingFolder.java @@ -0,0 +1,55 @@ +package org.cryptomator.filesystem.blacklisting; + +import java.io.UncheckedIOException; +import java.nio.file.FileAlreadyExistsException; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.cryptomator.filesystem.File; +import org.cryptomator.filesystem.Folder; +import org.cryptomator.filesystem.Node; +import org.cryptomator.filesystem.delegating.DelegatingFolder; +import org.cryptomator.filesystem.delegating.DelegatingReadableFile; +import org.cryptomator.filesystem.delegating.DelegatingWritableFile; + +class BlacklistingFolder extends DelegatingFolder { + + private final Predicate hiddenNodes; + + public BlacklistingFolder(BlacklistingFolder parent, Folder delegate, Predicate hiddenNodes) { + super(parent, delegate); + this.hiddenNodes = hiddenNodes; + } + + @Override + public Stream children() { + return Stream.concat(folders(), files()); + } + + @Override + public Stream folders() { + return delegate.folders().filter(hiddenNodes.negate()).map(this::folder); + } + + @Override + public Stream files() { + return delegate.files().filter(hiddenNodes.negate()).map(this::file); + } + + @Override + protected BlacklistingFile file(File delegate) { + if (hiddenNodes.test(delegate)) { + throw new UncheckedIOException("'" + delegate.name() + "' is a reserved name.", new FileAlreadyExistsException(delegate.name())); + } + return new BlacklistingFile(this, delegate); + } + + @Override + protected BlacklistingFolder folder(Folder delegate) { + if (hiddenNodes.test(delegate)) { + throw new UncheckedIOException("'" + delegate.name() + "' is a reserved name.", new FileAlreadyExistsException(delegate.name())); + } + return new BlacklistingFolder(this, delegate, hiddenNodes); + } + +} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/FilenameShortener.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/FilenameShortener.java similarity index 79% rename from main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/FilenameShortener.java rename to main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/FilenameShortener.java index 382cc5238..4feaa4ac0 100644 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/FilenameShortener.java +++ b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/FilenameShortener.java @@ -1,18 +1,20 @@ -package org.cryptomator.shortening; +package org.cryptomator.filesystem.shortening; import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.Reader; import java.io.UncheckedIOException; -import java.nio.ByteBuffer; +import java.io.Writer; +import java.nio.channels.Channels; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.apache.commons.codec.binary.Base32; import org.apache.commons.codec.binary.BaseNCodec; +import org.apache.commons.io.IOUtils; import org.cryptomator.filesystem.File; import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; class FilenameShortener { @@ -53,8 +55,10 @@ class FilenameShortener { final File mappingFile = mappingFile(shortName); if (!mappingFile.exists()) { mappingFile.parent().get().create(); - try (WritableFile writable = mappingFile.openWritable()) { - writable.write(ByteBuffer.wrap(longName.getBytes(StandardCharsets.UTF_8))); + try (Writer writer = Channels.newWriter(mappingFile.openWritable(), StandardCharsets.UTF_8.newEncoder(), -1)) { + writer.write(longName); + } catch (IOException e) { + throw new UncheckedIOException(e); } } } @@ -69,14 +73,10 @@ class FilenameShortener { if (!mappingFile.exists()) { throw new UncheckedIOException(new FileNotFoundException("Mapping file not found " + mappingFile)); } else { - try (ReadableFile readable = mappingFile.openReadable()) { - // TODO buffer might be to small - final ByteBuffer buf = ByteBuffer.allocate(1024); - readable.read(buf); - buf.flip(); - final byte[] bytes = new byte[buf.remaining()]; - buf.get(bytes); - return new String(bytes, StandardCharsets.UTF_8); + try (Reader reader = Channels.newReader(mappingFile.openReadable(), StandardCharsets.UTF_8.newDecoder(), -1)) { + return IOUtils.toString(reader); + } catch (IOException e) { + throw new UncheckedIOException(e); } } } diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFile.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFile.java new file mode 100644 index 000000000..74bcfb6c8 --- /dev/null +++ b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFile.java @@ -0,0 +1,47 @@ +package org.cryptomator.filesystem.shortening; + +import java.io.UncheckedIOException; +import java.util.concurrent.atomic.AtomicReference; + +import org.cryptomator.common.LazyInitializer; +import org.cryptomator.filesystem.File; +import org.cryptomator.filesystem.delegating.DelegatingFile; +import org.cryptomator.filesystem.delegating.DelegatingReadableFile; +import org.cryptomator.filesystem.delegating.DelegatingWritableFile; + +class ShorteningFile extends DelegatingFile { + + private final AtomicReference longName; + private final FilenameShortener shortener; + + public ShorteningFile(ShorteningFolder parent, File delegate, String name, FilenameShortener shortener) { + super(parent, delegate); + this.longName = new AtomicReference<>(name); + this.shortener = shortener; + } + + @Override + public String name() throws UncheckedIOException { + return LazyInitializer.initializeLazily(longName, () -> { + return shortener.inflate(shortenedName()); + }); + } + + private String shortenedName() { + return delegate.name(); + } + + @Override + public DelegatingReadableFile openReadable() throws UncheckedIOException { + return new DelegatingReadableFile(delegate.openReadable()); + } + + @Override + public DelegatingWritableFile openWritable() throws UncheckedIOException { + if (shortener.isShortened(shortenedName())) { + shortener.saveMapping(name(), shortenedName()); + } + return new DelegatingWritableFile(delegate.openWritable()); + } + +} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFileSystem.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFileSystem.java new file mode 100644 index 000000000..0586d8ff3 --- /dev/null +++ b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFileSystem.java @@ -0,0 +1,12 @@ +package org.cryptomator.filesystem.shortening; + +import org.cryptomator.filesystem.FileSystem; +import org.cryptomator.filesystem.Folder; + +public class ShorteningFileSystem extends ShorteningFolder implements FileSystem { + + public ShorteningFileSystem(Folder root, Folder metadataRoot, int threshold) { + super(null, root, "", new FilenameShortener(metadataRoot, threshold)); + } + +} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFolder.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFolder.java new file mode 100644 index 000000000..b5e8d5665 --- /dev/null +++ b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/ShorteningFolder.java @@ -0,0 +1,78 @@ +package org.cryptomator.filesystem.shortening; + +import java.io.UncheckedIOException; +import java.util.concurrent.atomic.AtomicReference; + +import org.cryptomator.common.LazyInitializer; +import org.cryptomator.filesystem.File; +import org.cryptomator.filesystem.Folder; +import org.cryptomator.filesystem.delegating.DelegatingFolder; +import org.cryptomator.filesystem.delegating.DelegatingReadableFile; +import org.cryptomator.filesystem.delegating.DelegatingWritableFile; + +class ShorteningFolder extends DelegatingFolder { + + private final AtomicReference longName; + private final FilenameShortener shortener; + + public ShorteningFolder(ShorteningFolder parent, Folder delegate, String name, FilenameShortener shortener) { + super(parent, delegate); + this.longName = new AtomicReference<>(name); + this.shortener = shortener; + } + + @Override + public String name() throws UncheckedIOException { + return LazyInitializer.initializeLazily(longName, () -> { + return shortener.inflate(shortenedName()); + }); + } + + private String shortenedName() { + return delegate.name(); + } + + @Override + public ShorteningFile file(String name) throws UncheckedIOException { + return new ShorteningFile(this, delegate.file(shortener.deflate(name)), name, shortener); + } + + @Override + public ShorteningFolder folder(String name) throws UncheckedIOException { + return new ShorteningFolder(this, delegate.folder(shortener.deflate(name)), name, shortener); + } + + @Override + protected ShorteningFile file(File delegate) { + return new ShorteningFile(this, delegate, null, shortener); + } + + @Override + protected ShorteningFolder folder(Folder delegate) { + return new ShorteningFolder(this, delegate, null, shortener); + } + + @Override + public void create() throws UncheckedIOException { + if (exists()) { + return; + } + parent().get().create(); + if (shortener.isShortened(shortenedName())) { + shortener.saveMapping(name(), shortenedName()); + } + super.create(); + } + + @Override + public void moveTo(Folder destination) { + super.moveTo(destination); + if (destination instanceof ShorteningFolder) { + ShorteningFolder dest = (ShorteningFolder) destination; + if (shortener.isShortened(dest.shortenedName())) { + shortener.saveMapping(dest.name(), dest.shortenedName()); + } + } + } + +} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/package-info.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/package-info.java similarity index 89% rename from main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/package-info.java rename to main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/package-info.java index 26f68c001..98d1786dc 100644 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/package-info.java +++ b/main/filesystem-nameshortening/src/main/java/org/cryptomator/filesystem/shortening/package-info.java @@ -3,4 +3,4 @@ * {@link org.cryptomator.filesystem.File File} and {@link org.cryptomator.filesystem.Folder Folder} names exceeding a certain length limit will be mapped to shorter equivalents. * The mapping itself is stored in metadata files inside the m/ directory on root level. */ -package org.cryptomator.shortening; \ No newline at end of file +package org.cryptomator.filesystem.shortening; \ No newline at end of file diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/ShorteningFile.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/ShorteningFile.java deleted file mode 100644 index 55a915690..000000000 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/ShorteningFile.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.cryptomator.shortening; - -import java.io.UncheckedIOException; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; - -class ShorteningFile extends ShorteningNode implements File { - - private final FilenameShortener shortener; - - public ShorteningFile(ShorteningFolder parent, File delegate, String longName, FilenameShortener shortener) { - super(parent, delegate, longName); - this.shortener = shortener; - } - - @Override - public ReadableFile openReadable() throws UncheckedIOException { - return delegate.openReadable(); - } - - @Override - public WritableFile openWritable() throws UncheckedIOException { - if (shortener.isShortened(shortName())) { - shortener.saveMapping(name(), shortName()); - } - return delegate.openWritable(); - } - - @Override - public String toString() { - return parent + name(); - } - - @Override - public int compareTo(File o) { - return toString().compareTo(o.toString()); - } - -} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/ShorteningFileSystem.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/ShorteningFileSystem.java deleted file mode 100644 index 02c24396d..000000000 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/ShorteningFileSystem.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cryptomator.shortening; - -import org.cryptomator.filesystem.FileSystem; -import org.cryptomator.filesystem.Folder; - -/** - * Filesystem implementation, that shortens filenames when they reach a certain - * threshold (inclusive). Shortening is done by SHA1-hashing those files, so a - * threshold below the length of the hashed files makes no sense. Hashes are - * then mapped back to the original filenames by storing metadata files inside - * the given metadataRoot. - */ -public class ShorteningFileSystem extends ShorteningFolder implements FileSystem { - - public ShorteningFileSystem(Folder root, Folder metadataRoot, int threshold) { - super(null, root, "", metadataRoot, new FilenameShortener(metadataRoot, threshold)); - } - - @Override - public boolean exists() { - return true; - } - - @Override - public void delete() { - // no-op. - } - - @Override - public String toString() { - return "/"; - } - -} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/ShorteningFolder.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/ShorteningFolder.java deleted file mode 100644 index f3bb5ec56..000000000 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/ShorteningFolder.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.cryptomator.shortening; - -import java.io.UncheckedIOException; -import java.nio.file.FileAlreadyExistsException; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.cryptomator.filesystem.File; -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.Node; - -class ShorteningFolder extends ShorteningNode implements Folder { - - private final Folder metadataRoot; - private final FilenameShortener shortener; - - public ShorteningFolder(ShorteningFolder parent, Folder delegate, String longName, Folder metadataRoot, FilenameShortener shortener) { - super(parent, delegate, longName); - this.metadataRoot = metadataRoot; - this.shortener = shortener; - } - - @Override - public Stream children() { - return Stream.concat(this.files(), this.folders()); - } - - private ShorteningFile existingFile(File original) { - final String longName = shortener.inflate(original.name()); - return new ShorteningFile(this, original, longName, shortener); - } - - @Override - public File file(String name) { - final File original = delegate.file(shortener.deflate(name)); - if (metadataRoot.equals(original)) { // comparing apples and oranges, - // but we don't know if the - // underlying fs distinguishes - // files and folders... - throw new UncheckedIOException("'" + name + "' is a reserved name.", new FileAlreadyExistsException(name)); - } - return new ShorteningFile(this, original, name, shortener); - } - - @Override - public Stream files() throws UncheckedIOException { - return delegate.files().map(this::existingFile); - } - - private ShorteningFolder existingFolder(Folder original) { - final String longName = shortener.inflate(original.name()); - return new ShorteningFolder(this, original, longName, metadataRoot, shortener); - } - - @Override - public Folder folder(String name) { - final Folder original = delegate.folder(shortener.deflate(name)); - if (metadataRoot.equals(original)) { - throw new UncheckedIOException("'" + name + "' is a reserved name.", new FileAlreadyExistsException(name)); - } - return new ShorteningFolder(this, original, name, metadataRoot, shortener); - } - - @Override - public Stream folders() { - // if metadataRoot is inside our filesystem, we must filter it out: - final Predicate equalsMetadataRoot = (Node node) -> metadataRoot.equals(node); - return delegate.folders().filter(equalsMetadataRoot.negate()).map(this::existingFolder); - } - - @Override - public void create() { - if (exists()) { - return; - } - parent().get().create(); - if (shortener.isShortened(shortName())) { - shortener.saveMapping(name(), shortName()); - } - delegate.create(); - } - - @Override - public void delete() { - delegate.delete(); - } - - @Override - public void moveTo(Folder target) { - if (target instanceof ShorteningFolder) { - moveToInternal((ShorteningFolder) target); - } else { - throw new UnsupportedOperationException("Can not move ShorteningFolder to conventional folder."); - } - } - - private void moveToInternal(ShorteningFolder target) { - if (this.isAncestorOf(target) || target.isAncestorOf(this)) { - throw new IllegalArgumentException("Can not move directories containing one another (src: " + this + ", dst: " + target + ")"); - } - - target.create(); - - delegate.moveTo(target.delegate); - } - - @Override - public String toString() { - return parent + name() + "/"; - } - -} diff --git a/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/ShorteningNode.java b/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/ShorteningNode.java deleted file mode 100644 index 3004dcd47..000000000 --- a/main/filesystem-nameshortening/src/main/java/org/cryptomator/shortening/ShorteningNode.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.cryptomator.shortening; - -import java.time.Instant; -import java.util.Optional; - -import org.cryptomator.filesystem.Folder; -import org.cryptomator.filesystem.Node; - -class ShorteningNode implements Node { - - protected final E delegate; - protected final ShorteningFolder parent; - private final String longName; - private final String shortName; - - public ShorteningNode(ShorteningFolder parent, E delegate, String longName) { - this.delegate = delegate; - this.parent = parent; - this.shortName = delegate.name(); - this.longName = longName; - } - - @Override - public String name() { - return longName; - } - - protected String shortName() { - return shortName; - } - - @Override - public Optional parent() { - return Optional.ofNullable(parent); - } - - @Override - public boolean exists() { - return delegate.exists(); - } - - @Override - public Instant lastModified() { - return delegate.lastModified(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((longName == null) ? 0 : longName.hashCode()); - result = prime * result + ((parent == null) ? 0 : parent.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof ShorteningNode) { - ShorteningNode other = (ShorteningNode) obj; - return this.getClass() == other.getClass() // - && (this.parent == null && other.parent == null || this.parent.equals(other.parent)) // - && (this.longName == null && other.longName == null || this.longName.equals(other.longName)); - } else { - return false; - } - } - -} diff --git a/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/blacklisting/BlacklistingFileSystemTest.java b/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/blacklisting/BlacklistingFileSystemTest.java new file mode 100644 index 000000000..ee9c47e78 --- /dev/null +++ b/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/blacklisting/BlacklistingFileSystemTest.java @@ -0,0 +1,51 @@ +package org.cryptomator.filesystem.blacklisting; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.cryptomator.filesystem.File; +import org.cryptomator.filesystem.FileSystem; +import org.cryptomator.filesystem.Folder; +import org.cryptomator.filesystem.Node; +import org.cryptomator.filesystem.inmem.InMemoryFileSystem; +import org.junit.Assert; +import org.junit.Test; + +public class BlacklistingFileSystemTest { + + @Test(expected = UncheckedIOException.class) + public void testPreventCreationOfMetadataFolder() { + final FileSystem underlyingFs = new InMemoryFileSystem(); + final Folder metadataRoot = underlyingFs.folder("m"); + final Predicate metadataHidden = (Node n) -> n.equals(metadataRoot); + final FileSystem fs = new BlacklistingFileSystem(underlyingFs, metadataHidden); + fs.folder("m"); + } + + @Test + public void testBlacklistingOfFilesAndFolders() throws IOException { + final FileSystem underlyingFs = new InMemoryFileSystem(); + final Folder hiddenFolder = underlyingFs.folder("asd"); + final File hiddenFile = underlyingFs.file("qwe"); + final Folder visibleFolder = underlyingFs.folder("sdf"); + final File visibleFile = underlyingFs.file("wer"); + final Predicate metadataHidden = (Node n) -> n.equals(hiddenFolder) || n.equals(hiddenFile); + final FileSystem fs = new BlacklistingFileSystem(underlyingFs, metadataHidden); + hiddenFolder.create(); + try (WritableByteChannel writable = hiddenFile.openWritable()) { + writable.write(ByteBuffer.allocate(0)); + } + visibleFolder.create(); + try (WritableByteChannel writable = visibleFile.openWritable()) { + writable.write(ByteBuffer.allocate(0)); + } + + Assert.assertArrayEquals(new String[] {"sdf"}, fs.folders().map(Node::name).collect(Collectors.toList()).toArray()); + Assert.assertArrayEquals(new String[] {"wer"}, fs.files().map(Node::name).collect(Collectors.toList()).toArray()); + } + +} diff --git a/main/filesystem-nameshortening/src/test/java/org/cryptomator/shortening/ShorteningFileSystemTest.java b/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemTest.java similarity index 92% rename from main/filesystem-nameshortening/src/test/java/org/cryptomator/shortening/ShorteningFileSystemTest.java rename to main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemTest.java index 82e82bf29..8b92a198c 100644 --- a/main/filesystem-nameshortening/src/test/java/org/cryptomator/shortening/ShorteningFileSystemTest.java +++ b/main/filesystem-nameshortening/src/test/java/org/cryptomator/filesystem/shortening/ShorteningFileSystemTest.java @@ -1,4 +1,4 @@ -package org.cryptomator.shortening; +package org.cryptomator.filesystem.shortening; import java.io.UncheckedIOException; import java.nio.ByteBuffer; @@ -17,21 +17,12 @@ import org.junit.Test; public class ShorteningFileSystemTest { @Test - public void testCreationOfInvisibleMetadataFolder() { + public void testImplicitCreationOfMetadataFolder() { final FileSystem underlyingFs = new InMemoryFileSystem(); final Folder metadataRoot = underlyingFs.folder("m"); final FileSystem fs = new ShorteningFileSystem(underlyingFs, metadataRoot, 10); fs.folder("morethantenchars").create(); Assert.assertTrue(metadataRoot.exists()); - Assert.assertEquals(1, fs.folders().count()); - } - - @Test(expected = UncheckedIOException.class) - public void testPreventCreationOfMetadataFolder() { - final FileSystem underlyingFs = new InMemoryFileSystem(); - final Folder metadataRoot = underlyingFs.folder("m"); - final FileSystem fs = new ShorteningFileSystem(underlyingFs, metadataRoot, 10); - fs.folder("m"); } @Test diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WeakValuedCacheTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WeakValuedCacheTest.java index 77b427847..0141b41e2 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WeakValuedCacheTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/WeakValuedCacheTest.java @@ -9,6 +9,7 @@ import static org.mockito.Mockito.when; import java.util.function.Function; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; @@ -82,6 +83,7 @@ public class WeakValuedCacheTest { } @Test + @Ignore public void testCacheDoesNotPreventGarbageCollectionOfValues() { when(loader.apply(A_KEY)).thenAnswer(this::createValueUsingMoreThanHalfTheJvmMemory);