From 630257444dd3af6a6fdf26f254b40dbef2bf7a5f Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 16 Sep 2024 15:24:11 +0200 Subject: [PATCH 01/17] bump version to snapshot [ci skip] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a0fc375d..53f8bb76 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.cryptomator cryptofs - 2.7.0 + 2.8.0-SNAPSHOT Cryptomator Crypto Filesystem This library provides the Java filesystem provider used by Cryptomator. https://github.com/cryptomator/cryptofs From 84e7167af9f588b3f8f5075ef1762de1c786a5ad Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Fri, 11 Oct 2024 23:52:54 +0200 Subject: [PATCH 02/17] invalidate/move also all cleartext path cache entries starting with the base invalidated path --- .../cryptofs/CryptoPathMapper.java | 18 ++- .../cryptofs/CryptoPathMapperTest.java | 110 ++++++++++++++++++ 2 files changed, 123 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java index c725611e..0dc4f940 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java @@ -28,6 +28,8 @@ import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.time.Duration; +import java.util.ArrayList; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -140,14 +142,20 @@ private String getCiphertextFileName(DirIdAndName dirIdAndName) { } public void invalidatePathMapping(CryptoPath cleartextPath) { - ciphertextDirectories.asMap().remove(cleartextPath); + ciphertextDirectories.asMap().keySet().removeIf(p -> p.startsWith(cleartextPath)); } public void movePathMapping(CryptoPath cleartextSrc, CryptoPath cleartextDst) { - var cachedValue = ciphertextDirectories.asMap().remove(cleartextSrc); - if (cachedValue != null) { - ciphertextDirectories.put(cleartextDst, cachedValue); - } + var remappedEntries = new ArrayList>>(); + ciphertextDirectories.asMap().entrySet().removeIf(e -> { + if (e.getKey().startsWith(cleartextSrc)) { + var remappedPath = cleartextDst.resolve(cleartextSrc.relativize(e.getKey())); + return remappedEntries.add(Map.entry(remappedPath, e.getValue())); + } else { + return false; + } + }); + remappedEntries.forEach(e -> ciphertextDirectories.put(e.getKey(), e.getValue())); } public CiphertextDirectory getCiphertextDir(CryptoPath cleartextPath) throws IOException { diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java index 32241fb8..0ff1536f 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java @@ -64,6 +64,116 @@ public void setup() { Mockito.when(fileSystem.getEmptyPath()).thenReturn(empty); } + @Test + @DisplayName("Removing a cached cleartext path also removes all cached child paths") + public void testInvalidatingCleartextPathCleansCacheFromChildPaths() throws IOException { + //prepare root + Path d00 = Mockito.mock(Path.class); + Mockito.when(dataRoot.resolve("00")).thenReturn(d00); + Mockito.when(fileNameCryptor.hashDirectoryId("")).thenReturn("0000"); + + //prepare cleartextDir "/foo" + Path d0000 = Mockito.mock(Path.class, "d/00/00"); + Path d0000oof = Mockito.mock(Path.class, "d/00/00/oof.c9r"); + Path d0000oofdir = Mockito.mock(Path.class, "d/00/00/oof.c9r/dir.c9r"); + Mockito.when(d00.resolve("00")).thenReturn(d0000); + Mockito.when(d0000.resolve("oof.c9r")).thenReturn(d0000oof); + Mockito.when(d0000oof.resolve("dir.c9r")).thenReturn(d0000oofdir); + Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("foo"), Mockito.any())).thenReturn("oof"); + Mockito.when(dirIdProvider.load(d0000oofdir)).thenReturn("1"); + Mockito.when(fileNameCryptor.hashDirectoryId("1")).thenReturn("0001"); + + //prepare cleartextDir "/foo/bar" + Path d0001 = Mockito.mock(Path.class, "d/00/01"); + Path d0001rab = Mockito.mock(Path.class, "d/00/01/rab.c9r"); + Path d0000rabdir = Mockito.mock(Path.class, "d/00/00/rab.c9r/dir.c9r"); + Mockito.when(d00.resolve("01")).thenReturn(d0001); + Mockito.when(d0001.resolve("rab.c9r")).thenReturn(d0001rab); + Mockito.when(d0001rab.resolve("dir.c9r")).thenReturn(d0000rabdir); + Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("bar"), Mockito.any())).thenReturn("rab"); + Mockito.when(dirIdProvider.load(d0000rabdir)).thenReturn("2"); + Mockito.when(fileNameCryptor.hashDirectoryId("2")).thenReturn("0002"); + + Path d0002 = Mockito.mock(Path.class); + Mockito.when(d00.resolve("02")).thenReturn(d0002); + + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); + //put cleartextpath /foo + Path cipherFooPath = mapper.getCiphertextDir(fileSystem.getPath("/foo")).path; + //put cleartextpath /foo/bar + Path cipherFooBarPath = mapper.getCiphertextDir(fileSystem.getPath("/foo/bar")).path; + //invalidate /foo + mapper.invalidatePathMapping(fileSystem.getPath("/foo")); + //cache should miss + var mapperSpy = Mockito.spy(mapper); + mapperSpy.getCiphertextDir(fileSystem.getPath("/foo/bar")); + Mockito.verify(mapperSpy, Mockito.atLeast(1)).getCiphertextFilePath(Mockito.any()); + } + + @Test + @DisplayName("Moving a cached cleartext path also remaps all cached child paths") + public void testMovingCleartextPathRemapsCachedChildPaths() throws IOException { + CryptoPath fooPath = fileSystem.getPath("/foo"); + CryptoPath fooBarPath = fileSystem.getPath("/foo/bar"); + CryptoPath unkelFooPath = fileSystem.getPath("/unkel/foo"); + CryptoPath unkelFooBarPath = fileSystem.getPath("/unkel/foo/bar"); + //prepare root + Path d00 = Mockito.mock(Path.class); + Mockito.when(dataRoot.resolve("00")).thenReturn(d00); + Mockito.when(fileNameCryptor.hashDirectoryId("")).thenReturn("0000"); + + //prepare cleartextDir "/foo" + Path d0000 = Mockito.mock(Path.class, "d/00/00"); + Path d0000oof = Mockito.mock(Path.class, "d/00/00/oof.c9r"); + Path d0000oofdir = Mockito.mock(Path.class, "d/00/00/oof.c9r/dir.c9r"); + Mockito.when(d00.resolve("00")).thenReturn(d0000); + Mockito.when(d0000.resolve("oof.c9r")).thenReturn(d0000oof); + Mockito.when(d0000oof.resolve("dir.c9r")).thenReturn(d0000oofdir); + Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("foo"), Mockito.any())).thenReturn("oof"); + Mockito.when(dirIdProvider.load(d0000oofdir)).thenReturn("1"); + Mockito.when(fileNameCryptor.hashDirectoryId("1")).thenReturn("0001"); + + //prepare cleartextDir "/foo/bar" + Path d0001 = Mockito.mock(Path.class, "d/00/01"); + Path d0001rab = Mockito.mock(Path.class, "d/00/01/rab.c9r"); + Path d0000rabdir = Mockito.mock(Path.class, "d/00/00/rab.c9r/dir.c9r"); + Mockito.when(d00.resolve("01")).thenReturn(d0001); + Mockito.when(d0001.resolve("rab.c9r")).thenReturn(d0001rab); + Mockito.when(d0001rab.resolve("dir.c9r")).thenReturn(d0000rabdir); + Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("bar"), Mockito.any())).thenReturn("rab"); + Mockito.when(dirIdProvider.load(d0000rabdir)).thenReturn("2"); + Mockito.when(fileNameCryptor.hashDirectoryId("2")).thenReturn("0002"); + + Path d0002 = Mockito.mock(Path.class); + Mockito.when(d00.resolve("02")).thenReturn(d0002); + + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); + //put cleartextpath /foo in cache + Path cipherFooPath = mapper.getCiphertextDir(fooPath).path; + //put cleartextpath /foo/bar in cache + Path cipherBarPath = mapper.getCiphertextDir(fooBarPath).path; + //move /foo to /unkel/dinkel/foo/, effectively moving also moving /foo/bar + mapper.movePathMapping(fooPath, unkelFooPath); + + //cache should ... + var mapperSpy = Mockito.spy(mapper); + var someCiphertextFilePath = Mockito.mock(CiphertextFilePath.class); + var someCiphertextDirFilePath = Mockito.mock(Path.class); + var someCipherDirObj = Mockito.mock(CryptoPathMapper.CiphertextDirectory.class); + Mockito.doReturn(someCiphertextFilePath).when(mapperSpy).getCiphertextFilePath(fooBarPath); + Mockito.doReturn(someCiphertextDirFilePath).when(someCiphertextFilePath).getDirFilePath(); + Mockito.doReturn(someCipherDirObj).when(mapperSpy).resolveDirectory(someCiphertextDirFilePath); + + //... succeed for /unkel/foo/ and /unkel/foo/bar + mapperSpy.getCiphertextDir(unkelFooPath); + mapperSpy.getCiphertextDir(unkelFooBarPath); + Mockito.verify(mapperSpy, Mockito.never()).getCiphertextFilePath(Mockito.any()); + + //...miss and return our mocked cipherDirObj + var actualCipherDirObj = mapperSpy.getCiphertextDir(fooBarPath); + Assertions.assertEquals(someCipherDirObj, actualCipherDirObj); + } + @Test public void testPathEncryptionForRoot() throws IOException { Path d00 = Mockito.mock(Path.class); From 35157945c93c5f471294fec422540fb1e27f6f71 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Sun, 13 Oct 2024 00:24:20 +0200 Subject: [PATCH 03/17] refactor dir cache to own inner static class --- .../cryptofs/CryptoPathMapper.java | 88 +++++++++----- .../cryptofs/CryptoPathMapperTest.java | 109 ------------------ 2 files changed, 62 insertions(+), 135 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java index 0dc4f940..2b03dd5d 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java @@ -41,8 +41,6 @@ public class CryptoPathMapper { private static final Logger LOG = LoggerFactory.getLogger(CryptoPathMapper.class); private static final int MAX_CACHED_CIPHERTEXT_NAMES = 5000; - private static final int MAX_CACHED_DIR_PATHS = 5000; - private static final Duration MAX_CACHE_AGE = Duration.ofSeconds(20); private final Cryptor cryptor; private final Path dataRoot; @@ -50,7 +48,7 @@ public class CryptoPathMapper { private final LongFileNameProvider longFileNameProvider; private final VaultConfig vaultConfig; private final LoadingCache ciphertextNames; - private final AsyncCache ciphertextDirectories; + private final ClearToCipherDirCache clearToCipherDirCache; private final CiphertextDirectory rootDirectory; @@ -62,7 +60,7 @@ public class CryptoPathMapper { this.longFileNameProvider = longFileNameProvider; this.vaultConfig = vaultConfig; this.ciphertextNames = Caffeine.newBuilder().maximumSize(MAX_CACHED_CIPHERTEXT_NAMES).build(this::getCiphertextFileName); - this.ciphertextDirectories = Caffeine.newBuilder().maximumSize(MAX_CACHED_DIR_PATHS).expireAfterWrite(MAX_CACHE_AGE).buildAsync(); + this.clearToCipherDirCache = new ClearToCipherDirCache(); this.rootDirectory = resolveDirectory(Constants.ROOT_DIR_ID); } @@ -141,39 +139,31 @@ private String getCiphertextFileName(DirIdAndName dirIdAndName) { return cryptor.fileNameCryptor().encryptFilename(BaseEncoding.base64Url(), dirIdAndName.name, dirIdAndName.dirId.getBytes(StandardCharsets.UTF_8)) + Constants.CRYPTOMATOR_FILE_SUFFIX; } + /** + * TODO: doc doc doc + * @param cleartextPath + */ public void invalidatePathMapping(CryptoPath cleartextPath) { - ciphertextDirectories.asMap().keySet().removeIf(p -> p.startsWith(cleartextPath)); + clearToCipherDirCache.removeAllKeysWithPrefix(cleartextPath); } + /** + * TODO: doc doc doc + */ public void movePathMapping(CryptoPath cleartextSrc, CryptoPath cleartextDst) { - var remappedEntries = new ArrayList>>(); - ciphertextDirectories.asMap().entrySet().removeIf(e -> { - if (e.getKey().startsWith(cleartextSrc)) { - var remappedPath = cleartextDst.resolve(cleartextSrc.relativize(e.getKey())); - return remappedEntries.add(Map.entry(remappedPath, e.getValue())); - } else { - return false; - } - }); - remappedEntries.forEach(e -> ciphertextDirectories.put(e.getKey(), e.getValue())); + clearToCipherDirCache.recomputeAllKeysWithPrefix(cleartextSrc, cleartextDst); } public CiphertextDirectory getCiphertextDir(CryptoPath cleartextPath) throws IOException { assert cleartextPath.isAbsolute(); - CryptoPath parentPath = cleartextPath.getParent(); - if (parentPath == null) { + if (cleartextPath.getParent() == null) { return rootDirectory; } else { - var lazyEntry = new CompletableFuture(); - var priorEntry = ciphertextDirectories.asMap().putIfAbsent(cleartextPath, lazyEntry); - if (priorEntry != null) { - return priorEntry.join(); - } else { + CipherDirLoader cipherDirLoaderIfAbsent = () -> { Path dirFile = getCiphertextFilePath(cleartextPath).getDirFilePath(); - CiphertextDirectory cipherDir = resolveDirectory(dirFile); - lazyEntry.complete(cipherDir); - return cipherDir; - } + return resolveDirectory(dirFile); + }; + return clearToCipherDirCache.putIfAbsent(cleartextPath, cipherDirLoaderIfAbsent); } } @@ -240,4 +230,50 @@ public boolean equals(Object obj) { } } + static class ClearToCipherDirCache { + + private static final int MAX_CACHED_DIR_PATHS = 5000; + private static final Duration MAX_CACHE_AGE = Duration.ofSeconds(20); + + private final AsyncCache ciphertextDirectories = Caffeine.newBuilder() // + .maximumSize(MAX_CACHED_DIR_PATHS) // + .expireAfterWrite(MAX_CACHE_AGE) // + .buildAsync(); + + void removeAllKeysWithPrefix(CryptoPath basePrefix) { + ciphertextDirectories.asMap().keySet().removeIf(p -> p.startsWith(basePrefix)); + } + + void recomputeAllKeysWithPrefix(CryptoPath oldPrefix, CryptoPath newPrefix) { + var remappedEntries = new ArrayList>>(); + ciphertextDirectories.asMap().entrySet().removeIf(e -> { + if (e.getKey().startsWith(oldPrefix)) { + var remappedPath = newPrefix.resolve(oldPrefix.relativize(e.getKey())); + return remappedEntries.add(Map.entry(remappedPath, e.getValue())); + } else { + return false; + } + }); + remappedEntries.forEach(e -> ciphertextDirectories.put(e.getKey(), e.getValue())); + } + + CiphertextDirectory putIfAbsent(CryptoPath cleartextPath, CipherDirLoader ifAbsent) throws IOException { + var futureMapping = new CompletableFuture(); + var currentMapping = ciphertextDirectories.asMap().putIfAbsent(cleartextPath, futureMapping); + if (currentMapping != null) { + return currentMapping.join(); + } else { + futureMapping.complete(ifAbsent.load()); + return futureMapping.join(); + } + } + + } + + @FunctionalInterface + interface CipherDirLoader { + + CiphertextDirectory load() throws IOException; + } + } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java index 0ff1536f..f218691d 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java @@ -64,115 +64,6 @@ public void setup() { Mockito.when(fileSystem.getEmptyPath()).thenReturn(empty); } - @Test - @DisplayName("Removing a cached cleartext path also removes all cached child paths") - public void testInvalidatingCleartextPathCleansCacheFromChildPaths() throws IOException { - //prepare root - Path d00 = Mockito.mock(Path.class); - Mockito.when(dataRoot.resolve("00")).thenReturn(d00); - Mockito.when(fileNameCryptor.hashDirectoryId("")).thenReturn("0000"); - - //prepare cleartextDir "/foo" - Path d0000 = Mockito.mock(Path.class, "d/00/00"); - Path d0000oof = Mockito.mock(Path.class, "d/00/00/oof.c9r"); - Path d0000oofdir = Mockito.mock(Path.class, "d/00/00/oof.c9r/dir.c9r"); - Mockito.when(d00.resolve("00")).thenReturn(d0000); - Mockito.when(d0000.resolve("oof.c9r")).thenReturn(d0000oof); - Mockito.when(d0000oof.resolve("dir.c9r")).thenReturn(d0000oofdir); - Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("foo"), Mockito.any())).thenReturn("oof"); - Mockito.when(dirIdProvider.load(d0000oofdir)).thenReturn("1"); - Mockito.when(fileNameCryptor.hashDirectoryId("1")).thenReturn("0001"); - - //prepare cleartextDir "/foo/bar" - Path d0001 = Mockito.mock(Path.class, "d/00/01"); - Path d0001rab = Mockito.mock(Path.class, "d/00/01/rab.c9r"); - Path d0000rabdir = Mockito.mock(Path.class, "d/00/00/rab.c9r/dir.c9r"); - Mockito.when(d00.resolve("01")).thenReturn(d0001); - Mockito.when(d0001.resolve("rab.c9r")).thenReturn(d0001rab); - Mockito.when(d0001rab.resolve("dir.c9r")).thenReturn(d0000rabdir); - Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("bar"), Mockito.any())).thenReturn("rab"); - Mockito.when(dirIdProvider.load(d0000rabdir)).thenReturn("2"); - Mockito.when(fileNameCryptor.hashDirectoryId("2")).thenReturn("0002"); - - Path d0002 = Mockito.mock(Path.class); - Mockito.when(d00.resolve("02")).thenReturn(d0002); - - CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); - //put cleartextpath /foo - Path cipherFooPath = mapper.getCiphertextDir(fileSystem.getPath("/foo")).path; - //put cleartextpath /foo/bar - Path cipherFooBarPath = mapper.getCiphertextDir(fileSystem.getPath("/foo/bar")).path; - //invalidate /foo - mapper.invalidatePathMapping(fileSystem.getPath("/foo")); - //cache should miss - var mapperSpy = Mockito.spy(mapper); - mapperSpy.getCiphertextDir(fileSystem.getPath("/foo/bar")); - Mockito.verify(mapperSpy, Mockito.atLeast(1)).getCiphertextFilePath(Mockito.any()); - } - - @Test - @DisplayName("Moving a cached cleartext path also remaps all cached child paths") - public void testMovingCleartextPathRemapsCachedChildPaths() throws IOException { - CryptoPath fooPath = fileSystem.getPath("/foo"); - CryptoPath fooBarPath = fileSystem.getPath("/foo/bar"); - CryptoPath unkelFooPath = fileSystem.getPath("/unkel/foo"); - CryptoPath unkelFooBarPath = fileSystem.getPath("/unkel/foo/bar"); - //prepare root - Path d00 = Mockito.mock(Path.class); - Mockito.when(dataRoot.resolve("00")).thenReturn(d00); - Mockito.when(fileNameCryptor.hashDirectoryId("")).thenReturn("0000"); - - //prepare cleartextDir "/foo" - Path d0000 = Mockito.mock(Path.class, "d/00/00"); - Path d0000oof = Mockito.mock(Path.class, "d/00/00/oof.c9r"); - Path d0000oofdir = Mockito.mock(Path.class, "d/00/00/oof.c9r/dir.c9r"); - Mockito.when(d00.resolve("00")).thenReturn(d0000); - Mockito.when(d0000.resolve("oof.c9r")).thenReturn(d0000oof); - Mockito.when(d0000oof.resolve("dir.c9r")).thenReturn(d0000oofdir); - Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("foo"), Mockito.any())).thenReturn("oof"); - Mockito.when(dirIdProvider.load(d0000oofdir)).thenReturn("1"); - Mockito.when(fileNameCryptor.hashDirectoryId("1")).thenReturn("0001"); - - //prepare cleartextDir "/foo/bar" - Path d0001 = Mockito.mock(Path.class, "d/00/01"); - Path d0001rab = Mockito.mock(Path.class, "d/00/01/rab.c9r"); - Path d0000rabdir = Mockito.mock(Path.class, "d/00/00/rab.c9r/dir.c9r"); - Mockito.when(d00.resolve("01")).thenReturn(d0001); - Mockito.when(d0001.resolve("rab.c9r")).thenReturn(d0001rab); - Mockito.when(d0001rab.resolve("dir.c9r")).thenReturn(d0000rabdir); - Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("bar"), Mockito.any())).thenReturn("rab"); - Mockito.when(dirIdProvider.load(d0000rabdir)).thenReturn("2"); - Mockito.when(fileNameCryptor.hashDirectoryId("2")).thenReturn("0002"); - - Path d0002 = Mockito.mock(Path.class); - Mockito.when(d00.resolve("02")).thenReturn(d0002); - - CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); - //put cleartextpath /foo in cache - Path cipherFooPath = mapper.getCiphertextDir(fooPath).path; - //put cleartextpath /foo/bar in cache - Path cipherBarPath = mapper.getCiphertextDir(fooBarPath).path; - //move /foo to /unkel/dinkel/foo/, effectively moving also moving /foo/bar - mapper.movePathMapping(fooPath, unkelFooPath); - - //cache should ... - var mapperSpy = Mockito.spy(mapper); - var someCiphertextFilePath = Mockito.mock(CiphertextFilePath.class); - var someCiphertextDirFilePath = Mockito.mock(Path.class); - var someCipherDirObj = Mockito.mock(CryptoPathMapper.CiphertextDirectory.class); - Mockito.doReturn(someCiphertextFilePath).when(mapperSpy).getCiphertextFilePath(fooBarPath); - Mockito.doReturn(someCiphertextDirFilePath).when(someCiphertextFilePath).getDirFilePath(); - Mockito.doReturn(someCipherDirObj).when(mapperSpy).resolveDirectory(someCiphertextDirFilePath); - - //... succeed for /unkel/foo/ and /unkel/foo/bar - mapperSpy.getCiphertextDir(unkelFooPath); - mapperSpy.getCiphertextDir(unkelFooBarPath); - Mockito.verify(mapperSpy, Mockito.never()).getCiphertextFilePath(Mockito.any()); - - //...miss and return our mocked cipherDirObj - var actualCipherDirObj = mapperSpy.getCiphertextDir(fooBarPath); - Assertions.assertEquals(someCipherDirObj, actualCipherDirObj); - } @Test public void testPathEncryptionForRoot() throws IOException { From eb064898fda56c7cb3f066b03d931ba8d2164a29 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 14 Oct 2024 12:22:05 +0200 Subject: [PATCH 04/17] extend refactoring by moving inner CryptoPathMapper classes to own class files --- .../org/cryptomator/cryptofs/CipherDir.java | 14 ++ .../cryptofs/CipherNodeNameParameters.java | 13 ++ .../cryptofs/ClearToCipherDirCache.java | 64 +++++++++ .../cryptofs/CryptoFileSystemImpl.java | 21 ++- .../cryptofs/CryptoFileSystemProvider.java | 2 +- .../cryptofs/CryptoPathMapper.java | 132 ++---------------- .../cryptofs/DirectoryIdBackup.java | 14 +- .../attr/AbstractCryptoFileAttributeView.java | 2 +- .../cryptofs/attr/AttributeProvider.java | 2 +- .../cryptofs/dir/BrokenDirectoryFilter.java | 2 +- .../cryptofs/dir/DirectoryStreamFactory.java | 8 +- .../health/dirid/MissingContentDir.java | 3 +- .../health/dirid/MissingDirIdBackup.java | 3 +- .../health/dirid/OrphanContentDir.java | 15 +- .../cryptofs/CryptoFileSystemImplTest.java | 39 +++--- .../cryptofs/CryptoPathMapperTest.java | 6 +- .../cryptofs/DirectoryIdBackupTest.java | 4 +- .../cryptofs/attr/AttributeProviderTest.java | 4 +- .../dir/BrokenDirectoryFilterTest.java | 5 +- .../dir/DirectoryStreamFactoryTest.java | 8 +- .../health/dirid/MissingContentDirTest.java | 3 +- .../health/dirid/MissingDirIdBackupTest.java | 3 +- .../cryptofs/health/dirid/OrphanDirTest.java | 37 ++--- 23 files changed, 197 insertions(+), 207 deletions(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/CipherDir.java create mode 100644 src/main/java/org/cryptomator/cryptofs/CipherNodeNameParameters.java create mode 100644 src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java diff --git a/src/main/java/org/cryptomator/cryptofs/CipherDir.java b/src/main/java/org/cryptomator/cryptofs/CipherDir.java new file mode 100644 index 00000000..09387953 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/CipherDir.java @@ -0,0 +1,14 @@ +package org.cryptomator.cryptofs; + +import java.nio.file.Path; +import java.util.Objects; + +//own file due to dagger +public record CipherDir(String dirId, Path contentDirPath) { + + public CipherDir(String dirId, Path contentDirPath) { + this.dirId = Objects.requireNonNull(dirId); + this.contentDirPath = Objects.requireNonNull(contentDirPath); + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/CipherNodeNameParameters.java b/src/main/java/org/cryptomator/cryptofs/CipherNodeNameParameters.java new file mode 100644 index 00000000..e27afb45 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/CipherNodeNameParameters.java @@ -0,0 +1,13 @@ +package org.cryptomator.cryptofs; + +import java.util.Objects; + +//own file due to dagger +public record CipherNodeNameParameters(String dirId, String clearNodeName) { + + public CipherNodeNameParameters(String dirId, String clearNodeName) { + this.dirId = Objects.requireNonNull(dirId); + this.clearNodeName = Objects.requireNonNull(clearNodeName); + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java b/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java new file mode 100644 index 00000000..f0bfac82 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java @@ -0,0 +1,64 @@ +package org.cryptomator.cryptofs; + +import com.github.benmanes.caffeine.cache.AsyncCache; +import com.github.benmanes.caffeine.cache.Caffeine; + +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class ClearToCipherDirCache { + + private static final int MAX_CACHED_PATHS = 5000; + private static final Duration MAX_CACHE_AGE = Duration.ofSeconds(20); + + //TODO: not testable! + private final AsyncCache ciphertextDirectories = Caffeine.newBuilder() // + .maximumSize(MAX_CACHED_PATHS) // + .expireAfterWrite(MAX_CACHE_AGE) // + .buildAsync(); + + //TODO: this a expensive operation + // with a cachesize of _n_ and comparsion cost of _x_ + // runtime is n*x + void removeAllKeysWithPrefix(CryptoPath basePrefix) { + ciphertextDirectories.asMap().keySet().removeIf(p -> p.startsWith(basePrefix)); + } + + //TODO: this is a very expensive operation + // with a cache size of _n_ and comparsion cost of _x_ + // runtime is n*(1+x) + void recomputeAllKeysWithPrefix(CryptoPath oldPrefix, CryptoPath newPrefix) { + var remappedEntries = new ArrayList>>(); + ciphertextDirectories.asMap().entrySet().removeIf(e -> { + if (e.getKey().startsWith(oldPrefix)) { + var remappedPath = newPrefix.resolve(oldPrefix.relativize(e.getKey())); + return remappedEntries.add(Map.entry(remappedPath, e.getValue())); + } else { + return false; + } + }); + remappedEntries.forEach(e -> ciphertextDirectories.put(e.getKey(), e.getValue())); + } + + //cheap operation: log(n) + CipherDir putIfAbsent(CryptoPath cleartextPath, CipherDirLoader ifAbsent) throws IOException { + var futureMapping = new CompletableFuture(); + var currentMapping = ciphertextDirectories.asMap().putIfAbsent(cleartextPath, futureMapping); + if (currentMapping != null) { + return currentMapping.join(); + } else { + futureMapping.complete(ifAbsent.load()); + return futureMapping.join(); + } + } + + @FunctionalInterface + interface CipherDirLoader { + + CipherDir load() throws IOException; + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index d3d7af7c..2c8bd060 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -8,7 +8,6 @@ *******************************************************************************/ package org.cryptomator.cryptofs; -import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory; import org.cryptomator.cryptofs.attr.AttributeByNameProvider; import org.cryptomator.cryptofs.attr.AttributeProvider; import org.cryptomator.cryptofs.attr.AttributeViewProvider; @@ -142,7 +141,7 @@ public Path getCiphertextPath(Path cleartextPath) throws IOException { var p = CryptoPath.castAndAssertAbsolute(cleartextPath); var nodeType = cryptoPathMapper.getCiphertextFileType(p); if (nodeType == CiphertextFileType.DIRECTORY) { - return cryptoPathMapper.getCiphertextDir(p).path; + return cryptoPathMapper.getCiphertextDir(p).contentDirPath(); } var cipherFile = cryptoPathMapper.getCiphertextFilePath(p); if (nodeType == CiphertextFileType.SYMLINK) { @@ -316,22 +315,22 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute... attrs) throws if (cleartextParentDir == null) { return; } - Path ciphertextParentDir = cryptoPathMapper.getCiphertextDir(cleartextParentDir).path; + Path ciphertextParentDir = cryptoPathMapper.getCiphertextDir(cleartextParentDir).contentDirPath(); if (!Files.exists(ciphertextParentDir)) { throw new NoSuchFileException(cleartextParentDir.toString()); } cryptoPathMapper.assertNonExisting(cleartextDir); CiphertextFilePath ciphertextPath = cryptoPathMapper.getCiphertextFilePath(cleartextDir); Path ciphertextDirFile = ciphertextPath.getDirFilePath(); - CiphertextDirectory ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir); + var ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir); // atomically check for FileAlreadyExists and create otherwise: Files.createDirectory(ciphertextPath.getRawPath()); try (FileChannel channel = FileChannel.open(ciphertextDirFile, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), attrs)) { - channel.write(UTF_8.encode(ciphertextDir.dirId)); + channel.write(UTF_8.encode(ciphertextDir.dirId())); } // create dir if and only if the dirFile has been created right now (not if it has been created before): try { - Files.createDirectories(ciphertextDir.path); + Files.createDirectories(ciphertextDir.contentDirPath()); dirIdBackup.execute(ciphertextDir); ciphertextPath.persistLongFileName(); } catch (IOException e) { @@ -432,7 +431,7 @@ private void deleteFileOrSymlink(CiphertextFilePath ciphertextPath) throws IOExc } private void deleteDirectory(CryptoPath cleartextPath, CiphertextFilePath ciphertextPath) throws IOException { - Path ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextPath).path; + Path ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextPath).contentDirPath(); Path ciphertextDirFile = ciphertextPath.getDirFilePath(); try { ciphertextDirDeleter.deleteCiphertextDirIncludingNonCiphertextFiles(ciphertextDir, cleartextPath); @@ -505,7 +504,7 @@ private void copyDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge ciphertextTarget.persistLongFileName(); } else if (ArrayUtils.contains(options, StandardCopyOption.REPLACE_EXISTING)) { // keep existing (if empty): - Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path; + Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).contentDirPath(); try (DirectoryStream ds = Files.newDirectoryStream(ciphertextTargetDir)) { if (ds.iterator().hasNext()) { throw new DirectoryNotEmptyException(cleartextTarget.toString()); @@ -515,8 +514,8 @@ private void copyDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge throw new FileAlreadyExistsException(cleartextTarget.toString(), null, "Ciphertext file already exists: " + ciphertextTarget); } if (ArrayUtils.contains(options, StandardCopyOption.COPY_ATTRIBUTES)) { - Path ciphertextSourceDir = cryptoPathMapper.getCiphertextDir(cleartextSource).path; - Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path; + Path ciphertextSourceDir = cryptoPathMapper.getCiphertextDir(cleartextSource).contentDirPath(); + Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).contentDirPath(); copyAttributes(ciphertextSourceDir, ciphertextTargetDir); } } @@ -622,7 +621,7 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge throw new AtomicMoveNotSupportedException(cleartextSource.toString(), cleartextTarget.toString(), "Replacing directories during move requires non-atomic status checks."); } // check if dir is empty: - Path targetCiphertextDirContentDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path; + Path targetCiphertextDirContentDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).contentDirPath(); boolean targetCiphertextDirExists = true; try (DirectoryStream ds = Files.newDirectoryStream(targetCiphertextDirContentDir, DirectoryStreamFilters.EXCLUDE_DIR_ID_BACKUP)) { if (ds.iterator().hasNext()) { diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 3fffd21c..6ff312bd 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -155,7 +155,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2)); Files.createDirectories(vaultCipherRootPath); // create dirId backup: - DirectoryIdBackup.backupManually(cryptor, new CryptoPathMapper.CiphertextDirectory(Constants.ROOT_DIR_ID, vaultCipherRootPath)); + DirectoryIdBackup.backupManually(cryptor, new CipherDir(Constants.ROOT_DIR_ID, vaultCipherRootPath)); } finally { Arrays.fill(rawKey, (byte) 0x00); } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java index 2b03dd5d..2875c256 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java @@ -8,7 +8,6 @@ *******************************************************************************/ package org.cryptomator.cryptofs; -import com.github.benmanes.caffeine.cache.AsyncCache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.common.io.BaseEncoding; @@ -27,12 +26,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Map; -import java.util.Objects; import java.util.Optional; -import java.util.concurrent.CompletableFuture; import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME; @@ -47,10 +41,10 @@ public class CryptoPathMapper { private final DirectoryIdProvider dirIdProvider; private final LongFileNameProvider longFileNameProvider; private final VaultConfig vaultConfig; - private final LoadingCache ciphertextNames; + private final LoadingCache ciphertextNames; private final ClearToCipherDirCache clearToCipherDirCache; - private final CiphertextDirectory rootDirectory; + private final CipherDir rootDirectory; @Inject CryptoPathMapper(@PathToVault Path pathToVault, Cryptor cryptor, DirectoryIdProvider dirIdProvider, LongFileNameProvider longFileNameProvider, VaultConfig vaultConfig) { @@ -69,7 +63,7 @@ public class CryptoPathMapper { * * @param cleartextPath A path * @throws FileAlreadyExistsException If the node exists - * @throws IOException If any I/O error occurs while attempting to resolve the ciphertext path + * @throws IOException If any I/O error occurs while attempting to resolve the ciphertext path */ public void assertNonExisting(CryptoPath cleartextPath) throws FileAlreadyExistsException, IOException { try { @@ -119,13 +113,13 @@ public CiphertextFilePath getCiphertextFilePath(CryptoPath cleartextPath) throws if (parentPath == null) { throw new IllegalArgumentException("Invalid file path (must have a parent): " + cleartextPath); } - CiphertextDirectory parent = getCiphertextDir(parentPath); + CipherDir parent = getCiphertextDir(parentPath); String cleartextName = cleartextPath.getFileName().toString(); - return getCiphertextFilePath(parent.path, parent.dirId, cleartextName); + return getCiphertextFilePath(parent.contentDirPath(), parent.dirId(), cleartextName); } public CiphertextFilePath getCiphertextFilePath(Path parentCiphertextDir, String parentDirId, String cleartextName) { - String ciphertextName = ciphertextNames.get(new DirIdAndName(parentDirId, cleartextName)); + String ciphertextName = ciphertextNames.get(new CipherNodeNameParameters(parentDirId, cleartextName)); Path c9rPath = parentCiphertextDir.resolve(ciphertextName); if (ciphertextName.length() > vaultConfig.getShorteningThreshold()) { LongFileNameProvider.DeflatedFileName deflatedFileName = longFileNameProvider.deflate(c9rPath); @@ -135,13 +129,12 @@ public CiphertextFilePath getCiphertextFilePath(Path parentCiphertextDir, String } } - private String getCiphertextFileName(DirIdAndName dirIdAndName) { - return cryptor.fileNameCryptor().encryptFilename(BaseEncoding.base64Url(), dirIdAndName.name, dirIdAndName.dirId.getBytes(StandardCharsets.UTF_8)) + Constants.CRYPTOMATOR_FILE_SUFFIX; + private String getCiphertextFileName(CipherNodeNameParameters dirIdAndName) { + return cryptor.fileNameCryptor().encryptFilename(BaseEncoding.base64Url(), dirIdAndName.clearNodeName(), dirIdAndName.dirId().getBytes(StandardCharsets.UTF_8)) + Constants.CRYPTOMATOR_FILE_SUFFIX; } /** * TODO: doc doc doc - * @param cleartextPath */ public void invalidatePathMapping(CryptoPath cleartextPath) { clearToCipherDirCache.removeAllKeysWithPrefix(cleartextPath); @@ -154,12 +147,12 @@ public void movePathMapping(CryptoPath cleartextSrc, CryptoPath cleartextDst) { clearToCipherDirCache.recomputeAllKeysWithPrefix(cleartextSrc, cleartextDst); } - public CiphertextDirectory getCiphertextDir(CryptoPath cleartextPath) throws IOException { + public CipherDir getCiphertextDir(CryptoPath cleartextPath) throws IOException { assert cleartextPath.isAbsolute(); if (cleartextPath.getParent() == null) { return rootDirectory; } else { - CipherDirLoader cipherDirLoaderIfAbsent = () -> { + ClearToCipherDirCache.CipherDirLoader cipherDirLoaderIfAbsent = () -> { Path dirFile = getCiphertextFilePath(cleartextPath).getDirFilePath(); return resolveDirectory(dirFile); }; @@ -167,113 +160,14 @@ public CiphertextDirectory getCiphertextDir(CryptoPath cleartextPath) throws IOE } } - public CiphertextDirectory resolveDirectory(Path directoryFile) throws IOException { + public CipherDir resolveDirectory(Path directoryFile) throws IOException { String dirId = dirIdProvider.load(directoryFile); return resolveDirectory(dirId); } - private CiphertextDirectory resolveDirectory(String dirId) { + private CipherDir resolveDirectory(String dirId) { String dirHash = cryptor.fileNameCryptor().hashDirectoryId(dirId); Path dirPath = dataRoot.resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2)); - return new CiphertextDirectory(dirId, dirPath); + return new CipherDir(dirId, dirPath); } - - public static class CiphertextDirectory { - public final String dirId; - public final Path path; - - public CiphertextDirectory(String dirId, Path path) { - this.dirId = Objects.requireNonNull(dirId); - this.path = Objects.requireNonNull(path); - } - - @Override - public int hashCode() { - return Objects.hash(dirId, path); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } else if (obj instanceof CiphertextDirectory other) { - return this.dirId.equals(other.dirId) && this.path.equals(other.path); - } else { - return false; - } - } - } - - private static class DirIdAndName { - public final String dirId; - public final String name; - - public DirIdAndName(String dirId, String name) { - this.dirId = Objects.requireNonNull(dirId); - this.name = Objects.requireNonNull(name); - } - - @Override - public int hashCode() { - return Objects.hash(dirId, name); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } else if (obj instanceof DirIdAndName other) { - return this.dirId.equals(other.dirId) && this.name.equals(other.name); - } else { - return false; - } - } - } - - static class ClearToCipherDirCache { - - private static final int MAX_CACHED_DIR_PATHS = 5000; - private static final Duration MAX_CACHE_AGE = Duration.ofSeconds(20); - - private final AsyncCache ciphertextDirectories = Caffeine.newBuilder() // - .maximumSize(MAX_CACHED_DIR_PATHS) // - .expireAfterWrite(MAX_CACHE_AGE) // - .buildAsync(); - - void removeAllKeysWithPrefix(CryptoPath basePrefix) { - ciphertextDirectories.asMap().keySet().removeIf(p -> p.startsWith(basePrefix)); - } - - void recomputeAllKeysWithPrefix(CryptoPath oldPrefix, CryptoPath newPrefix) { - var remappedEntries = new ArrayList>>(); - ciphertextDirectories.asMap().entrySet().removeIf(e -> { - if (e.getKey().startsWith(oldPrefix)) { - var remappedPath = newPrefix.resolve(oldPrefix.relativize(e.getKey())); - return remappedEntries.add(Map.entry(remappedPath, e.getValue())); - } else { - return false; - } - }); - remappedEntries.forEach(e -> ciphertextDirectories.put(e.getKey(), e.getValue())); - } - - CiphertextDirectory putIfAbsent(CryptoPath cleartextPath, CipherDirLoader ifAbsent) throws IOException { - var futureMapping = new CompletableFuture(); - var currentMapping = ciphertextDirectories.asMap().putIfAbsent(cleartextPath, futureMapping); - if (currentMapping != null) { - return currentMapping.join(); - } else { - futureMapping.complete(ifAbsent.load()); - return futureMapping.join(); - } - } - - } - - @FunctionalInterface - interface CipherDirLoader { - - CiphertextDirectory load() throws IOException; - } - } diff --git a/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java b/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java index b3cdc0db..b39feca0 100644 --- a/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java +++ b/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java @@ -26,17 +26,17 @@ public DirectoryIdBackup(Cryptor cryptor) { } /** - * Performs the backup operation for the given {@link CryptoPathMapper.CiphertextDirectory} object. + * Performs the backup operation for the given {@link CipherDir} object. *

- * The directory id is written via an encrypting channel to the file {@link CryptoPathMapper.CiphertextDirectory#path}/{@value Constants#DIR_BACKUP_FILE_NAME}. + * The directory id is written via an encrypting channel to the file {@link CipherDir#contentDirPath()} /{@value Constants#DIR_BACKUP_FILE_NAME}. * * @param ciphertextDirectory The cipher dir object containing the dir id and the encrypted content root * @throws IOException if an IOException is raised during the write operation */ - public void execute(CryptoPathMapper.CiphertextDirectory ciphertextDirectory) throws IOException { - try (var channel = Files.newByteChannel(ciphertextDirectory.path.resolve(Constants.DIR_BACKUP_FILE_NAME), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); // + public void execute(CipherDir ciphertextDirectory) throws IOException { + try (var channel = Files.newByteChannel(ciphertextDirectory.contentDirPath().resolve(Constants.DIR_BACKUP_FILE_NAME), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); // var encryptingChannel = wrapEncryptionAround(channel, cryptor)) { - encryptingChannel.write(ByteBuffer.wrap(ciphertextDirectory.dirId.getBytes(StandardCharsets.US_ASCII))); + encryptingChannel.write(ByteBuffer.wrap(ciphertextDirectory.dirId().getBytes(StandardCharsets.US_ASCII))); } } @@ -44,10 +44,10 @@ public void execute(CryptoPathMapper.CiphertextDirectory ciphertextDirectory) th * Static method to explicitly back up the directory id for a specified ciphertext directory. * * @param cryptor The cryptor to be used - * @param ciphertextDirectory A {@link org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory} for which the dirId should be back up'd. + * @param ciphertextDirectory A {@link CipherDir} for which the dirId should be back up'd. * @throws IOException when the dirId file already exists, or it cannot be written to. */ - public static void backupManually(Cryptor cryptor, CryptoPathMapper.CiphertextDirectory ciphertextDirectory) throws IOException { + public static void backupManually(Cryptor cryptor, CipherDir ciphertextDirectory) throws IOException { new DirectoryIdBackup(cryptor).execute(ciphertextDirectory); } diff --git a/src/main/java/org/cryptomator/cryptofs/attr/AbstractCryptoFileAttributeView.java b/src/main/java/org/cryptomator/cryptofs/attr/AbstractCryptoFileAttributeView.java index 82563853..04e00b47 100644 --- a/src/main/java/org/cryptomator/cryptofs/attr/AbstractCryptoFileAttributeView.java +++ b/src/main/java/org/cryptomator/cryptofs/attr/AbstractCryptoFileAttributeView.java @@ -60,7 +60,7 @@ private Path getCiphertextPath(CryptoPath path) throws IOException { yield getCiphertextPath(resolved); } case DIRECTORY: - yield pathMapper.getCiphertextDir(path).path; + yield pathMapper.getCiphertextDir(path).contentDirPath(); case FILE: yield pathMapper.getCiphertextFilePath(path).getFilePath(); }; diff --git a/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java b/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java index b2195161..17ea4751 100644 --- a/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java @@ -53,7 +53,7 @@ public A readAttributes(CryptoPath cleartextPath private Path getCiphertextPath(CryptoPath path, CiphertextFileType type) throws IOException { return switch (type) { case SYMLINK -> pathMapper.getCiphertextFilePath(path).getSymlinkFilePath(); - case DIRECTORY -> pathMapper.getCiphertextDir(path).path; + case DIRECTORY -> pathMapper.getCiphertextDir(path).contentDirPath(); case FILE -> pathMapper.getCiphertextFilePath(path).getFilePath(); }; } diff --git a/src/main/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilter.java b/src/main/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilter.java index 2c05b34b..823cea0d 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilter.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilter.java @@ -28,7 +28,7 @@ public Stream process(Node node) { if (Files.isRegularFile(dirFile)) { final Path dirPath; try { - dirPath = cryptoPathMapper.resolveDirectory(dirFile).path; + dirPath = cryptoPathMapper.resolveDirectory(dirFile).contentDirPath(); } catch (IOException e) { LOG.warn("Broken directory file: " + dirFile, e); return Stream.empty(); diff --git a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactory.java b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactory.java index 651d7dff..39fbb93d 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactory.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactory.java @@ -1,9 +1,9 @@ package org.cryptomator.cryptofs.dir; +import org.cryptomator.cryptofs.CipherDir; import org.cryptomator.cryptofs.CryptoFileSystemScoped; import org.cryptomator.cryptofs.CryptoPath; import org.cryptomator.cryptofs.CryptoPathMapper; -import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory; import org.cryptomator.cryptofs.common.Constants; import javax.inject.Inject; @@ -36,9 +36,9 @@ public synchronized CryptoDirectoryStream newDirectoryStream(CryptoPath cleartex if (closed) { throw new ClosedFileSystemException(); } - CiphertextDirectory ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir); - DirectoryStream ciphertextDirStream = Files.newDirectoryStream(ciphertextDir.path, this::matchesEncryptedContentPattern); - var cleartextDirStream = directoryStreamComponentFactory.create(cleartextDir, ciphertextDir.dirId, ciphertextDirStream, filter, streams::remove).directoryStream(); + CipherDir ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir); + DirectoryStream ciphertextDirStream = Files.newDirectoryStream(ciphertextDir.contentDirPath(), this::matchesEncryptedContentPattern); + var cleartextDirStream = directoryStreamComponentFactory.create(cleartextDir, ciphertextDir.dirId(), ciphertextDirStream, filter, streams::remove).directoryStream(); streams.put(cleartextDirStream, ciphertextDirStream); return cleartextDirStream; } diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java index 7e59d982..bdf9ec29 100644 --- a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java +++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java @@ -1,5 +1,6 @@ package org.cryptomator.cryptofs.health.dirid; +import org.cryptomator.cryptofs.CipherDir; import org.cryptomator.cryptofs.CryptoPathMapper; import org.cryptomator.cryptofs.DirectoryIdBackup; import org.cryptomator.cryptofs.VaultConfig; @@ -51,7 +52,7 @@ void fix(Path pathToVault, Cryptor cryptor) throws IOException { var dirIdHash = cryptor.fileNameCryptor().hashDirectoryId(dirId); Path dirPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirIdHash.substring(0, 2)).resolve(dirIdHash.substring(2, 32)); Files.createDirectories(dirPath); - DirectoryIdBackup.backupManually(cryptor, new CryptoPathMapper.CiphertextDirectory(dirId, dirPath)); + DirectoryIdBackup.backupManually(cryptor, new CipherDir(dirId, dirPath)); } @Override diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java index be480db0..7ab9ed46 100644 --- a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java +++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java @@ -1,5 +1,6 @@ package org.cryptomator.cryptofs.health.dirid; +import org.cryptomator.cryptofs.CipherDir; import org.cryptomator.cryptofs.CryptoPathMapper; import org.cryptomator.cryptofs.DirectoryIdBackup; import org.cryptomator.cryptofs.VaultConfig; @@ -30,7 +31,7 @@ public String toString() { //visible for testing void fix(Path pathToVault, Cryptor cryptor) throws IOException { Path absCipherDir = pathToVault.resolve(contentDir); - DirectoryIdBackup.backupManually(cryptor, new CryptoPathMapper.CiphertextDirectory(dirId, absCipherDir)); + DirectoryIdBackup.backupManually(cryptor, new CipherDir(dirId, absCipherDir)); } @Override diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java index 505c0dc7..abf11454 100644 --- a/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java +++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java @@ -1,6 +1,7 @@ package org.cryptomator.cryptofs.health.dirid; import com.google.common.io.BaseEncoding; +import org.cryptomator.cryptofs.CipherDir; import org.cryptomator.cryptofs.CryptoPathMapper; import org.cryptomator.cryptofs.DirectoryIdBackup; import org.cryptomator.cryptofs.VaultConfig; @@ -115,7 +116,7 @@ private void fix(Path pathToVault, VaultConfig config, Cryptor cryptor) throws I Files.deleteIfExists(orphanedDir.resolve(Constants.DIR_BACKUP_FILE_NAME)); try (var nonCryptomatorFiles = Files.newDirectoryStream(orphanedDir)) { for (Path p : nonCryptomatorFiles) { - Files.move(p, stepParentDir.path.resolve(p.getFileName()), LinkOption.NOFOLLOW_LINKS); + Files.move(p, stepParentDir.contentDirPath().resolve(p.getFileName()), LinkOption.NOFOLLOW_LINKS); } } Files.delete(orphanedDir); @@ -154,7 +155,7 @@ Path prepareRecoveryDir(Path pathToVault, FileNameCryptor cryptor) throws IOExce } // visible for testing - CryptoPathMapper.CiphertextDirectory prepareStepParent(Path dataDir, Path cipherRecoveryDir, Cryptor cryptor, String clearStepParentDirName) throws IOException { + CipherDir prepareStepParent(Path dataDir, Path cipherRecoveryDir, Cryptor cryptor, String clearStepParentDirName) throws IOException { //create "stepparent" directory to move orphaned files to String cipherStepParentDirName = encrypt(cryptor.fileNameCryptor(), clearStepParentDirName, Constants.RECOVERY_DIR_ID); Path cipherStepParentDirFile = cipherRecoveryDir.resolve(cipherStepParentDirName + "/" + Constants.DIR_FILE_NAME); @@ -169,7 +170,7 @@ CryptoPathMapper.CiphertextDirectory prepareStepParent(Path dataDir, Path cipher String stepParentDirHash = cryptor.fileNameCryptor().hashDirectoryId(stepParentUUID); Path stepParentDir = dataDir.resolve(stepParentDirHash.substring(0, 2)).resolve(stepParentDirHash.substring(2)).toAbsolutePath(); Files.createDirectories(stepParentDir); - var stepParentCipherDir = new CryptoPathMapper.CiphertextDirectory(stepParentUUID, stepParentDir); + var stepParentCipherDir = new CipherDir(stepParentUUID, stepParentDir); //only if it does not exist try { DirectoryIdBackup.backupManually(cryptor, stepParentCipherDir); @@ -215,11 +216,11 @@ String decryptFileName(Path orphanedResource, boolean isShortened, String dirId, } // visible for testing - void adoptOrphanedResource(Path oldCipherPath, String newClearName, boolean isShortened, CryptoPathMapper.CiphertextDirectory stepParentDir, FileNameCryptor cryptor, MessageDigest sha1) throws IOException { - var newCipherName = encrypt(cryptor, newClearName, stepParentDir.dirId); + void adoptOrphanedResource(Path oldCipherPath, String newClearName, boolean isShortened, CipherDir stepParentDir, FileNameCryptor cryptor, MessageDigest sha1) throws IOException { + var newCipherName = encrypt(cryptor, newClearName, stepParentDir.dirId()); if (isShortened) { var deflatedName = BaseEncoding.base64Url().encode(sha1.digest(newCipherName.getBytes(StandardCharsets.UTF_8))) + Constants.DEFLATED_FILE_SUFFIX; - Path targetPath = stepParentDir.path.resolve(deflatedName); + Path targetPath = stepParentDir.contentDirPath().resolve(deflatedName); Files.move(oldCipherPath, targetPath); //adjust name.c9s @@ -227,7 +228,7 @@ void adoptOrphanedResource(Path oldCipherPath, String newClearName, boolean isSh fc.write(ByteBuffer.wrap(newCipherName.getBytes(StandardCharsets.UTF_8))); } } else { - Path targetPath = stepParentDir.path.resolve(newCipherName); + Path targetPath = stepParentDir.contentDirPath().resolve(newCipherName); Files.move(oldCipherPath, targetPath); } } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java index 5095b5cd..645cd4b4 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java @@ -1,6 +1,5 @@ package org.cryptomator.cryptofs; -import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory; import org.cryptomator.cryptofs.attr.AttributeByNameProvider; import org.cryptomator.cryptofs.attr.AttributeProvider; import org.cryptomator.cryptofs.attr.AttributeViewProvider; @@ -203,7 +202,7 @@ public void testCleartextDirectory() throws IOException { try (var cryptoPathMock = Mockito.mockStatic(CryptoPath.class)) { cryptoPathMock.when(() -> CryptoPath.castAndAssertAbsolute(any())).thenReturn(cleartext); when(cryptoPathMapper.getCiphertextFileType(any())).thenReturn(CiphertextFileType.DIRECTORY); - when(cryptoPathMapper.getCiphertextDir(any())).thenReturn(new CiphertextDirectory("foo", ciphertext)); + when(cryptoPathMapper.getCiphertextDir(any())).thenReturn(new CipherDir("foo", ciphertext)); Path result = inTest.getCiphertextPath(cleartext); Assertions.assertEquals(ciphertext, result); @@ -579,7 +578,7 @@ public void setup() throws IOException { when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath); when(ciphertextPath.getFilePath()).thenReturn(ciphertextFilePath); when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFilePath); - when(cryptoPathMapper.getCiphertextDir(cleartextPath)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(cleartextPath)).thenReturn(new CipherDir("foo", ciphertextDirPath)); when(physicalFsProv.readAttributes(ciphertextRawPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(ciphertextPathAttr); when(physicalFsProv.readAttributes(ciphertextDirFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(ciphertextDirFilePathAttr); @@ -695,8 +694,8 @@ public void setup() throws IOException { when(ciphertextDestinationDirFile.getName(3)).thenReturn(ciphertextDestinationFileName); when(cryptoPathMapper.getCiphertextFilePath(cleartextSource)).thenReturn(ciphertextSource); when(cryptoPathMapper.getCiphertextFilePath(cleartextDestination)).thenReturn(ciphertextDestination); - when(cryptoPathMapper.getCiphertextDir(cleartextSource)).thenReturn(new CiphertextDirectory("foo", ciphertextSourceDir)); - when(cryptoPathMapper.getCiphertextDir(cleartextDestination)).thenReturn(new CiphertextDirectory("bar", ciphertextDestinationDir)); + when(cryptoPathMapper.getCiphertextDir(cleartextSource)).thenReturn(new CipherDir("foo", ciphertextSourceDir)); + when(cryptoPathMapper.getCiphertextDir(cleartextDestination)).thenReturn(new CipherDir("bar", ciphertextDestinationDir)); when(symlinks.resolveRecursively(cleartextSource)).thenReturn(sourceLinkTarget); when(symlinks.resolveRecursively(cleartextDestination)).thenReturn(destinationLinkTarget); when(cryptoPathMapper.getCiphertextFileType(sourceLinkTarget)).thenReturn(CiphertextFileType.FILE); @@ -871,8 +870,8 @@ public void setup() throws IOException, ReflectiveOperationException { when(ciphertextTargetParent.getFileSystem()).thenReturn(physicalFs); when(ciphertextDestinationDir.getFileSystem()).thenReturn(physicalFs); - when(cryptoPathMapper.getCiphertextDir(cleartextTargetParent)).thenReturn(new CiphertextDirectory("41", ciphertextTargetParent)); - when(cryptoPathMapper.getCiphertextDir(cleartextDestination)).thenReturn(new CiphertextDirectory("42", ciphertextDestinationDir)); + when(cryptoPathMapper.getCiphertextDir(cleartextTargetParent)).thenReturn(new CipherDir("41", ciphertextTargetParent)); + when(cryptoPathMapper.getCiphertextDir(cleartextDestination)).thenReturn(new CipherDir("42", ciphertextDestinationDir)); when(physicalFsProv.newFileChannel(Mockito.same(ciphertextDestinationDirFile), Mockito.anySet(), Mockito.any())).thenReturn(ciphertextTargetDirDirFileFileChannel); } @@ -1156,7 +1155,7 @@ public void createDirectoryIfPathHasNoParentDoesNothing() throws IOException { @Test public void createDirectoryIfPathsParentDoesNotExistsThrowsNoSuchFileException() throws IOException { Path ciphertextParent = mock(Path.class); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("foo", ciphertextParent)); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CipherDir("foo", ciphertextParent)); when(ciphertextParent.getFileSystem()).thenReturn(fileSystem); doThrow(NoSuchFileException.class).when(provider).checkAccess(ciphertextParent); @@ -1169,7 +1168,7 @@ public void createDirectoryIfPathsParentDoesNotExistsThrowsNoSuchFileException() @Test public void createDirectoryIfPathCiphertextFileDoesExistThrowsFileAlreadyException() throws IOException { Path ciphertextParent = mock(Path.class); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("foo", ciphertextParent)); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CipherDir("foo", ciphertextParent)); when(ciphertextParent.getFileSystem()).thenReturn(fileSystem); doThrow(new FileAlreadyExistsException(path.toString())).when(cryptoPathMapper).assertNonExisting(path); when(provider.exists(ciphertextParent)).thenReturn(true); @@ -1191,8 +1190,8 @@ public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOExceptio FileChannelMock channel = new FileChannelMock(100); when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, ciphertextDirPath)); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextParent)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir(dirId, ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CipherDir("parentDirId", ciphertextParent)); when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class); when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath); when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile); @@ -1221,8 +1220,8 @@ public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() thro FileChannelMock channel = new FileChannelMock(100); when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, ciphertextDirPath)); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextParent)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir(dirId, ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CipherDir("parentDirId", ciphertextParent)); when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class); when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath); when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile); @@ -1258,12 +1257,12 @@ public void createDirectoryBackupsDirIdInCiphertextDirPath() throws IOException Path ciphertextDirPath = mock(Path.class, "d/FF/FF/"); CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class, "ciphertext"); String dirId = "DirId1234ABC"; - CiphertextDirectory cipherDirObject = new CiphertextDirectory(dirId, ciphertextDirPath); + CipherDir cipherDirObject = new CipherDir(dirId, ciphertextDirPath); FileChannelMock channel = new FileChannelMock(100); when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(cipherDirObject); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextParent)); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CipherDir("parentDirId", ciphertextParent)); when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class); when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath); when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile); @@ -1325,7 +1324,7 @@ public class IsHidden { public void setup() throws IOException { when(fileSystem.provider()).thenReturn(provider); when(cryptoPathMapper.getCiphertextFileType(path)).thenReturn(CiphertextFileType.DIRECTORY); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir("foo", ciphertextDirPath)); when(ciphertextDirPath.getFileSystem()).thenReturn(fileSystem); } @@ -1373,7 +1372,7 @@ public class CheckAccess { public void setup() throws IOException { when(fileSystem.provider()).thenReturn(provider); when(cryptoPathMapper.getCiphertextFileType(path)).thenReturn(CiphertextFileType.DIRECTORY); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir("foo", ciphertextDirPath)); when(ciphertextDirPath.getFileSystem()).thenReturn(fileSystem); } @@ -1555,7 +1554,7 @@ public void setAttributeOnRegularDirectory() throws IOException { CryptoPath path = mock(CryptoPath.class); Path ciphertextDirPath = mock(Path.class); when(cryptoPathMapper.getCiphertextFileType(path)).thenReturn(CiphertextFileType.DIRECTORY); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir("foo", ciphertextDirPath)); inTest.setAttribute(path, name, value); @@ -1570,7 +1569,7 @@ public void setAttributeOnNonExistingRootDirectory() throws IOException { CryptoPath path = mock(CryptoPath.class); Path ciphertextDirPath = mock(Path.class); when(cryptoPathMapper.getCiphertextFileType(path)).thenReturn(CiphertextFileType.DIRECTORY); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir("foo", ciphertextDirPath)); doThrow(new NoSuchFileException("")).when(provider).checkAccess(ciphertextDirPath); inTest.setAttribute(path, name, value); @@ -1588,7 +1587,7 @@ public void setAttributeOnFile() throws IOException { Path ciphertextFilePath = mock(Path.class); CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class); when(cryptoPathMapper.getCiphertextFileType(path)).thenReturn(CiphertextFileType.FILE); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir("foo", ciphertextDirPath)); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); when(ciphertextPath.getFilePath()).thenReturn(ciphertextFilePath); doThrow(new NoSuchFileException("")).when(provider).checkAccess(ciphertextDirPath); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java index f218691d..c8e2b844 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java @@ -75,7 +75,7 @@ public void testPathEncryptionForRoot() throws IOException { Mockito.when(d00.resolve("00")).thenReturn(d0000); CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); - Path path = mapper.getCiphertextDir(fileSystem.getRootPath()).path; + Path path = mapper.getCiphertextDir(fileSystem.getRootPath()).contentDirPath(); Assertions.assertEquals(d0000, path); } @@ -99,7 +99,7 @@ public void testPathEncryptionForFoo() throws IOException { Mockito.when(d00.resolve("01")).thenReturn(d0001); CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); - Path path = mapper.getCiphertextDir(fileSystem.getPath("/foo")).path; + Path path = mapper.getCiphertextDir(fileSystem.getPath("/foo")).contentDirPath(); Assertions.assertEquals(d0001, path); } @@ -133,7 +133,7 @@ public void testPathEncryptionForFooBar() throws IOException { Mockito.when(d00.resolve("02")).thenReturn(d0002); CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); - Path path = mapper.getCiphertextDir(fileSystem.getPath("/foo/bar")).path; + Path path = mapper.getCiphertextDir(fileSystem.getPath("/foo/bar")).contentDirPath(); Assertions.assertEquals(d0002, path); } diff --git a/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java b/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java index 19805b25..3bf7c86b 100644 --- a/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java @@ -22,7 +22,7 @@ public class DirectoryIdBackupTest { Path contentPath; private String dirId = "12345678"; - private CryptoPathMapper.CiphertextDirectory cipherDirObject; + private CipherDir cipherDirObject; private EncryptingWritableByteChannel encChannel; private Cryptor cryptor; @@ -31,7 +31,7 @@ public class DirectoryIdBackupTest { @BeforeEach public void init() { - cipherDirObject = new CryptoPathMapper.CiphertextDirectory(dirId, contentPath); + cipherDirObject = new CipherDir(dirId, contentPath); cryptor = Mockito.mock(Cryptor.class); encChannel = Mockito.mock(EncryptingWritableByteChannel.class); diff --git a/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java b/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java index d123f3df..556834db 100644 --- a/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java @@ -8,10 +8,10 @@ *******************************************************************************/ package org.cryptomator.cryptofs.attr; +import org.cryptomator.cryptofs.CipherDir; import org.cryptomator.cryptofs.CiphertextFilePath; import org.cryptomator.cryptofs.CryptoPath; import org.cryptomator.cryptofs.CryptoPathMapper; -import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory; import org.cryptomator.cryptofs.Symlinks; import org.cryptomator.cryptofs.common.CiphertextFileType; import org.junit.jupiter.api.Assertions; @@ -140,7 +140,7 @@ public class Directories { public void setup() throws IOException { Mockito.when(symlinks.resolveRecursively(cleartextPath)).thenReturn(cleartextPath); Mockito.when(pathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.DIRECTORY); - Mockito.when(pathMapper.getCiphertextDir(cleartextPath)).thenReturn(new CiphertextDirectory("foo", ciphertextRawPath)); + Mockito.when(pathMapper.getCiphertextDir(cleartextPath)).thenReturn(new CipherDir("foo", ciphertextRawPath)); prov = new AttributeProvider(attributeComponentFactory, pathMapper, symlinks); } diff --git a/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java b/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java index e18810e1..96239bf3 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java @@ -1,5 +1,6 @@ package org.cryptomator.cryptofs.dir; +import org.cryptomator.cryptofs.CipherDir; import org.cryptomator.cryptofs.CryptoPathMapper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -31,7 +32,7 @@ public void testProcessNormalDirectoryNode(@TempDir Path dir) throws IOException Path targetDir = Files.createDirectories(dir.resolve("d/ab/cdefg")); Files.createDirectory(dir.resolve("foo.c9r")); Files.write(dir.resolve("foo.c9r/dir.c9r"), "".getBytes()); - Mockito.when(cryptoPathMapper.resolveDirectory(Mockito.any())).thenReturn(new CryptoPathMapper.CiphertextDirectory("asd", targetDir)); + Mockito.when(cryptoPathMapper.resolveDirectory(Mockito.any())).thenReturn(new CipherDir("asd", targetDir)); Node unfiltered = new Node(dir.resolve("foo.c9r")); Stream result = brokenDirectoryFilter.process(unfiltered); @@ -45,7 +46,7 @@ public void testProcessNodeWithMissingTargetDir(@TempDir Path dir) throws IOExce Path targetDir = dir.resolve("d/ab/cdefg"); // not existing! Files.createDirectory(dir.resolve("foo.c9r")); Files.write(dir.resolve("foo.c9r/dir.c9r"), "".getBytes()); - Mockito.when(cryptoPathMapper.resolveDirectory(Mockito.any())).thenReturn(new CryptoPathMapper.CiphertextDirectory("asd", targetDir)); + Mockito.when(cryptoPathMapper.resolveDirectory(Mockito.any())).thenReturn(new CipherDir("asd", targetDir)); Node unfiltered = new Node(dir.resolve("foo.c9r")); Stream result = brokenDirectoryFilter.process(unfiltered); diff --git a/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java b/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java index c4361278..67e060f5 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java @@ -1,8 +1,8 @@ package org.cryptomator.cryptofs.dir; +import org.cryptomator.cryptofs.CipherDir; import org.cryptomator.cryptofs.CryptoPath; import org.cryptomator.cryptofs.CryptoPathMapper; -import org.cryptomator.cryptofs.CryptoPathMapper.CiphertextDirectory; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -55,7 +55,7 @@ public void testNewDirectoryStreamCreatesDirectoryStream() throws IOException { String dirId = "dirIdAbc"; Path dirPath = mock(Path.class, "dirAbc"); when(dirPath.getFileSystem()).thenReturn(fileSystem); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, dirPath)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir(dirId, dirPath)); DirectoryStream stream = mock(DirectoryStream.class); when(provider.newDirectoryStream(same(dirPath), any())).thenReturn(stream); @@ -74,8 +74,8 @@ public void testCloseClosesAllNonClosedDirectoryStreams() throws IOException { when(dirPathA.getFileSystem()).thenReturn(fileSystem); Path dirPathB = mock(Path.class, "dirPathB"); when(dirPathB.getFileSystem()).thenReturn(fileSystem); - when(cryptoPathMapper.getCiphertextDir(pathA)).thenReturn(new CiphertextDirectory("dirIdA", dirPathA)); - when(cryptoPathMapper.getCiphertextDir(pathB)).thenReturn(new CiphertextDirectory("dirIdB", dirPathB)); + when(cryptoPathMapper.getCiphertextDir(pathA)).thenReturn(new CipherDir("dirIdA", dirPathA)); + when(cryptoPathMapper.getCiphertextDir(pathB)).thenReturn(new CipherDir("dirIdB", dirPathB)); DirectoryStream streamA = mock(DirectoryStream.class, "streamA"); DirectoryStream streamB = mock(DirectoryStream.class, "streamB"); when(provider.newDirectoryStream(same(dirPathA), any())).thenReturn(streamA); diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java index 8a9aef15..a2e3330e 100644 --- a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java +++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java @@ -1,5 +1,6 @@ package org.cryptomator.cryptofs.health.dirid; +import org.cryptomator.cryptofs.CipherDir; import org.cryptomator.cryptofs.CryptoPathMapper; import org.cryptomator.cryptofs.DirectoryIdBackup; import org.cryptomator.cryptofs.VaultConfig; @@ -60,7 +61,7 @@ public void testFix() throws IOException { result.fix(pathToVault, cryptor); var expectedPath = pathToVault.resolve("d/ri/diculous-32-char-pseudo-hashhh"); - ArgumentMatcher cipherDirMatcher = obj -> obj.dirId.equals(dirId) && obj.path.endsWith(expectedPath); + ArgumentMatcher cipherDirMatcher = obj -> obj.dirId().equals(dirId) && obj.contentDirPath().endsWith(expectedPath); dirIdBackupMock.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher)), Mockito.times(1)); var attr = Assertions.assertDoesNotThrow(() -> Files.readAttributes(expectedPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)); Assertions.assertTrue(attr.isDirectory()); diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java index 09aedef5..2858117e 100644 --- a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java +++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java @@ -1,5 +1,6 @@ package org.cryptomator.cryptofs.health.dirid; +import org.cryptomator.cryptofs.CipherDir; import org.cryptomator.cryptofs.CryptoPathMapper; import org.cryptomator.cryptofs.DirectoryIdBackup; import org.cryptomator.cryptofs.VaultConfig; @@ -43,7 +44,7 @@ public void testFix() throws IOException { result.fix(pathToVault, cryptor); var expectedPath = pathToVault.resolve(cipherDir); - ArgumentMatcher cipherDirMatcher = obj -> obj.dirId.equals(dirId) && obj.path.isAbsolute() && obj.path.equals(expectedPath); + ArgumentMatcher cipherDirMatcher = obj -> obj.dirId().equals(dirId) && obj.contentDirPath().isAbsolute() && obj.contentDirPath().equals(expectedPath); dirIdBackupMock.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher)), Mockito.times(1)); } } diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java index fcc5888e..d11ebe45 100644 --- a/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java +++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java @@ -1,6 +1,7 @@ package org.cryptomator.cryptofs.health.dirid; import com.google.common.io.BaseEncoding; +import org.cryptomator.cryptofs.CipherDir; import org.cryptomator.cryptofs.CryptoPathMapper; import org.cryptomator.cryptofs.DirectoryIdBackup; import org.cryptomator.cryptofs.VaultConfig; @@ -327,15 +328,15 @@ public void testAdoptOrphanedUnshortened() throws IOException { String newClearName = "OliverTwist"; Files.writeString(oldCipherPath, expectedMsg, StandardCharsets.UTF_8, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); - CryptoPathMapper.CiphertextDirectory stepParentDir = new CryptoPathMapper.CiphertextDirectory("aaaaaa", pathToVault.resolve("d/22/2222")); - Files.createDirectories(stepParentDir.path); + CipherDir stepParentDir = new CipherDir("aaaaaa", pathToVault.resolve("d/22/2222")); + Files.createDirectories(stepParentDir.contentDirPath()); - Mockito.doReturn("adopted").when(fileNameCryptor).encryptFilename(BaseEncoding.base64Url(), newClearName, stepParentDir.dirId.getBytes(StandardCharsets.UTF_8)); + Mockito.doReturn("adopted").when(fileNameCryptor).encryptFilename(BaseEncoding.base64Url(), newClearName, stepParentDir.dirId().getBytes(StandardCharsets.UTF_8)); var sha1 = Mockito.mock(MessageDigest.class); result.adoptOrphanedResource(oldCipherPath, newClearName, false, stepParentDir, fileNameCryptor, sha1); - Assertions.assertEquals(expectedMsg, Files.readString(stepParentDir.path.resolve("adopted.c9r"))); + Assertions.assertEquals(expectedMsg, Files.readString(stepParentDir.contentDirPath().resolve("adopted.c9r"))); Assertions.assertTrue(Files.notExists(oldCipherPath)); } @@ -348,8 +349,8 @@ public void testAdoptOrphanedShortened() throws IOException { Files.createDirectories(oldCipherPath); Files.createFile(oldCipherPath.resolve("name.c9s")); - CryptoPathMapper.CiphertextDirectory stepParentDir = new CryptoPathMapper.CiphertextDirectory("aaaaaa", pathToVault.resolve("d/22/2222")); - Files.createDirectories(stepParentDir.path); + CipherDir stepParentDir = new CipherDir("aaaaaa", pathToVault.resolve("d/22/2222")); + Files.createDirectories(stepParentDir.contentDirPath()); Mockito.doReturn("adopted").when(fileNameCryptor).encryptFilename(Mockito.any(), Mockito.any(), Mockito.any()); try (var baseEncodingClass = Mockito.mockStatic(BaseEncoding.class)) { @@ -363,8 +364,8 @@ public void testAdoptOrphanedShortened() throws IOException { result.adoptOrphanedResource(oldCipherPath, newClearName, true, stepParentDir, fileNameCryptor, sha1); } - Assertions.assertTrue(Files.exists(stepParentDir.path.resolve("adopted_shortened.c9s"))); - Assertions.assertEquals("adopted.c9r", Files.readString(stepParentDir.path.resolve("adopted_shortened.c9s/name.c9s"))); + Assertions.assertTrue(Files.exists(stepParentDir.contentDirPath().resolve("adopted_shortened.c9s"))); + Assertions.assertEquals("adopted.c9r", Files.readString(stepParentDir.contentDirPath().resolve("adopted_shortened.c9s/name.c9s"))); Assertions.assertTrue(Files.notExists(oldCipherPath)); } @@ -376,8 +377,8 @@ public void testAdoptOrphanedShortenedMissingNameC9s() throws IOException { String newClearName = "TomSawyer"; Files.createDirectories(oldCipherPath); - CryptoPathMapper.CiphertextDirectory stepParentDir = new CryptoPathMapper.CiphertextDirectory("aaaaaa", pathToVault.resolve("d/22/2222")); - Files.createDirectories(stepParentDir.path); + CipherDir stepParentDir = new CipherDir("aaaaaa", pathToVault.resolve("d/22/2222")); + Files.createDirectories(stepParentDir.contentDirPath()); Mockito.doReturn("adopted").when(fileNameCryptor).encryptFilename(Mockito.any(), Mockito.any(), Mockito.any()); try (var baseEncodingClass = Mockito.mockStatic(BaseEncoding.class)) { @@ -391,8 +392,8 @@ public void testAdoptOrphanedShortenedMissingNameC9s() throws IOException { result.adoptOrphanedResource(oldCipherPath, newClearName, true, stepParentDir, fileNameCryptor, sha1); } - Assertions.assertTrue(Files.exists(stepParentDir.path.resolve("adopted_shortened.c9s"))); - Assertions.assertEquals("adopted.c9r", Files.readString(stepParentDir.path.resolve("adopted_shortened.c9s/name.c9s"))); + Assertions.assertTrue(Files.exists(stepParentDir.contentDirPath().resolve("adopted_shortened.c9s"))); + Assertions.assertEquals("adopted.c9r", Files.readString(stepParentDir.contentDirPath().resolve("adopted_shortened.c9s/name.c9s"))); Assertions.assertTrue(Files.notExists(oldCipherPath)); } @@ -410,7 +411,7 @@ public void testFixNoDirId() throws IOException { Files.createFile(orphan1); Files.createDirectories(orphan2); - CryptoPathMapper.CiphertextDirectory stepParentDir = new CryptoPathMapper.CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222")); + CipherDir stepParentDir = new CipherDir("aaaaaa", dataDir.resolve("22/2222")); VaultConfig config = Mockito.mock(VaultConfig.class); Mockito.doReturn(170).when(config).getShorteningThreshold(); @@ -443,7 +444,7 @@ public void testFixContinuesOnNotRecoverableFilename() throws IOException { var dirId = Optional.of("trololo-id"); - CryptoPathMapper.CiphertextDirectory stepParentDir = new CryptoPathMapper.CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222")); + CipherDir stepParentDir = new CipherDir("aaaaaa", dataDir.resolve("22/2222")); VaultConfig config = Mockito.mock(VaultConfig.class); Mockito.doReturn(170).when(config).getShorteningThreshold(); @@ -482,7 +483,7 @@ public void testFixWithDirId() throws IOException { var dirId = Optional.of("trololo-id"); - CryptoPathMapper.CiphertextDirectory stepParentDir = new CryptoPathMapper.CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222")); + CipherDir stepParentDir = new CipherDir("aaaaaa", dataDir.resolve("22/2222")); VaultConfig config = Mockito.mock(VaultConfig.class); Mockito.doReturn(170).when(config).getShorteningThreshold(); @@ -525,8 +526,8 @@ public void testFixWithNonCryptomatorFiles() throws IOException { var dirId = Optional.of("trololo-id"); - CryptoPathMapper.CiphertextDirectory stepParentDir = new CryptoPathMapper.CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222")); - Files.createDirectories(stepParentDir.path); //needs to be created here, otherwise the Files.move(non-crypto-resource, stepparent) will fail + CipherDir stepParentDir = new CipherDir("aaaaaa", dataDir.resolve("22/2222")); + Files.createDirectories(stepParentDir.contentDirPath()); //needs to be created here, otherwise the Files.move(non-crypto-resource, stepparent) will fail VaultConfig config = Mockito.mock(VaultConfig.class); Mockito.doReturn(170).when(config).getShorteningThreshold(); @@ -548,7 +549,7 @@ public void testFixWithNonCryptomatorFiles() throws IOException { Mockito.verify(resultSpy, Mockito.never()).adoptOrphanedResource(Mockito.eq(unrelated), Mockito.any(), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any()); Mockito.verify(resultSpy, Mockito.times(1)).adoptOrphanedResource(Mockito.eq(orphan1), Mockito.eq(lostName1), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any()); Mockito.verify(resultSpy, Mockito.times(1)).adoptOrphanedResource(Mockito.eq(orphan2), Mockito.eq(lostName2), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any()); - Assertions.assertTrue(Files.exists(stepParentDir.path.resolve("unrelated.file"))); + Assertions.assertTrue(Files.exists(stepParentDir.contentDirPath().resolve("unrelated.file"))); Assertions.assertTrue(Files.notExists(cipherOrphan)); } From 325c13fd85c6f715d6b12f5e12db44d10e553f40 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Mon, 14 Oct 2024 14:18:39 +0200 Subject: [PATCH 05/17] add unit tests for dir cache --- .../cryptofs/ClearToCipherDirCache.java | 40 ++++-- .../cryptofs/ClearToCipherDirCacheTest.java | 119 ++++++++++++++++++ 2 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 src/test/java/org/cryptomator/cryptofs/ClearToCipherDirCacheTest.java diff --git a/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java b/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java index f0bfac82..9adca293 100644 --- a/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java +++ b/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.time.Duration; import java.util.ArrayList; -import java.util.Map; import java.util.concurrent.CompletableFuture; public class ClearToCipherDirCache { @@ -14,36 +13,49 @@ public class ClearToCipherDirCache { private static final int MAX_CACHED_PATHS = 5000; private static final Duration MAX_CACHE_AGE = Duration.ofSeconds(20); - //TODO: not testable! private final AsyncCache ciphertextDirectories = Caffeine.newBuilder() // .maximumSize(MAX_CACHED_PATHS) // .expireAfterWrite(MAX_CACHE_AGE) // .buildAsync(); - //TODO: this a expensive operation - // with a cachesize of _n_ and comparsion cost of _x_ - // runtime is n*x + /** + * Removes all (key,value) entries, where {@code key.startsWith(oldPrefix) == true}. + * + * @param basePrefix The prefix key which the keys are checked against + */ void removeAllKeysWithPrefix(CryptoPath basePrefix) { + //TODO: this a expensive operation + // with a cachesize of _n_, comparsion cost of _x_ and removal costs of y + // worstcase runtime is n*(x+y) + // worstcase example: recursivley deleting a deeply nested dir, where all child dirs are cached ciphertextDirectories.asMap().keySet().removeIf(p -> p.startsWith(basePrefix)); } - //TODO: this is a very expensive operation - // with a cache size of _n_ and comparsion cost of _x_ - // runtime is n*(1+x) + /** + * Remaps all (key,value) entries, where {@code key.startsWith(oldPrefix) == true}. + * The new key is computed by replacing the oldPrefix with the newPrefix. + * + * @param oldPrefix the prefix key which the keys are checked against + * @param newPrefix the prefix key which replaces {@code oldPrefix} + */ void recomputeAllKeysWithPrefix(CryptoPath oldPrefix, CryptoPath newPrefix) { - var remappedEntries = new ArrayList>>(); + //TODO: this is a very expensive operation + // with a cache size of _n_, comparsion cost of _x_, removal costs of y and insertion costs of z + // worstcase runtime is n*x + n*y + n*z + // worstcase example: moving the root of a deeply nested dir, where all child dirs are cached + // Structure: /foo/bar/foo/bar/foo/bar...; Call: remapWithPrefix(/foo,/seb) + var remappedEntries = new ArrayList(); ciphertextDirectories.asMap().entrySet().removeIf(e -> { if (e.getKey().startsWith(oldPrefix)) { var remappedPath = newPrefix.resolve(oldPrefix.relativize(e.getKey())); - return remappedEntries.add(Map.entry(remappedPath, e.getValue())); + return remappedEntries.add(new CacheEntry(remappedPath, e.getValue())); } else { return false; } }); - remappedEntries.forEach(e -> ciphertextDirectories.put(e.getKey(), e.getValue())); + remappedEntries.forEach(e -> ciphertextDirectories.put(e.clearPath(), e.cipherDir())); } - //cheap operation: log(n) CipherDir putIfAbsent(CryptoPath cleartextPath, CipherDirLoader ifAbsent) throws IOException { var futureMapping = new CompletableFuture(); var currentMapping = ciphertextDirectories.asMap().putIfAbsent(cleartextPath, futureMapping); @@ -61,4 +73,8 @@ interface CipherDirLoader { CipherDir load() throws IOException; } + private record CacheEntry(CryptoPath clearPath, CompletableFuture cipherDir) { + + } + } diff --git a/src/test/java/org/cryptomator/cryptofs/ClearToCipherDirCacheTest.java b/src/test/java/org/cryptomator/cryptofs/ClearToCipherDirCacheTest.java new file mode 100644 index 00000000..2e60c7df --- /dev/null +++ b/src/test/java/org/cryptomator/cryptofs/ClearToCipherDirCacheTest.java @@ -0,0 +1,119 @@ +package org.cryptomator.cryptofs; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.io.IOException; +import java.nio.file.Path; + +public class ClearToCipherDirCacheTest { + + ClearToCipherDirCache cache; + CryptoPath clearPath; + ClearToCipherDirCache.CipherDirLoader dirLoader; + + + @BeforeEach + public void beforeEach() throws IOException { + cache = new ClearToCipherDirCache(); + clearPath = Mockito.mock(CryptoPath.class); + dirLoader = Mockito.mock(ClearToCipherDirCache.CipherDirLoader.class); + var cipherDir = Mockito.mock(CipherDir.class); + Mockito.when(dirLoader.load()).thenReturn(cipherDir); + } + + @Test + public void testPuttingNewEntryTriggersLoader() throws IOException { + var cipherDir = Mockito.mock(CipherDir.class); + Mockito.when(dirLoader.load()).thenReturn(cipherDir); + + var result = cache.putIfAbsent(clearPath, dirLoader); + Assertions.assertEquals(cipherDir, result); + Mockito.verify(dirLoader).load(); + } + + @Test + public void testPuttingKnownEntryDoesNotTriggerLoader() throws IOException { + Mockito.when(dirLoader.load()).thenReturn(Mockito.mock(CipherDir.class)); + var dirLoader2 = Mockito.mock(ClearToCipherDirCache.CipherDirLoader.class); + + var result = cache.putIfAbsent(clearPath, dirLoader); + var result2 = cache.putIfAbsent(clearPath, dirLoader2); + Assertions.assertEquals(result2, result); + Mockito.verify(dirLoader2, Mockito.never()).load(); + } + + @Nested + public class RemovalTest { + + CryptoPath prefixPath = Mockito.mock(CryptoPath.class); + + @Test + public void entryRemovedOnPrefixSuccess() throws IOException { + Mockito.when(clearPath.startsWith(prefixPath)).thenReturn(true); + + cache.putIfAbsent(clearPath, dirLoader); //triggers loader + cache.removeAllKeysWithPrefix(prefixPath); + cache.putIfAbsent(clearPath, dirLoader); //triggers loader + + Mockito.verify(dirLoader, Mockito.times(2)).load(); + } + + @Test + public void entryStaysOnPrefixFailure() throws IOException { + Mockito.when(clearPath.startsWith(prefixPath)).thenReturn(false); + + cache.putIfAbsent(clearPath, dirLoader); //triggers loader + cache.removeAllKeysWithPrefix(prefixPath); + cache.putIfAbsent(clearPath, dirLoader); //does not trigger + + Mockito.verify(dirLoader).load(); + } + } + + + @Nested + public class RemapTest { + + CryptoPath newClearPath; + CryptoPath oldPrefixPath; + CryptoPath newPrefixPath; + + @BeforeEach + public void beforeEach() throws IOException { + newClearPath = Mockito.mock(CryptoPath.class); + oldPrefixPath = Mockito.mock(CryptoPath.class); + newPrefixPath = Mockito.mock(CryptoPath.class); + Mockito.when(oldPrefixPath.relativize(Mockito.any())).thenReturn(oldPrefixPath); + Mockito.when(newPrefixPath.resolve((Path) Mockito.any())).thenReturn(newClearPath); + } + + @Test + public void entryRemappedOnPrefixSuccess() throws IOException { + Mockito.when(clearPath.startsWith(oldPrefixPath)).thenReturn(true); + + cache.putIfAbsent(clearPath, dirLoader); //triggers loader + cache.recomputeAllKeysWithPrefix(oldPrefixPath, newPrefixPath); + cache.putIfAbsent(clearPath, dirLoader); //does trigger + cache.putIfAbsent(newClearPath, dirLoader); //does not trigger + + Mockito.verify(dirLoader, Mockito.times(2)).load(); + } + + @Test + public void entryUntouchedOnPrefixFailure() throws IOException { + Mockito.when(clearPath.startsWith(oldPrefixPath)).thenReturn(false); + + cache.putIfAbsent(clearPath, dirLoader); //triggers loader + cache.recomputeAllKeysWithPrefix(oldPrefixPath, newPrefixPath); + cache.putIfAbsent(clearPath, dirLoader); //does not trigger + cache.putIfAbsent(newClearPath, dirLoader); //does trigger + + Mockito.verify(dirLoader, Mockito.times(2)).load(); + } + } + +} \ No newline at end of file From bffacaf93bef9214de8ab621a9ebf987227f4def Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 17 Oct 2024 16:09:18 +0200 Subject: [PATCH 06/17] removed TODOs and renamed a function --- .../cryptofs/ClearToCipherDirCache.java | 19 +++++++------- .../cryptofs/CryptoPathMapper.java | 2 +- .../cryptofs/ClearToCipherDirCacheTest.java | 26 +++++++++---------- 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java b/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java index 9adca293..eb4ccb24 100644 --- a/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java +++ b/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java @@ -24,10 +24,6 @@ public class ClearToCipherDirCache { * @param basePrefix The prefix key which the keys are checked against */ void removeAllKeysWithPrefix(CryptoPath basePrefix) { - //TODO: this a expensive operation - // with a cachesize of _n_, comparsion cost of _x_ and removal costs of y - // worstcase runtime is n*(x+y) - // worstcase example: recursivley deleting a deeply nested dir, where all child dirs are cached ciphertextDirectories.asMap().keySet().removeIf(p -> p.startsWith(basePrefix)); } @@ -39,11 +35,6 @@ void removeAllKeysWithPrefix(CryptoPath basePrefix) { * @param newPrefix the prefix key which replaces {@code oldPrefix} */ void recomputeAllKeysWithPrefix(CryptoPath oldPrefix, CryptoPath newPrefix) { - //TODO: this is a very expensive operation - // with a cache size of _n_, comparsion cost of _x_, removal costs of y and insertion costs of z - // worstcase runtime is n*x + n*y + n*z - // worstcase example: moving the root of a deeply nested dir, where all child dirs are cached - // Structure: /foo/bar/foo/bar/foo/bar...; Call: remapWithPrefix(/foo,/seb) var remappedEntries = new ArrayList(); ciphertextDirectories.asMap().entrySet().removeIf(e -> { if (e.getKey().startsWith(oldPrefix)) { @@ -56,7 +47,15 @@ void recomputeAllKeysWithPrefix(CryptoPath oldPrefix, CryptoPath newPrefix) { remappedEntries.forEach(e -> ciphertextDirectories.put(e.clearPath(), e.cipherDir())); } - CipherDir putIfAbsent(CryptoPath cleartextPath, CipherDirLoader ifAbsent) throws IOException { + + /** + * Gets the cipher directory for the given cleartext path. If a cache miss occurs, the mapping is loaded with the {@code ifAbsent} function. + * @param cleartextPath Cleartext path key + * @param ifAbsent Function to compute the (cleartextPath, cipherDir) mapping on a cache miss. + * @return a {@link CipherDir}, containing the dirId and the ciphertext content directory path + * @throws IOException if the loading function throws an IOExcecption + */ + CipherDir get(CryptoPath cleartextPath, CipherDirLoader ifAbsent) throws IOException { var futureMapping = new CompletableFuture(); var currentMapping = ciphertextDirectories.asMap().putIfAbsent(cleartextPath, futureMapping); if (currentMapping != null) { diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java index 2875c256..d0b50179 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java @@ -156,7 +156,7 @@ public CipherDir getCiphertextDir(CryptoPath cleartextPath) throws IOException { Path dirFile = getCiphertextFilePath(cleartextPath).getDirFilePath(); return resolveDirectory(dirFile); }; - return clearToCipherDirCache.putIfAbsent(cleartextPath, cipherDirLoaderIfAbsent); + return clearToCipherDirCache.get(cleartextPath, cipherDirLoaderIfAbsent); } } diff --git a/src/test/java/org/cryptomator/cryptofs/ClearToCipherDirCacheTest.java b/src/test/java/org/cryptomator/cryptofs/ClearToCipherDirCacheTest.java index 2e60c7df..67bf6bca 100644 --- a/src/test/java/org/cryptomator/cryptofs/ClearToCipherDirCacheTest.java +++ b/src/test/java/org/cryptomator/cryptofs/ClearToCipherDirCacheTest.java @@ -30,7 +30,7 @@ public void testPuttingNewEntryTriggersLoader() throws IOException { var cipherDir = Mockito.mock(CipherDir.class); Mockito.when(dirLoader.load()).thenReturn(cipherDir); - var result = cache.putIfAbsent(clearPath, dirLoader); + var result = cache.get(clearPath, dirLoader); Assertions.assertEquals(cipherDir, result); Mockito.verify(dirLoader).load(); } @@ -40,8 +40,8 @@ public void testPuttingKnownEntryDoesNotTriggerLoader() throws IOException { Mockito.when(dirLoader.load()).thenReturn(Mockito.mock(CipherDir.class)); var dirLoader2 = Mockito.mock(ClearToCipherDirCache.CipherDirLoader.class); - var result = cache.putIfAbsent(clearPath, dirLoader); - var result2 = cache.putIfAbsent(clearPath, dirLoader2); + var result = cache.get(clearPath, dirLoader); + var result2 = cache.get(clearPath, dirLoader2); Assertions.assertEquals(result2, result); Mockito.verify(dirLoader2, Mockito.never()).load(); } @@ -55,9 +55,9 @@ public class RemovalTest { public void entryRemovedOnPrefixSuccess() throws IOException { Mockito.when(clearPath.startsWith(prefixPath)).thenReturn(true); - cache.putIfAbsent(clearPath, dirLoader); //triggers loader + cache.get(clearPath, dirLoader); //triggers loader cache.removeAllKeysWithPrefix(prefixPath); - cache.putIfAbsent(clearPath, dirLoader); //triggers loader + cache.get(clearPath, dirLoader); //triggers loader Mockito.verify(dirLoader, Mockito.times(2)).load(); } @@ -66,9 +66,9 @@ public void entryRemovedOnPrefixSuccess() throws IOException { public void entryStaysOnPrefixFailure() throws IOException { Mockito.when(clearPath.startsWith(prefixPath)).thenReturn(false); - cache.putIfAbsent(clearPath, dirLoader); //triggers loader + cache.get(clearPath, dirLoader); //triggers loader cache.removeAllKeysWithPrefix(prefixPath); - cache.putIfAbsent(clearPath, dirLoader); //does not trigger + cache.get(clearPath, dirLoader); //does not trigger Mockito.verify(dirLoader).load(); } @@ -95,10 +95,10 @@ public void beforeEach() throws IOException { public void entryRemappedOnPrefixSuccess() throws IOException { Mockito.when(clearPath.startsWith(oldPrefixPath)).thenReturn(true); - cache.putIfAbsent(clearPath, dirLoader); //triggers loader + cache.get(clearPath, dirLoader); //triggers loader cache.recomputeAllKeysWithPrefix(oldPrefixPath, newPrefixPath); - cache.putIfAbsent(clearPath, dirLoader); //does trigger - cache.putIfAbsent(newClearPath, dirLoader); //does not trigger + cache.get(clearPath, dirLoader); //does trigger + cache.get(newClearPath, dirLoader); //does not trigger Mockito.verify(dirLoader, Mockito.times(2)).load(); } @@ -107,10 +107,10 @@ public void entryRemappedOnPrefixSuccess() throws IOException { public void entryUntouchedOnPrefixFailure() throws IOException { Mockito.when(clearPath.startsWith(oldPrefixPath)).thenReturn(false); - cache.putIfAbsent(clearPath, dirLoader); //triggers loader + cache.get(clearPath, dirLoader); //triggers loader cache.recomputeAllKeysWithPrefix(oldPrefixPath, newPrefixPath); - cache.putIfAbsent(clearPath, dirLoader); //does not trigger - cache.putIfAbsent(newClearPath, dirLoader); //does trigger + cache.get(clearPath, dirLoader); //does not trigger + cache.get(newClearPath, dirLoader); //does trigger Mockito.verify(dirLoader, Mockito.times(2)).load(); } From 17b0e0a031ca3e9a9f5b5f88ae4cbb2e553b80b4 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 17 Oct 2024 16:41:43 +0200 Subject: [PATCH 07/17] add integrate-like-tests in cryptoPathMapper --- .../cryptofs/CryptoPathMapperTest.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java index c8e2b844..58aac532 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java @@ -334,7 +334,113 @@ public void testGetCiphertextFileTypeForShortenedFile() throws IOException { Assertions.assertEquals(CiphertextFileType.FILE, type); } + } + + @Nested + public class InvalidateOrMovePathMapping { + + Path d00 = Mockito.mock(Path.class); + Path d0000 = Mockito.mock(Path.class, "d/00/00"); + Path d0000oof = Mockito.mock(Path.class, "d/00/00/oof.c9r"); + Path d0000oofdirFile = Mockito.mock(Path.class, "d/00/00/oof.c9r/dir.c9r"); + Path d0001 = Mockito.mock(Path.class, "d/00/01"); + Path d0001rab = Mockito.mock(Path.class, "d/00/01/rab.c9r"); + Path d0000rabdirFile = Mockito.mock(Path.class, "d/00/00/rab.c9r/dir.c9r"); + Path d0002 = Mockito.mock(Path.class); + Path d0000kik = Mockito.mock(Path.class, "d/00/00/kik.c9r"); + Path d0000kikdirFile = Mockito.mock(Path.class, "d/00/00/kik.c9r/dir.c9r"); + Path d0003 = Mockito.mock(Path.class, "d/00/03/kik.c9r"); + + @BeforeEach + void beforeEach() throws IOException { + Mockito.when(dataRoot.resolve("00")).thenReturn(d00); + Mockito.when(fileNameCryptor.hashDirectoryId("")).thenReturn("0000"); + // /foo + Mockito.when(d00.resolve("00")).thenReturn(d0000); + Mockito.when(d0000.resolve("oof.c9r")).thenReturn(d0000oof); + Mockito.when(d0000oof.resolve("dir.c9r")).thenReturn(d0000oofdirFile); + Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("foo"), Mockito.any())).thenReturn("oof"); + Mockito.when(dirIdProvider.load(d0000oofdirFile)).thenReturn("1"); + Mockito.when(fileNameCryptor.hashDirectoryId("1")).thenReturn("0001"); + + // /kik + Mockito.when(d0000.resolve("kik.c9r")).thenReturn(d0000kik); + Mockito.when(d0000kik.resolve("dir.c9r")).thenReturn(d0000kikdirFile); + Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("kik"), Mockito.any())).thenReturn("kik"); + Mockito.when(dirIdProvider.load(d0000kikdirFile)).thenReturn("3"); + Mockito.when(fileNameCryptor.hashDirectoryId("3")).thenReturn("0003"); + + // /foo/bar + Mockito.when(d00.resolve("01")).thenReturn(d0001); + Mockito.when(d0001.resolve("rab.c9r")).thenReturn(d0001rab); + Mockito.when(d0001rab.resolve("dir.c9r")).thenReturn(d0000rabdirFile); + Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("bar"), Mockito.any())).thenReturn("rab"); + Mockito.when(dirIdProvider.load(d0000rabdirFile)).thenReturn("2"); + Mockito.when(fileNameCryptor.hashDirectoryId("2")).thenReturn("0002"); + + Mockito.when(d00.resolve("02")).thenReturn(d0002); + Mockito.when(d00.resolve("03")).thenReturn(d0003); + } + + @Test + @DisplayName("Invalidating node causes cache miss on next retrieval") + public void testRemovedEntryMiss() throws IOException { + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); + var fooPath = fileSystem.getPath("/foo"); + mapper.getCiphertextDir(fooPath); + mapper.invalidatePathMapping(fooPath); + var mapperSpy = Mockito.spy(mapper); + mapperSpy.getCiphertextDir(fooPath); + Mockito.verify(mapperSpy, Mockito.atLeastOnce()).getCiphertextFilePath(fooPath); //loader is triggered, hence we have a cache miss + } + + @Test + @DisplayName("Invalidating node also invalidates all children") + public void testRemovedEntryChildMiss() throws IOException { + var fooPath = fileSystem.getPath("/foo"); + var fooBarPath = fileSystem.getPath("/foo/bar"); + + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); + mapper.getCiphertextDir(fooPath); + mapper.getCiphertextDir(fooBarPath); + mapper.invalidatePathMapping(fooPath); + var mapperSpy = Mockito.spy(mapper); + mapperSpy.getCiphertextDir(fooBarPath); + Mockito.verify(mapperSpy, Mockito.atLeastOnce()).getCiphertextFilePath(fooBarPath); //loader is triggered, hence we have a cache miss + mapperSpy.getCiphertextDir(fooBarPath); + } + + @Test + @DisplayName("Moving node causes cache miss for oldPath and cache hit for new") + public void testMoveEntryOldMissNewHit() throws IOException { + var fooPath = fileSystem.getPath("/foo"); + var kikPath = fileSystem.getPath("/kik"); + + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); + mapper.getCiphertextDir(fooPath); + mapper.movePathMapping(fooPath, kikPath); + var mapperSpy = Mockito.spy(mapper); + mapperSpy.getCiphertextDir(fooPath); + Mockito.verify(mapperSpy, Mockito.atLeastOnce()).getCiphertextFilePath(fooPath); //loader is triggered, hence we have a cache miss + Mockito.verify(mapperSpy, Mockito.never()).getCiphertextFilePath(kikPath); //loader is not triggered, hence we have a cache hit + } + + @Test + @DisplayName("Moving node causes cache miss for childs of oldPath") + public void testMoveEntryOldChildMiss() throws IOException { + var fooPath = fileSystem.getPath("/foo"); + var fooBarPath = fileSystem.getPath("/foo/bar"); + var kikPath = fileSystem.getPath("/kik"); + + CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); + mapper.getCiphertextDir(fooPath); + mapper.getCiphertextDir(fooBarPath); + mapper.movePathMapping(fooPath, kikPath); + var mapperSpy = Mockito.spy(mapper); + mapperSpy.getCiphertextDir(fooBarPath); + Mockito.verify(mapperSpy, Mockito.atLeastOnce()).getCiphertextFilePath(fooBarPath); //loader is triggered, hence we have a cache miss + } } } From 0cd494cdeb8f4a72b00488263b3bb919a6d40280 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Thu, 17 Oct 2024 17:03:31 +0200 Subject: [PATCH 08/17] doc doc doc --- .../java/org/cryptomator/cryptofs/CryptoPathMapper.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java index d0b50179..29b5a8f4 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java @@ -134,14 +134,17 @@ private String getCiphertextFileName(CipherNodeNameParameters dirIdAndName) { } /** - * TODO: doc doc doc + * Removes the given cleartext path and all cached child paths from the dir cache + * @param cleartextPath the root cleartext path, for which all mappings starting with it will be removed */ public void invalidatePathMapping(CryptoPath cleartextPath) { clearToCipherDirCache.removeAllKeysWithPrefix(cleartextPath); } /** - * TODO: doc doc doc + * Moves the given cleartext path and all cached child paths in the dir cache + * @param cleartextSrc the root cleartext path, for which alle mappings starting with it will be moved + * @param cleartextDst the destination cleartext path. The path itself and all childs will be adjusted to start with cleartextDst. */ public void movePathMapping(CryptoPath cleartextSrc, CryptoPath cleartextDst) { clearToCipherDirCache.recomputeAllKeysWithPrefix(cleartextSrc, cleartextDst); From eda73ffc426ddf9341de9a5b2a2ccf1dc23f9fbb Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 29 Oct 2024 11:55:09 +0100 Subject: [PATCH 09/17] reduce diff by reverting renames --- .../org/cryptomator/cryptofs/CipherDir.java | 14 ------- .../cryptofs/CipherNodeNameParameters.java | 13 ------ ...rDirCache.java => CiphertextDirCache.java} | 17 ++++---- .../cryptofs/CiphertextDirectory.java | 21 ++++++++++ .../cryptofs/CryptoFileSystemImpl.java | 16 +++---- .../cryptofs/CryptoFileSystemProvider.java | 2 +- .../cryptofs/CryptoPathMapper.java | 32 +++++++------- .../cryptomator/cryptofs/DirIdAndName.java | 19 +++++++++ .../cryptofs/DirectoryIdBackup.java | 12 +++--- .../attr/AbstractCryptoFileAttributeView.java | 2 +- .../cryptofs/attr/AttributeProvider.java | 2 +- .../cryptofs/dir/BrokenDirectoryFilter.java | 2 +- .../cryptofs/dir/DirectoryStreamFactory.java | 6 +-- .../health/dirid/MissingContentDir.java | 5 +-- .../health/dirid/MissingDirIdBackup.java | 6 +-- .../health/dirid/OrphanContentDir.java | 15 ++++--- ...eTest.java => CiphertextDirCacheTest.java} | 18 ++++---- .../cryptofs/CryptoFileSystemImplTest.java | 42 +++++++++---------- .../cryptofs/CryptoPathMapperTest.java | 6 +-- .../cryptofs/DirectoryIdBackupTest.java | 8 ++-- .../cryptofs/attr/AttributeProviderTest.java | 4 +- .../dir/BrokenDirectoryFilterTest.java | 6 +-- .../dir/DirectoryStreamFactoryTest.java | 8 ++-- .../health/dirid/MissingContentDirTest.java | 5 +-- .../health/dirid/MissingDirIdBackupTest.java | 5 +-- .../cryptofs/health/dirid/OrphanDirTest.java | 38 ++++++++--------- 26 files changed, 166 insertions(+), 158 deletions(-) delete mode 100644 src/main/java/org/cryptomator/cryptofs/CipherDir.java delete mode 100644 src/main/java/org/cryptomator/cryptofs/CipherNodeNameParameters.java rename src/main/java/org/cryptomator/cryptofs/{ClearToCipherDirCache.java => CiphertextDirCache.java} (79%) create mode 100644 src/main/java/org/cryptomator/cryptofs/CiphertextDirectory.java create mode 100644 src/main/java/org/cryptomator/cryptofs/DirIdAndName.java rename src/test/java/org/cryptomator/cryptofs/{ClearToCipherDirCacheTest.java => CiphertextDirCacheTest.java} (86%) diff --git a/src/main/java/org/cryptomator/cryptofs/CipherDir.java b/src/main/java/org/cryptomator/cryptofs/CipherDir.java deleted file mode 100644 index 09387953..00000000 --- a/src/main/java/org/cryptomator/cryptofs/CipherDir.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.cryptomator.cryptofs; - -import java.nio.file.Path; -import java.util.Objects; - -//own file due to dagger -public record CipherDir(String dirId, Path contentDirPath) { - - public CipherDir(String dirId, Path contentDirPath) { - this.dirId = Objects.requireNonNull(dirId); - this.contentDirPath = Objects.requireNonNull(contentDirPath); - } - -} diff --git a/src/main/java/org/cryptomator/cryptofs/CipherNodeNameParameters.java b/src/main/java/org/cryptomator/cryptofs/CipherNodeNameParameters.java deleted file mode 100644 index e27afb45..00000000 --- a/src/main/java/org/cryptomator/cryptofs/CipherNodeNameParameters.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.cryptomator.cryptofs; - -import java.util.Objects; - -//own file due to dagger -public record CipherNodeNameParameters(String dirId, String clearNodeName) { - - public CipherNodeNameParameters(String dirId, String clearNodeName) { - this.dirId = Objects.requireNonNull(dirId); - this.clearNodeName = Objects.requireNonNull(clearNodeName); - } - -} diff --git a/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java b/src/main/java/org/cryptomator/cryptofs/CiphertextDirCache.java similarity index 79% rename from src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java rename to src/main/java/org/cryptomator/cryptofs/CiphertextDirCache.java index eb4ccb24..5045a656 100644 --- a/src/main/java/org/cryptomator/cryptofs/ClearToCipherDirCache.java +++ b/src/main/java/org/cryptomator/cryptofs/CiphertextDirCache.java @@ -8,12 +8,15 @@ import java.util.ArrayList; import java.util.concurrent.CompletableFuture; -public class ClearToCipherDirCache { +/** + * Caches for the cleartext path of a directory its ciphertext path to the content directory. + */ +public class CiphertextDirCache { private static final int MAX_CACHED_PATHS = 5000; private static final Duration MAX_CACHE_AGE = Duration.ofSeconds(20); - private final AsyncCache ciphertextDirectories = Caffeine.newBuilder() // + private final AsyncCache ciphertextDirectories = Caffeine.newBuilder() // .maximumSize(MAX_CACHED_PATHS) // .expireAfterWrite(MAX_CACHE_AGE) // .buildAsync(); @@ -52,11 +55,11 @@ void recomputeAllKeysWithPrefix(CryptoPath oldPrefix, CryptoPath newPrefix) { * Gets the cipher directory for the given cleartext path. If a cache miss occurs, the mapping is loaded with the {@code ifAbsent} function. * @param cleartextPath Cleartext path key * @param ifAbsent Function to compute the (cleartextPath, cipherDir) mapping on a cache miss. - * @return a {@link CipherDir}, containing the dirId and the ciphertext content directory path + * @return a {@link CiphertextDirectory}, containing the dirId and the ciphertext content directory path * @throws IOException if the loading function throws an IOExcecption */ - CipherDir get(CryptoPath cleartextPath, CipherDirLoader ifAbsent) throws IOException { - var futureMapping = new CompletableFuture(); + CiphertextDirectory get(CryptoPath cleartextPath, CipherDirLoader ifAbsent) throws IOException { + var futureMapping = new CompletableFuture(); var currentMapping = ciphertextDirectories.asMap().putIfAbsent(cleartextPath, futureMapping); if (currentMapping != null) { return currentMapping.join(); @@ -69,10 +72,10 @@ CipherDir get(CryptoPath cleartextPath, CipherDirLoader ifAbsent) throws IOExcep @FunctionalInterface interface CipherDirLoader { - CipherDir load() throws IOException; + CiphertextDirectory load() throws IOException; } - private record CacheEntry(CryptoPath clearPath, CompletableFuture cipherDir) { + private record CacheEntry(CryptoPath clearPath, CompletableFuture cipherDir) { } diff --git a/src/main/java/org/cryptomator/cryptofs/CiphertextDirectory.java b/src/main/java/org/cryptomator/cryptofs/CiphertextDirectory.java new file mode 100644 index 00000000..4dc8f025 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/CiphertextDirectory.java @@ -0,0 +1,21 @@ +package org.cryptomator.cryptofs; + +import java.nio.file.Path; +import java.util.Objects; + +//own file due to dagger + +/** + * Represents a ciphertext directory without it's mount point in the virtual filesystem. + * + * @param dirId The (ciphertext) dir id (not encrypted, just a uuid) + * @param path The path to content directory (which contains the actual encrypted files and links to subdirectories) + */ +public record CiphertextDirectory(String dirId, Path path) { + + public CiphertextDirectory(String dirId, Path path) { + this.dirId = Objects.requireNonNull(dirId); + this.path = Objects.requireNonNull(path); + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index 2c8bd060..b0c67943 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -141,7 +141,7 @@ public Path getCiphertextPath(Path cleartextPath) throws IOException { var p = CryptoPath.castAndAssertAbsolute(cleartextPath); var nodeType = cryptoPathMapper.getCiphertextFileType(p); if (nodeType == CiphertextFileType.DIRECTORY) { - return cryptoPathMapper.getCiphertextDir(p).contentDirPath(); + return cryptoPathMapper.getCiphertextDir(p).path(); } var cipherFile = cryptoPathMapper.getCiphertextFilePath(p); if (nodeType == CiphertextFileType.SYMLINK) { @@ -315,7 +315,7 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute... attrs) throws if (cleartextParentDir == null) { return; } - Path ciphertextParentDir = cryptoPathMapper.getCiphertextDir(cleartextParentDir).contentDirPath(); + Path ciphertextParentDir = cryptoPathMapper.getCiphertextDir(cleartextParentDir).path(); if (!Files.exists(ciphertextParentDir)) { throw new NoSuchFileException(cleartextParentDir.toString()); } @@ -330,7 +330,7 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute... attrs) throws } // create dir if and only if the dirFile has been created right now (not if it has been created before): try { - Files.createDirectories(ciphertextDir.contentDirPath()); + Files.createDirectories(ciphertextDir.path()); dirIdBackup.execute(ciphertextDir); ciphertextPath.persistLongFileName(); } catch (IOException e) { @@ -431,7 +431,7 @@ private void deleteFileOrSymlink(CiphertextFilePath ciphertextPath) throws IOExc } private void deleteDirectory(CryptoPath cleartextPath, CiphertextFilePath ciphertextPath) throws IOException { - Path ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextPath).contentDirPath(); + Path ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextPath).path(); Path ciphertextDirFile = ciphertextPath.getDirFilePath(); try { ciphertextDirDeleter.deleteCiphertextDirIncludingNonCiphertextFiles(ciphertextDir, cleartextPath); @@ -504,7 +504,7 @@ private void copyDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge ciphertextTarget.persistLongFileName(); } else if (ArrayUtils.contains(options, StandardCopyOption.REPLACE_EXISTING)) { // keep existing (if empty): - Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).contentDirPath(); + Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path(); try (DirectoryStream ds = Files.newDirectoryStream(ciphertextTargetDir)) { if (ds.iterator().hasNext()) { throw new DirectoryNotEmptyException(cleartextTarget.toString()); @@ -514,8 +514,8 @@ private void copyDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge throw new FileAlreadyExistsException(cleartextTarget.toString(), null, "Ciphertext file already exists: " + ciphertextTarget); } if (ArrayUtils.contains(options, StandardCopyOption.COPY_ATTRIBUTES)) { - Path ciphertextSourceDir = cryptoPathMapper.getCiphertextDir(cleartextSource).contentDirPath(); - Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).contentDirPath(); + Path ciphertextSourceDir = cryptoPathMapper.getCiphertextDir(cleartextSource).path(); + Path ciphertextTargetDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path(); copyAttributes(ciphertextSourceDir, ciphertextTargetDir); } } @@ -621,7 +621,7 @@ private void moveDirectory(CryptoPath cleartextSource, CryptoPath cleartextTarge throw new AtomicMoveNotSupportedException(cleartextSource.toString(), cleartextTarget.toString(), "Replacing directories during move requires non-atomic status checks."); } // check if dir is empty: - Path targetCiphertextDirContentDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).contentDirPath(); + Path targetCiphertextDirContentDir = cryptoPathMapper.getCiphertextDir(cleartextTarget).path(); boolean targetCiphertextDirExists = true; try (DirectoryStream ds = Files.newDirectoryStream(targetCiphertextDirContentDir, DirectoryStreamFilters.EXCLUDE_DIR_ID_BACKUP)) { if (ds.iterator().hasNext()) { diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 6ff312bd..1360d6d7 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -155,7 +155,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2)); Files.createDirectories(vaultCipherRootPath); // create dirId backup: - DirectoryIdBackup.backupManually(cryptor, new CipherDir(Constants.ROOT_DIR_ID, vaultCipherRootPath)); + DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(Constants.ROOT_DIR_ID, vaultCipherRootPath)); } finally { Arrays.fill(rawKey, (byte) 0x00); } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java index 29b5a8f4..01633d38 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java @@ -41,10 +41,10 @@ public class CryptoPathMapper { private final DirectoryIdProvider dirIdProvider; private final LongFileNameProvider longFileNameProvider; private final VaultConfig vaultConfig; - private final LoadingCache ciphertextNames; - private final ClearToCipherDirCache clearToCipherDirCache; + private final LoadingCache ciphertextNames; + private final CiphertextDirCache ciphertextDirCache; - private final CipherDir rootDirectory; + private final CiphertextDirectory rootDirectory; @Inject CryptoPathMapper(@PathToVault Path pathToVault, Cryptor cryptor, DirectoryIdProvider dirIdProvider, LongFileNameProvider longFileNameProvider, VaultConfig vaultConfig) { @@ -54,7 +54,7 @@ public class CryptoPathMapper { this.longFileNameProvider = longFileNameProvider; this.vaultConfig = vaultConfig; this.ciphertextNames = Caffeine.newBuilder().maximumSize(MAX_CACHED_CIPHERTEXT_NAMES).build(this::getCiphertextFileName); - this.clearToCipherDirCache = new ClearToCipherDirCache(); + this.ciphertextDirCache = new CiphertextDirCache(); this.rootDirectory = resolveDirectory(Constants.ROOT_DIR_ID); } @@ -113,13 +113,13 @@ public CiphertextFilePath getCiphertextFilePath(CryptoPath cleartextPath) throws if (parentPath == null) { throw new IllegalArgumentException("Invalid file path (must have a parent): " + cleartextPath); } - CipherDir parent = getCiphertextDir(parentPath); + CiphertextDirectory parent = getCiphertextDir(parentPath); String cleartextName = cleartextPath.getFileName().toString(); - return getCiphertextFilePath(parent.contentDirPath(), parent.dirId(), cleartextName); + return getCiphertextFilePath(parent.path(), parent.dirId(), cleartextName); } public CiphertextFilePath getCiphertextFilePath(Path parentCiphertextDir, String parentDirId, String cleartextName) { - String ciphertextName = ciphertextNames.get(new CipherNodeNameParameters(parentDirId, cleartextName)); + String ciphertextName = ciphertextNames.get(new DirIdAndName(parentDirId, cleartextName)); Path c9rPath = parentCiphertextDir.resolve(ciphertextName); if (ciphertextName.length() > vaultConfig.getShorteningThreshold()) { LongFileNameProvider.DeflatedFileName deflatedFileName = longFileNameProvider.deflate(c9rPath); @@ -129,7 +129,7 @@ public CiphertextFilePath getCiphertextFilePath(Path parentCiphertextDir, String } } - private String getCiphertextFileName(CipherNodeNameParameters dirIdAndName) { + private String getCiphertextFileName(DirIdAndName dirIdAndName) { return cryptor.fileNameCryptor().encryptFilename(BaseEncoding.base64Url(), dirIdAndName.clearNodeName(), dirIdAndName.dirId().getBytes(StandardCharsets.UTF_8)) + Constants.CRYPTOMATOR_FILE_SUFFIX; } @@ -138,7 +138,7 @@ private String getCiphertextFileName(CipherNodeNameParameters dirIdAndName) { * @param cleartextPath the root cleartext path, for which all mappings starting with it will be removed */ public void invalidatePathMapping(CryptoPath cleartextPath) { - clearToCipherDirCache.removeAllKeysWithPrefix(cleartextPath); + ciphertextDirCache.removeAllKeysWithPrefix(cleartextPath); } /** @@ -147,30 +147,30 @@ public void invalidatePathMapping(CryptoPath cleartextPath) { * @param cleartextDst the destination cleartext path. The path itself and all childs will be adjusted to start with cleartextDst. */ public void movePathMapping(CryptoPath cleartextSrc, CryptoPath cleartextDst) { - clearToCipherDirCache.recomputeAllKeysWithPrefix(cleartextSrc, cleartextDst); + ciphertextDirCache.recomputeAllKeysWithPrefix(cleartextSrc, cleartextDst); } - public CipherDir getCiphertextDir(CryptoPath cleartextPath) throws IOException { + public CiphertextDirectory getCiphertextDir(CryptoPath cleartextPath) throws IOException { assert cleartextPath.isAbsolute(); if (cleartextPath.getParent() == null) { return rootDirectory; } else { - ClearToCipherDirCache.CipherDirLoader cipherDirLoaderIfAbsent = () -> { + CiphertextDirCache.CipherDirLoader cipherDirLoaderIfAbsent = () -> { Path dirFile = getCiphertextFilePath(cleartextPath).getDirFilePath(); return resolveDirectory(dirFile); }; - return clearToCipherDirCache.get(cleartextPath, cipherDirLoaderIfAbsent); + return ciphertextDirCache.get(cleartextPath, cipherDirLoaderIfAbsent); } } - public CipherDir resolveDirectory(Path directoryFile) throws IOException { + public CiphertextDirectory resolveDirectory(Path directoryFile) throws IOException { String dirId = dirIdProvider.load(directoryFile); return resolveDirectory(dirId); } - private CipherDir resolveDirectory(String dirId) { + private CiphertextDirectory resolveDirectory(String dirId) { String dirHash = cryptor.fileNameCryptor().hashDirectoryId(dirId); Path dirPath = dataRoot.resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2)); - return new CipherDir(dirId, dirPath); + return new CiphertextDirectory(dirId, dirPath); } } diff --git a/src/main/java/org/cryptomator/cryptofs/DirIdAndName.java b/src/main/java/org/cryptomator/cryptofs/DirIdAndName.java new file mode 100644 index 00000000..8367e556 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/DirIdAndName.java @@ -0,0 +1,19 @@ +package org.cryptomator.cryptofs; + +import java.util.Objects; + +//own file due to dagger + +/** + * Helper object to store the dir id of a directory along with its cleartext name (aka, the last element in the cleartext path) + * @param dirId + * @param clearNodeName + */ +record DirIdAndName(String dirId, String clearNodeName) { + + public DirIdAndName(String dirId, String clearNodeName) { + this.dirId = Objects.requireNonNull(dirId); + this.clearNodeName = Objects.requireNonNull(clearNodeName); + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java b/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java index b39feca0..6dc2f7fe 100644 --- a/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java +++ b/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java @@ -26,15 +26,15 @@ public DirectoryIdBackup(Cryptor cryptor) { } /** - * Performs the backup operation for the given {@link CipherDir} object. + * Performs the backup operation for the given {@link CiphertextDirectory} object. *

- * The directory id is written via an encrypting channel to the file {@link CipherDir#contentDirPath()} /{@value Constants#DIR_BACKUP_FILE_NAME}. + * The directory id is written via an encrypting channel to the file {@link CiphertextDirectory#path()} /{@value Constants#DIR_BACKUP_FILE_NAME}. * * @param ciphertextDirectory The cipher dir object containing the dir id and the encrypted content root * @throws IOException if an IOException is raised during the write operation */ - public void execute(CipherDir ciphertextDirectory) throws IOException { - try (var channel = Files.newByteChannel(ciphertextDirectory.contentDirPath().resolve(Constants.DIR_BACKUP_FILE_NAME), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); // + public void execute(CiphertextDirectory ciphertextDirectory) throws IOException { + try (var channel = Files.newByteChannel(ciphertextDirectory.path().resolve(Constants.DIR_BACKUP_FILE_NAME), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); // var encryptingChannel = wrapEncryptionAround(channel, cryptor)) { encryptingChannel.write(ByteBuffer.wrap(ciphertextDirectory.dirId().getBytes(StandardCharsets.US_ASCII))); } @@ -44,10 +44,10 @@ public void execute(CipherDir ciphertextDirectory) throws IOException { * Static method to explicitly back up the directory id for a specified ciphertext directory. * * @param cryptor The cryptor to be used - * @param ciphertextDirectory A {@link CipherDir} for which the dirId should be back up'd. + * @param ciphertextDirectory A {@link CiphertextDirectory} for which the dirId should be back up'd. * @throws IOException when the dirId file already exists, or it cannot be written to. */ - public static void backupManually(Cryptor cryptor, CipherDir ciphertextDirectory) throws IOException { + public static void backupManually(Cryptor cryptor, CiphertextDirectory ciphertextDirectory) throws IOException { new DirectoryIdBackup(cryptor).execute(ciphertextDirectory); } diff --git a/src/main/java/org/cryptomator/cryptofs/attr/AbstractCryptoFileAttributeView.java b/src/main/java/org/cryptomator/cryptofs/attr/AbstractCryptoFileAttributeView.java index 04e00b47..7a91c2a6 100644 --- a/src/main/java/org/cryptomator/cryptofs/attr/AbstractCryptoFileAttributeView.java +++ b/src/main/java/org/cryptomator/cryptofs/attr/AbstractCryptoFileAttributeView.java @@ -60,7 +60,7 @@ private Path getCiphertextPath(CryptoPath path) throws IOException { yield getCiphertextPath(resolved); } case DIRECTORY: - yield pathMapper.getCiphertextDir(path).contentDirPath(); + yield pathMapper.getCiphertextDir(path).path(); case FILE: yield pathMapper.getCiphertextFilePath(path).getFilePath(); }; diff --git a/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java b/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java index 17ea4751..feff23db 100644 --- a/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/attr/AttributeProvider.java @@ -53,7 +53,7 @@ public A readAttributes(CryptoPath cleartextPath private Path getCiphertextPath(CryptoPath path, CiphertextFileType type) throws IOException { return switch (type) { case SYMLINK -> pathMapper.getCiphertextFilePath(path).getSymlinkFilePath(); - case DIRECTORY -> pathMapper.getCiphertextDir(path).contentDirPath(); + case DIRECTORY -> pathMapper.getCiphertextDir(path).path(); case FILE -> pathMapper.getCiphertextFilePath(path).getFilePath(); }; } diff --git a/src/main/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilter.java b/src/main/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilter.java index 823cea0d..ba1e86ad 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilter.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilter.java @@ -28,7 +28,7 @@ public Stream process(Node node) { if (Files.isRegularFile(dirFile)) { final Path dirPath; try { - dirPath = cryptoPathMapper.resolveDirectory(dirFile).contentDirPath(); + dirPath = cryptoPathMapper.resolveDirectory(dirFile).path(); } catch (IOException e) { LOG.warn("Broken directory file: " + dirFile, e); return Stream.empty(); diff --git a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactory.java b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactory.java index 39fbb93d..77ceb6f0 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactory.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactory.java @@ -1,6 +1,6 @@ package org.cryptomator.cryptofs.dir; -import org.cryptomator.cryptofs.CipherDir; +import org.cryptomator.cryptofs.CiphertextDirectory; import org.cryptomator.cryptofs.CryptoFileSystemScoped; import org.cryptomator.cryptofs.CryptoPath; import org.cryptomator.cryptofs.CryptoPathMapper; @@ -36,8 +36,8 @@ public synchronized CryptoDirectoryStream newDirectoryStream(CryptoPath cleartex if (closed) { throw new ClosedFileSystemException(); } - CipherDir ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir); - DirectoryStream ciphertextDirStream = Files.newDirectoryStream(ciphertextDir.contentDirPath(), this::matchesEncryptedContentPattern); + CiphertextDirectory ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir); + DirectoryStream ciphertextDirStream = Files.newDirectoryStream(ciphertextDir.path(), this::matchesEncryptedContentPattern); var cleartextDirStream = directoryStreamComponentFactory.create(cleartextDir, ciphertextDir.dirId(), ciphertextDirStream, filter, streams::remove).directoryStream(); streams.put(cleartextDirStream, ciphertextDirStream); return cleartextDirStream; diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java index bdf9ec29..28cd3614 100644 --- a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java +++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java @@ -1,7 +1,6 @@ package org.cryptomator.cryptofs.health.dirid; -import org.cryptomator.cryptofs.CipherDir; -import org.cryptomator.cryptofs.CryptoPathMapper; +import org.cryptomator.cryptofs.CiphertextDirectory; import org.cryptomator.cryptofs.DirectoryIdBackup; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.common.Constants; @@ -52,7 +51,7 @@ void fix(Path pathToVault, Cryptor cryptor) throws IOException { var dirIdHash = cryptor.fileNameCryptor().hashDirectoryId(dirId); Path dirPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirIdHash.substring(0, 2)).resolve(dirIdHash.substring(2, 32)); Files.createDirectories(dirPath); - DirectoryIdBackup.backupManually(cryptor, new CipherDir(dirId, dirPath)); + DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(dirId, dirPath)); } @Override diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java index 7ab9ed46..9002ba94 100644 --- a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java +++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java @@ -1,10 +1,8 @@ package org.cryptomator.cryptofs.health.dirid; -import org.cryptomator.cryptofs.CipherDir; -import org.cryptomator.cryptofs.CryptoPathMapper; +import org.cryptomator.cryptofs.CiphertextDirectory; import org.cryptomator.cryptofs.DirectoryIdBackup; import org.cryptomator.cryptofs.VaultConfig; -import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.health.api.DiagnosticResult; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.Masterkey; @@ -31,7 +29,7 @@ public String toString() { //visible for testing void fix(Path pathToVault, Cryptor cryptor) throws IOException { Path absCipherDir = pathToVault.resolve(contentDir); - DirectoryIdBackup.backupManually(cryptor, new CipherDir(dirId, absCipherDir)); + DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(dirId, absCipherDir)); } @Override diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java index abf11454..350616a9 100644 --- a/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java +++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java @@ -1,8 +1,7 @@ package org.cryptomator.cryptofs.health.dirid; import com.google.common.io.BaseEncoding; -import org.cryptomator.cryptofs.CipherDir; -import org.cryptomator.cryptofs.CryptoPathMapper; +import org.cryptomator.cryptofs.CiphertextDirectory; import org.cryptomator.cryptofs.DirectoryIdBackup; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.common.CiphertextFileType; @@ -116,7 +115,7 @@ private void fix(Path pathToVault, VaultConfig config, Cryptor cryptor) throws I Files.deleteIfExists(orphanedDir.resolve(Constants.DIR_BACKUP_FILE_NAME)); try (var nonCryptomatorFiles = Files.newDirectoryStream(orphanedDir)) { for (Path p : nonCryptomatorFiles) { - Files.move(p, stepParentDir.contentDirPath().resolve(p.getFileName()), LinkOption.NOFOLLOW_LINKS); + Files.move(p, stepParentDir.path().resolve(p.getFileName()), LinkOption.NOFOLLOW_LINKS); } } Files.delete(orphanedDir); @@ -155,7 +154,7 @@ Path prepareRecoveryDir(Path pathToVault, FileNameCryptor cryptor) throws IOExce } // visible for testing - CipherDir prepareStepParent(Path dataDir, Path cipherRecoveryDir, Cryptor cryptor, String clearStepParentDirName) throws IOException { + CiphertextDirectory prepareStepParent(Path dataDir, Path cipherRecoveryDir, Cryptor cryptor, String clearStepParentDirName) throws IOException { //create "stepparent" directory to move orphaned files to String cipherStepParentDirName = encrypt(cryptor.fileNameCryptor(), clearStepParentDirName, Constants.RECOVERY_DIR_ID); Path cipherStepParentDirFile = cipherRecoveryDir.resolve(cipherStepParentDirName + "/" + Constants.DIR_FILE_NAME); @@ -170,7 +169,7 @@ CipherDir prepareStepParent(Path dataDir, Path cipherRecoveryDir, Cryptor crypto String stepParentDirHash = cryptor.fileNameCryptor().hashDirectoryId(stepParentUUID); Path stepParentDir = dataDir.resolve(stepParentDirHash.substring(0, 2)).resolve(stepParentDirHash.substring(2)).toAbsolutePath(); Files.createDirectories(stepParentDir); - var stepParentCipherDir = new CipherDir(stepParentUUID, stepParentDir); + var stepParentCipherDir = new CiphertextDirectory(stepParentUUID, stepParentDir); //only if it does not exist try { DirectoryIdBackup.backupManually(cryptor, stepParentCipherDir); @@ -216,11 +215,11 @@ String decryptFileName(Path orphanedResource, boolean isShortened, String dirId, } // visible for testing - void adoptOrphanedResource(Path oldCipherPath, String newClearName, boolean isShortened, CipherDir stepParentDir, FileNameCryptor cryptor, MessageDigest sha1) throws IOException { + void adoptOrphanedResource(Path oldCipherPath, String newClearName, boolean isShortened, CiphertextDirectory stepParentDir, FileNameCryptor cryptor, MessageDigest sha1) throws IOException { var newCipherName = encrypt(cryptor, newClearName, stepParentDir.dirId()); if (isShortened) { var deflatedName = BaseEncoding.base64Url().encode(sha1.digest(newCipherName.getBytes(StandardCharsets.UTF_8))) + Constants.DEFLATED_FILE_SUFFIX; - Path targetPath = stepParentDir.contentDirPath().resolve(deflatedName); + Path targetPath = stepParentDir.path().resolve(deflatedName); Files.move(oldCipherPath, targetPath); //adjust name.c9s @@ -228,7 +227,7 @@ void adoptOrphanedResource(Path oldCipherPath, String newClearName, boolean isSh fc.write(ByteBuffer.wrap(newCipherName.getBytes(StandardCharsets.UTF_8))); } } else { - Path targetPath = stepParentDir.contentDirPath().resolve(newCipherName); + Path targetPath = stepParentDir.path().resolve(newCipherName); Files.move(oldCipherPath, targetPath); } } diff --git a/src/test/java/org/cryptomator/cryptofs/ClearToCipherDirCacheTest.java b/src/test/java/org/cryptomator/cryptofs/CiphertextDirCacheTest.java similarity index 86% rename from src/test/java/org/cryptomator/cryptofs/ClearToCipherDirCacheTest.java rename to src/test/java/org/cryptomator/cryptofs/CiphertextDirCacheTest.java index 67bf6bca..9589571a 100644 --- a/src/test/java/org/cryptomator/cryptofs/ClearToCipherDirCacheTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CiphertextDirCacheTest.java @@ -9,25 +9,25 @@ import java.io.IOException; import java.nio.file.Path; -public class ClearToCipherDirCacheTest { +public class CiphertextDirCacheTest { - ClearToCipherDirCache cache; + CiphertextDirCache cache; CryptoPath clearPath; - ClearToCipherDirCache.CipherDirLoader dirLoader; + CiphertextDirCache.CipherDirLoader dirLoader; @BeforeEach public void beforeEach() throws IOException { - cache = new ClearToCipherDirCache(); + cache = new CiphertextDirCache(); clearPath = Mockito.mock(CryptoPath.class); - dirLoader = Mockito.mock(ClearToCipherDirCache.CipherDirLoader.class); - var cipherDir = Mockito.mock(CipherDir.class); + dirLoader = Mockito.mock(CiphertextDirCache.CipherDirLoader.class); + var cipherDir = Mockito.mock(CiphertextDirectory.class); Mockito.when(dirLoader.load()).thenReturn(cipherDir); } @Test public void testPuttingNewEntryTriggersLoader() throws IOException { - var cipherDir = Mockito.mock(CipherDir.class); + var cipherDir = Mockito.mock(CiphertextDirectory.class); Mockito.when(dirLoader.load()).thenReturn(cipherDir); var result = cache.get(clearPath, dirLoader); @@ -37,8 +37,8 @@ public void testPuttingNewEntryTriggersLoader() throws IOException { @Test public void testPuttingKnownEntryDoesNotTriggerLoader() throws IOException { - Mockito.when(dirLoader.load()).thenReturn(Mockito.mock(CipherDir.class)); - var dirLoader2 = Mockito.mock(ClearToCipherDirCache.CipherDirLoader.class); + Mockito.when(dirLoader.load()).thenReturn(Mockito.mock(CiphertextDirectory.class)); + var dirLoader2 = Mockito.mock(CiphertextDirCache.CipherDirLoader.class); var result = cache.get(clearPath, dirLoader); var result2 = cache.get(clearPath, dirLoader2); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java index 645cd4b4..3b7d8462 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java @@ -202,7 +202,7 @@ public void testCleartextDirectory() throws IOException { try (var cryptoPathMock = Mockito.mockStatic(CryptoPath.class)) { cryptoPathMock.when(() -> CryptoPath.castAndAssertAbsolute(any())).thenReturn(cleartext); when(cryptoPathMapper.getCiphertextFileType(any())).thenReturn(CiphertextFileType.DIRECTORY); - when(cryptoPathMapper.getCiphertextDir(any())).thenReturn(new CipherDir("foo", ciphertext)); + when(cryptoPathMapper.getCiphertextDir(any())).thenReturn(new CiphertextDirectory("foo", ciphertext)); Path result = inTest.getCiphertextPath(cleartext); Assertions.assertEquals(ciphertext, result); @@ -578,7 +578,7 @@ public void setup() throws IOException { when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath); when(ciphertextPath.getFilePath()).thenReturn(ciphertextFilePath); when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFilePath); - when(cryptoPathMapper.getCiphertextDir(cleartextPath)).thenReturn(new CipherDir("foo", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(cleartextPath)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath)); when(physicalFsProv.readAttributes(ciphertextRawPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(ciphertextPathAttr); when(physicalFsProv.readAttributes(ciphertextDirFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(ciphertextDirFilePathAttr); @@ -694,8 +694,8 @@ public void setup() throws IOException { when(ciphertextDestinationDirFile.getName(3)).thenReturn(ciphertextDestinationFileName); when(cryptoPathMapper.getCiphertextFilePath(cleartextSource)).thenReturn(ciphertextSource); when(cryptoPathMapper.getCiphertextFilePath(cleartextDestination)).thenReturn(ciphertextDestination); - when(cryptoPathMapper.getCiphertextDir(cleartextSource)).thenReturn(new CipherDir("foo", ciphertextSourceDir)); - when(cryptoPathMapper.getCiphertextDir(cleartextDestination)).thenReturn(new CipherDir("bar", ciphertextDestinationDir)); + when(cryptoPathMapper.getCiphertextDir(cleartextSource)).thenReturn(new CiphertextDirectory("foo", ciphertextSourceDir)); + when(cryptoPathMapper.getCiphertextDir(cleartextDestination)).thenReturn(new CiphertextDirectory("bar", ciphertextDestinationDir)); when(symlinks.resolveRecursively(cleartextSource)).thenReturn(sourceLinkTarget); when(symlinks.resolveRecursively(cleartextDestination)).thenReturn(destinationLinkTarget); when(cryptoPathMapper.getCiphertextFileType(sourceLinkTarget)).thenReturn(CiphertextFileType.FILE); @@ -870,8 +870,8 @@ public void setup() throws IOException, ReflectiveOperationException { when(ciphertextTargetParent.getFileSystem()).thenReturn(physicalFs); when(ciphertextDestinationDir.getFileSystem()).thenReturn(physicalFs); - when(cryptoPathMapper.getCiphertextDir(cleartextTargetParent)).thenReturn(new CipherDir("41", ciphertextTargetParent)); - when(cryptoPathMapper.getCiphertextDir(cleartextDestination)).thenReturn(new CipherDir("42", ciphertextDestinationDir)); + when(cryptoPathMapper.getCiphertextDir(cleartextTargetParent)).thenReturn(new CiphertextDirectory("41", ciphertextTargetParent)); + when(cryptoPathMapper.getCiphertextDir(cleartextDestination)).thenReturn(new CiphertextDirectory("42", ciphertextDestinationDir)); when(physicalFsProv.newFileChannel(Mockito.same(ciphertextDestinationDirFile), Mockito.anySet(), Mockito.any())).thenReturn(ciphertextTargetDirDirFileFileChannel); } @@ -1155,7 +1155,7 @@ public void createDirectoryIfPathHasNoParentDoesNothing() throws IOException { @Test public void createDirectoryIfPathsParentDoesNotExistsThrowsNoSuchFileException() throws IOException { Path ciphertextParent = mock(Path.class); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CipherDir("foo", ciphertextParent)); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("foo", ciphertextParent)); when(ciphertextParent.getFileSystem()).thenReturn(fileSystem); doThrow(NoSuchFileException.class).when(provider).checkAccess(ciphertextParent); @@ -1168,7 +1168,7 @@ public void createDirectoryIfPathsParentDoesNotExistsThrowsNoSuchFileException() @Test public void createDirectoryIfPathCiphertextFileDoesExistThrowsFileAlreadyException() throws IOException { Path ciphertextParent = mock(Path.class); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CipherDir("foo", ciphertextParent)); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("foo", ciphertextParent)); when(ciphertextParent.getFileSystem()).thenReturn(fileSystem); doThrow(new FileAlreadyExistsException(path.toString())).when(cryptoPathMapper).assertNonExisting(path); when(provider.exists(ciphertextParent)).thenReturn(true); @@ -1190,8 +1190,8 @@ public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOExceptio FileChannelMock channel = new FileChannelMock(100); when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir(dirId, ciphertextDirPath)); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CipherDir("parentDirId", ciphertextParent)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextParent)); when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class); when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath); when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile); @@ -1220,8 +1220,8 @@ public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() thro FileChannelMock channel = new FileChannelMock(100); when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir(dirId, ciphertextDirPath)); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CipherDir("parentDirId", ciphertextParent)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextParent)); when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class); when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath); when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile); @@ -1257,12 +1257,12 @@ public void createDirectoryBackupsDirIdInCiphertextDirPath() throws IOException Path ciphertextDirPath = mock(Path.class, "d/FF/FF/"); CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class, "ciphertext"); String dirId = "DirId1234ABC"; - CipherDir cipherDirObject = new CipherDir(dirId, ciphertextDirPath); + CiphertextDirectory ciphertextDirectoryObject = new CiphertextDirectory(dirId, ciphertextDirPath); FileChannelMock channel = new FileChannelMock(100); when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(cipherDirObject); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CipherDir("parentDirId", ciphertextParent)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(ciphertextDirectoryObject); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextParent)); when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class); when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath); when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile); @@ -1277,7 +1277,7 @@ public void createDirectoryBackupsDirIdInCiphertextDirPath() throws IOException inTest.createDirectory(path); verify(readonlyFlag).assertWritable(); - verify(dirIdBackup, Mockito.times(1)).execute(cipherDirObject); + verify(dirIdBackup, Mockito.times(1)).execute(ciphertextDirectoryObject); } @@ -1324,7 +1324,7 @@ public class IsHidden { public void setup() throws IOException { when(fileSystem.provider()).thenReturn(provider); when(cryptoPathMapper.getCiphertextFileType(path)).thenReturn(CiphertextFileType.DIRECTORY); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir("foo", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath)); when(ciphertextDirPath.getFileSystem()).thenReturn(fileSystem); } @@ -1372,7 +1372,7 @@ public class CheckAccess { public void setup() throws IOException { when(fileSystem.provider()).thenReturn(provider); when(cryptoPathMapper.getCiphertextFileType(path)).thenReturn(CiphertextFileType.DIRECTORY); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir("foo", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath)); when(ciphertextDirPath.getFileSystem()).thenReturn(fileSystem); } @@ -1554,7 +1554,7 @@ public void setAttributeOnRegularDirectory() throws IOException { CryptoPath path = mock(CryptoPath.class); Path ciphertextDirPath = mock(Path.class); when(cryptoPathMapper.getCiphertextFileType(path)).thenReturn(CiphertextFileType.DIRECTORY); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir("foo", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath)); inTest.setAttribute(path, name, value); @@ -1569,7 +1569,7 @@ public void setAttributeOnNonExistingRootDirectory() throws IOException { CryptoPath path = mock(CryptoPath.class); Path ciphertextDirPath = mock(Path.class); when(cryptoPathMapper.getCiphertextFileType(path)).thenReturn(CiphertextFileType.DIRECTORY); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir("foo", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath)); doThrow(new NoSuchFileException("")).when(provider).checkAccess(ciphertextDirPath); inTest.setAttribute(path, name, value); @@ -1587,7 +1587,7 @@ public void setAttributeOnFile() throws IOException { Path ciphertextFilePath = mock(Path.class); CiphertextFilePath ciphertextPath = mock(CiphertextFilePath.class); when(cryptoPathMapper.getCiphertextFileType(path)).thenReturn(CiphertextFileType.FILE); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir("foo", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory("foo", ciphertextDirPath)); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); when(ciphertextPath.getFilePath()).thenReturn(ciphertextFilePath); doThrow(new NoSuchFileException("")).when(provider).checkAccess(ciphertextDirPath); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java index 58aac532..93cdda8d 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java @@ -75,7 +75,7 @@ public void testPathEncryptionForRoot() throws IOException { Mockito.when(d00.resolve("00")).thenReturn(d0000); CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); - Path path = mapper.getCiphertextDir(fileSystem.getRootPath()).contentDirPath(); + Path path = mapper.getCiphertextDir(fileSystem.getRootPath()).path(); Assertions.assertEquals(d0000, path); } @@ -99,7 +99,7 @@ public void testPathEncryptionForFoo() throws IOException { Mockito.when(d00.resolve("01")).thenReturn(d0001); CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); - Path path = mapper.getCiphertextDir(fileSystem.getPath("/foo")).contentDirPath(); + Path path = mapper.getCiphertextDir(fileSystem.getPath("/foo")).path(); Assertions.assertEquals(d0001, path); } @@ -133,7 +133,7 @@ public void testPathEncryptionForFooBar() throws IOException { Mockito.when(d00.resolve("02")).thenReturn(d0002); CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); - Path path = mapper.getCiphertextDir(fileSystem.getPath("/foo/bar")).contentDirPath(); + Path path = mapper.getCiphertextDir(fileSystem.getPath("/foo/bar")).path(); Assertions.assertEquals(d0002, path); } diff --git a/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java b/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java index 3bf7c86b..58739e90 100644 --- a/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java @@ -22,7 +22,7 @@ public class DirectoryIdBackupTest { Path contentPath; private String dirId = "12345678"; - private CipherDir cipherDirObject; + private CiphertextDirectory ciphertextDirectoryObject; private EncryptingWritableByteChannel encChannel; private Cryptor cryptor; @@ -31,7 +31,7 @@ public class DirectoryIdBackupTest { @BeforeEach public void init() { - cipherDirObject = new CipherDir(dirId, contentPath); + ciphertextDirectoryObject = new CiphertextDirectory(dirId, contentPath); cryptor = Mockito.mock(Cryptor.class); encChannel = Mockito.mock(EncryptingWritableByteChannel.class); @@ -44,7 +44,7 @@ public void testIdFileCreated() throws IOException { backupMock.when(() -> DirectoryIdBackup.wrapEncryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(encChannel); Mockito.when(encChannel.write(Mockito.any())).thenReturn(0); - dirIdBackup.execute(cipherDirObject); + dirIdBackup.execute(ciphertextDirectoryObject); Assertions.assertTrue(Files.exists(contentPath.resolve(Constants.DIR_BACKUP_FILE_NAME))); } @@ -58,7 +58,7 @@ public void testContentIsWritten() throws IOException { try (MockedStatic backupMock = Mockito.mockStatic(DirectoryIdBackup.class)) { backupMock.when(() -> DirectoryIdBackup.wrapEncryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(encChannel); - dirIdBackup.execute(cipherDirObject); + dirIdBackup.execute(ciphertextDirectoryObject); Mockito.verify(encChannel, Mockito.times(1)).write(Mockito.argThat(b -> b.equals(expectedWrittenContent))); } diff --git a/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java b/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java index 556834db..33230151 100644 --- a/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/attr/AttributeProviderTest.java @@ -8,7 +8,7 @@ *******************************************************************************/ package org.cryptomator.cryptofs.attr; -import org.cryptomator.cryptofs.CipherDir; +import org.cryptomator.cryptofs.CiphertextDirectory; import org.cryptomator.cryptofs.CiphertextFilePath; import org.cryptomator.cryptofs.CryptoPath; import org.cryptomator.cryptofs.CryptoPathMapper; @@ -140,7 +140,7 @@ public class Directories { public void setup() throws IOException { Mockito.when(symlinks.resolveRecursively(cleartextPath)).thenReturn(cleartextPath); Mockito.when(pathMapper.getCiphertextFileType(cleartextPath)).thenReturn(CiphertextFileType.DIRECTORY); - Mockito.when(pathMapper.getCiphertextDir(cleartextPath)).thenReturn(new CipherDir("foo", ciphertextRawPath)); + Mockito.when(pathMapper.getCiphertextDir(cleartextPath)).thenReturn(new CiphertextDirectory("foo", ciphertextRawPath)); prov = new AttributeProvider(attributeComponentFactory, pathMapper, symlinks); } diff --git a/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java b/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java index 96239bf3..c2bf66a9 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/BrokenDirectoryFilterTest.java @@ -1,6 +1,6 @@ package org.cryptomator.cryptofs.dir; -import org.cryptomator.cryptofs.CipherDir; +import org.cryptomator.cryptofs.CiphertextDirectory; import org.cryptomator.cryptofs.CryptoPathMapper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -32,7 +32,7 @@ public void testProcessNormalDirectoryNode(@TempDir Path dir) throws IOException Path targetDir = Files.createDirectories(dir.resolve("d/ab/cdefg")); Files.createDirectory(dir.resolve("foo.c9r")); Files.write(dir.resolve("foo.c9r/dir.c9r"), "".getBytes()); - Mockito.when(cryptoPathMapper.resolveDirectory(Mockito.any())).thenReturn(new CipherDir("asd", targetDir)); + Mockito.when(cryptoPathMapper.resolveDirectory(Mockito.any())).thenReturn(new CiphertextDirectory("asd", targetDir)); Node unfiltered = new Node(dir.resolve("foo.c9r")); Stream result = brokenDirectoryFilter.process(unfiltered); @@ -46,7 +46,7 @@ public void testProcessNodeWithMissingTargetDir(@TempDir Path dir) throws IOExce Path targetDir = dir.resolve("d/ab/cdefg"); // not existing! Files.createDirectory(dir.resolve("foo.c9r")); Files.write(dir.resolve("foo.c9r/dir.c9r"), "".getBytes()); - Mockito.when(cryptoPathMapper.resolveDirectory(Mockito.any())).thenReturn(new CipherDir("asd", targetDir)); + Mockito.when(cryptoPathMapper.resolveDirectory(Mockito.any())).thenReturn(new CiphertextDirectory("asd", targetDir)); Node unfiltered = new Node(dir.resolve("foo.c9r")); Stream result = brokenDirectoryFilter.process(unfiltered); diff --git a/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java b/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java index 67e060f5..eada83a8 100644 --- a/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java +++ b/src/test/java/org/cryptomator/cryptofs/dir/DirectoryStreamFactoryTest.java @@ -1,6 +1,6 @@ package org.cryptomator.cryptofs.dir; -import org.cryptomator.cryptofs.CipherDir; +import org.cryptomator.cryptofs.CiphertextDirectory; import org.cryptomator.cryptofs.CryptoPath; import org.cryptomator.cryptofs.CryptoPathMapper; import org.junit.jupiter.api.Assertions; @@ -55,7 +55,7 @@ public void testNewDirectoryStreamCreatesDirectoryStream() throws IOException { String dirId = "dirIdAbc"; Path dirPath = mock(Path.class, "dirAbc"); when(dirPath.getFileSystem()).thenReturn(fileSystem); - when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CipherDir(dirId, dirPath)); + when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, dirPath)); DirectoryStream stream = mock(DirectoryStream.class); when(provider.newDirectoryStream(same(dirPath), any())).thenReturn(stream); @@ -74,8 +74,8 @@ public void testCloseClosesAllNonClosedDirectoryStreams() throws IOException { when(dirPathA.getFileSystem()).thenReturn(fileSystem); Path dirPathB = mock(Path.class, "dirPathB"); when(dirPathB.getFileSystem()).thenReturn(fileSystem); - when(cryptoPathMapper.getCiphertextDir(pathA)).thenReturn(new CipherDir("dirIdA", dirPathA)); - when(cryptoPathMapper.getCiphertextDir(pathB)).thenReturn(new CipherDir("dirIdB", dirPathB)); + when(cryptoPathMapper.getCiphertextDir(pathA)).thenReturn(new CiphertextDirectory("dirIdA", dirPathA)); + when(cryptoPathMapper.getCiphertextDir(pathB)).thenReturn(new CiphertextDirectory("dirIdB", dirPathB)); DirectoryStream streamA = mock(DirectoryStream.class, "streamA"); DirectoryStream streamB = mock(DirectoryStream.class, "streamB"); when(provider.newDirectoryStream(same(dirPathA), any())).thenReturn(streamA); diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java index a2e3330e..10e9ba35 100644 --- a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java +++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java @@ -1,7 +1,6 @@ package org.cryptomator.cryptofs.health.dirid; -import org.cryptomator.cryptofs.CipherDir; -import org.cryptomator.cryptofs.CryptoPathMapper; +import org.cryptomator.cryptofs.CiphertextDirectory; import org.cryptomator.cryptofs.DirectoryIdBackup; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptolib.api.Cryptor; @@ -61,7 +60,7 @@ public void testFix() throws IOException { result.fix(pathToVault, cryptor); var expectedPath = pathToVault.resolve("d/ri/diculous-32-char-pseudo-hashhh"); - ArgumentMatcher cipherDirMatcher = obj -> obj.dirId().equals(dirId) && obj.contentDirPath().endsWith(expectedPath); + ArgumentMatcher cipherDirMatcher = obj -> obj.dirId().equals(dirId) && obj.path().endsWith(expectedPath); dirIdBackupMock.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher)), Mockito.times(1)); var attr = Assertions.assertDoesNotThrow(() -> Files.readAttributes(expectedPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)); Assertions.assertTrue(attr.isDirectory()); diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java index 2858117e..cb97d0b7 100644 --- a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java +++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java @@ -1,7 +1,6 @@ package org.cryptomator.cryptofs.health.dirid; -import org.cryptomator.cryptofs.CipherDir; -import org.cryptomator.cryptofs.CryptoPathMapper; +import org.cryptomator.cryptofs.CiphertextDirectory; import org.cryptomator.cryptofs.DirectoryIdBackup; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptolib.api.Cryptor; @@ -44,7 +43,7 @@ public void testFix() throws IOException { result.fix(pathToVault, cryptor); var expectedPath = pathToVault.resolve(cipherDir); - ArgumentMatcher cipherDirMatcher = obj -> obj.dirId().equals(dirId) && obj.contentDirPath().isAbsolute() && obj.contentDirPath().equals(expectedPath); + ArgumentMatcher cipherDirMatcher = obj -> obj.dirId().equals(dirId) && obj.path().isAbsolute() && obj.path().equals(expectedPath); dirIdBackupMock.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher)), Mockito.times(1)); } } diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java index d11ebe45..9f9c93b2 100644 --- a/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java +++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java @@ -1,8 +1,7 @@ package org.cryptomator.cryptofs.health.dirid; import com.google.common.io.BaseEncoding; -import org.cryptomator.cryptofs.CipherDir; -import org.cryptomator.cryptofs.CryptoPathMapper; +import org.cryptomator.cryptofs.CiphertextDirectory; import org.cryptomator.cryptofs.DirectoryIdBackup; import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.common.Constants; @@ -21,7 +20,6 @@ import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.FileAlreadyExistsException; @@ -328,15 +326,15 @@ public void testAdoptOrphanedUnshortened() throws IOException { String newClearName = "OliverTwist"; Files.writeString(oldCipherPath, expectedMsg, StandardCharsets.UTF_8, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); - CipherDir stepParentDir = new CipherDir("aaaaaa", pathToVault.resolve("d/22/2222")); - Files.createDirectories(stepParentDir.contentDirPath()); + CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", pathToVault.resolve("d/22/2222")); + Files.createDirectories(stepParentDir.path()); Mockito.doReturn("adopted").when(fileNameCryptor).encryptFilename(BaseEncoding.base64Url(), newClearName, stepParentDir.dirId().getBytes(StandardCharsets.UTF_8)); var sha1 = Mockito.mock(MessageDigest.class); result.adoptOrphanedResource(oldCipherPath, newClearName, false, stepParentDir, fileNameCryptor, sha1); - Assertions.assertEquals(expectedMsg, Files.readString(stepParentDir.contentDirPath().resolve("adopted.c9r"))); + Assertions.assertEquals(expectedMsg, Files.readString(stepParentDir.path().resolve("adopted.c9r"))); Assertions.assertTrue(Files.notExists(oldCipherPath)); } @@ -349,8 +347,8 @@ public void testAdoptOrphanedShortened() throws IOException { Files.createDirectories(oldCipherPath); Files.createFile(oldCipherPath.resolve("name.c9s")); - CipherDir stepParentDir = new CipherDir("aaaaaa", pathToVault.resolve("d/22/2222")); - Files.createDirectories(stepParentDir.contentDirPath()); + CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", pathToVault.resolve("d/22/2222")); + Files.createDirectories(stepParentDir.path()); Mockito.doReturn("adopted").when(fileNameCryptor).encryptFilename(Mockito.any(), Mockito.any(), Mockito.any()); try (var baseEncodingClass = Mockito.mockStatic(BaseEncoding.class)) { @@ -364,8 +362,8 @@ public void testAdoptOrphanedShortened() throws IOException { result.adoptOrphanedResource(oldCipherPath, newClearName, true, stepParentDir, fileNameCryptor, sha1); } - Assertions.assertTrue(Files.exists(stepParentDir.contentDirPath().resolve("adopted_shortened.c9s"))); - Assertions.assertEquals("adopted.c9r", Files.readString(stepParentDir.contentDirPath().resolve("adopted_shortened.c9s/name.c9s"))); + Assertions.assertTrue(Files.exists(stepParentDir.path().resolve("adopted_shortened.c9s"))); + Assertions.assertEquals("adopted.c9r", Files.readString(stepParentDir.path().resolve("adopted_shortened.c9s/name.c9s"))); Assertions.assertTrue(Files.notExists(oldCipherPath)); } @@ -377,8 +375,8 @@ public void testAdoptOrphanedShortenedMissingNameC9s() throws IOException { String newClearName = "TomSawyer"; Files.createDirectories(oldCipherPath); - CipherDir stepParentDir = new CipherDir("aaaaaa", pathToVault.resolve("d/22/2222")); - Files.createDirectories(stepParentDir.contentDirPath()); + CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", pathToVault.resolve("d/22/2222")); + Files.createDirectories(stepParentDir.path()); Mockito.doReturn("adopted").when(fileNameCryptor).encryptFilename(Mockito.any(), Mockito.any(), Mockito.any()); try (var baseEncodingClass = Mockito.mockStatic(BaseEncoding.class)) { @@ -392,8 +390,8 @@ public void testAdoptOrphanedShortenedMissingNameC9s() throws IOException { result.adoptOrphanedResource(oldCipherPath, newClearName, true, stepParentDir, fileNameCryptor, sha1); } - Assertions.assertTrue(Files.exists(stepParentDir.contentDirPath().resolve("adopted_shortened.c9s"))); - Assertions.assertEquals("adopted.c9r", Files.readString(stepParentDir.contentDirPath().resolve("adopted_shortened.c9s/name.c9s"))); + Assertions.assertTrue(Files.exists(stepParentDir.path().resolve("adopted_shortened.c9s"))); + Assertions.assertEquals("adopted.c9r", Files.readString(stepParentDir.path().resolve("adopted_shortened.c9s/name.c9s"))); Assertions.assertTrue(Files.notExists(oldCipherPath)); } @@ -411,7 +409,7 @@ public void testFixNoDirId() throws IOException { Files.createFile(orphan1); Files.createDirectories(orphan2); - CipherDir stepParentDir = new CipherDir("aaaaaa", dataDir.resolve("22/2222")); + CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222")); VaultConfig config = Mockito.mock(VaultConfig.class); Mockito.doReturn(170).when(config).getShorteningThreshold(); @@ -444,7 +442,7 @@ public void testFixContinuesOnNotRecoverableFilename() throws IOException { var dirId = Optional.of("trololo-id"); - CipherDir stepParentDir = new CipherDir("aaaaaa", dataDir.resolve("22/2222")); + CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222")); VaultConfig config = Mockito.mock(VaultConfig.class); Mockito.doReturn(170).when(config).getShorteningThreshold(); @@ -483,7 +481,7 @@ public void testFixWithDirId() throws IOException { var dirId = Optional.of("trololo-id"); - CipherDir stepParentDir = new CipherDir("aaaaaa", dataDir.resolve("22/2222")); + CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222")); VaultConfig config = Mockito.mock(VaultConfig.class); Mockito.doReturn(170).when(config).getShorteningThreshold(); @@ -526,8 +524,8 @@ public void testFixWithNonCryptomatorFiles() throws IOException { var dirId = Optional.of("trololo-id"); - CipherDir stepParentDir = new CipherDir("aaaaaa", dataDir.resolve("22/2222")); - Files.createDirectories(stepParentDir.contentDirPath()); //needs to be created here, otherwise the Files.move(non-crypto-resource, stepparent) will fail + CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222")); + Files.createDirectories(stepParentDir.path()); //needs to be created here, otherwise the Files.move(non-crypto-resource, stepparent) will fail VaultConfig config = Mockito.mock(VaultConfig.class); Mockito.doReturn(170).when(config).getShorteningThreshold(); @@ -549,7 +547,7 @@ public void testFixWithNonCryptomatorFiles() throws IOException { Mockito.verify(resultSpy, Mockito.never()).adoptOrphanedResource(Mockito.eq(unrelated), Mockito.any(), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any()); Mockito.verify(resultSpy, Mockito.times(1)).adoptOrphanedResource(Mockito.eq(orphan1), Mockito.eq(lostName1), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any()); Mockito.verify(resultSpy, Mockito.times(1)).adoptOrphanedResource(Mockito.eq(orphan2), Mockito.eq(lostName2), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any()); - Assertions.assertTrue(Files.exists(stepParentDir.contentDirPath().resolve("unrelated.file"))); + Assertions.assertTrue(Files.exists(stepParentDir.path().resolve("unrelated.file"))); Assertions.assertTrue(Files.notExists(cipherOrphan)); } From ec01bb67a39d5ecb8ad2bcd1fced436b67b70655 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Tue, 29 Oct 2024 11:57:39 +0100 Subject: [PATCH 10/17] apply suggestions --- .../java/org/cryptomator/cryptofs/CiphertextDirCache.java | 2 +- .../java/org/cryptomator/cryptofs/CiphertextDirCacheTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/cryptomator/cryptofs/CiphertextDirCache.java b/src/main/java/org/cryptomator/cryptofs/CiphertextDirCache.java index 5045a656..699f03db 100644 --- a/src/main/java/org/cryptomator/cryptofs/CiphertextDirCache.java +++ b/src/main/java/org/cryptomator/cryptofs/CiphertextDirCache.java @@ -56,7 +56,7 @@ void recomputeAllKeysWithPrefix(CryptoPath oldPrefix, CryptoPath newPrefix) { * @param cleartextPath Cleartext path key * @param ifAbsent Function to compute the (cleartextPath, cipherDir) mapping on a cache miss. * @return a {@link CiphertextDirectory}, containing the dirId and the ciphertext content directory path - * @throws IOException if the loading function throws an IOExcecption + * @throws IOException if the loading function throws an IOException */ CiphertextDirectory get(CryptoPath cleartextPath, CipherDirLoader ifAbsent) throws IOException { var futureMapping = new CompletableFuture(); diff --git a/src/test/java/org/cryptomator/cryptofs/CiphertextDirCacheTest.java b/src/test/java/org/cryptomator/cryptofs/CiphertextDirCacheTest.java index 9589571a..9af0e613 100644 --- a/src/test/java/org/cryptomator/cryptofs/CiphertextDirCacheTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CiphertextDirCacheTest.java @@ -87,8 +87,8 @@ public void beforeEach() throws IOException { newClearPath = Mockito.mock(CryptoPath.class); oldPrefixPath = Mockito.mock(CryptoPath.class); newPrefixPath = Mockito.mock(CryptoPath.class); - Mockito.when(oldPrefixPath.relativize(Mockito.any())).thenReturn(oldPrefixPath); - Mockito.when(newPrefixPath.resolve((Path) Mockito.any())).thenReturn(newClearPath); + Mockito.when(oldPrefixPath.relativize(Mockito.any(Path.class))).thenReturn(oldPrefixPath); + Mockito.when(newPrefixPath.resolve(Mockito.any(Path.class))).thenReturn(newClearPath); } @Test From fdf3521f5a4d83ba560ecd79c12e30b2d9df22e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:38:01 +0000 Subject: [PATCH 11/17] Bump the java-test-dependencies group across 1 directory with 2 updates (#255) --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 53f8bb76..2c3531c2 100644 --- a/pom.xml +++ b/pom.xml @@ -26,8 +26,8 @@ 2.0.13 - 5.10.3 - 5.12.0 + 5.11.3 + 5.14.2 3.0 1.3.0 From 0455b3104a3a31ac50cdf14fb5ea7d82893d5d4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 11:38:43 +0000 Subject: [PATCH 12/17] Bump the maven-build-plugins group across 1 directory with 4 updates (#256) --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 2c3531c2..945ce043 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ 1.3.0 - 10.0.3 + 11.0.0 1.3.0 0.8.12 1.7.0 @@ -158,7 +158,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.3.1 + 3.5.1 me.fabriciorby @@ -197,7 +197,7 @@ maven-javadoc-plugin - 3.8.0 + 3.10.1 attach-javadocs @@ -300,7 +300,7 @@ maven-gpg-plugin - 3.2.4 + 3.2.7 sign-artifacts From 4197507b4fc38bc30b76656de4b491951f0dcc25 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 30 Oct 2024 12:50:26 +0100 Subject: [PATCH 13/17] define maven plugin versions in properties --- pom.xml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index 945ce043..8e6d6d00 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,13 @@ 1.3.0 + 3.13.0 + 3.5.1 + 3.4.2 + 3.3.1 + 3.10.1 + 3.2.7 + 11.0.0 1.3.0 0.8.12 @@ -143,7 +150,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + ${mvn-compiler.version} true @@ -158,7 +165,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.5.1 + ${mvn-surefire.version} me.fabriciorby @@ -181,11 +188,11 @@ org.apache.maven.plugins maven-jar-plugin - 3.4.2 + ${mvn-jar.version} maven-source-plugin - 3.3.1 + ${mvn-source.version} attach-sources @@ -197,7 +204,7 @@ maven-javadoc-plugin - 3.10.1 + ${mvn-javadoc.version} attach-javadocs @@ -300,7 +307,7 @@ maven-gpg-plugin - 3.2.7 + ${mvn-gpg.version} sign-artifacts From f3d35322ea4decc11de8c8c9133ffd952b8949ff Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 30 Oct 2024 12:50:50 +0100 Subject: [PATCH 14/17] explicitly mention bytebuddy as javaagent --- pom.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pom.xml b/pom.xml index 8e6d6d00..766e5a8e 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ 3.13.0 + 3.7.1 3.5.1 3.4.2 3.3.1 @@ -43,6 +44,9 @@ 1.3.0 0.8.12 1.7.0 + + + @@ -162,6 +166,19 @@ + + org.apache.maven.plugins + maven-dependency-plugin + ${mvn-dependency.version} + + + jar-paths-to-properties + + properties + + + + org.apache.maven.plugins maven-surefire-plugin @@ -183,6 +200,7 @@ + @{surefire.jacoco.args} -javaagent:${net.bytebuddy:byte-buddy-agent:jar} @@ -288,6 +306,9 @@ prepare-agent + + surefire.jacoco.args + report From 1e8a8de0babf603d27e95af7ba8ba6aea2fe175c Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 30 Oct 2024 13:07:13 +0100 Subject: [PATCH 15/17] bump slf4j --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 766e5a8e..c1aa3b9e 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ 2.51.1 33.2.1-jre 3.1.8 - 2.0.13 + 2.0.16 5.11.3 From a7accc2606f73432de24a6284efca76f1671cb0e Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 30 Oct 2024 13:08:31 +0100 Subject: [PATCH 16/17] prepare 2.7.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c1aa3b9e..79aaa787 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.cryptomator cryptofs - 2.8.0-SNAPSHOT + 2.7.1 Cryptomator Crypto Filesystem This library provides the Java filesystem provider used by Cryptomator. https://github.com/cryptomator/cryptofs From 89aacb8d4003ac3f26a2a77a4a87f797dc0feef0 Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 30 Oct 2024 15:30:34 +0100 Subject: [PATCH 17/17] identify signing key with fingerprint --- .github/workflows/publish-central.yml | 1 + .github/workflows/publish-github.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/publish-central.yml b/.github/workflows/publish-central.yml index 07727e4c..2e2882c9 100644 --- a/.github/workflows/publish-central.yml +++ b/.github/workflows/publish-central.yml @@ -36,3 +36,4 @@ jobs: MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import + MAVEN_GPG_KEY_FINGERPRINT: "58117AFA1F85B3EEC154677D615D449FE6E6A235" \ No newline at end of file diff --git a/.github/workflows/publish-github.yml b/.github/workflows/publish-github.yml index 59b312e4..5980df5c 100644 --- a/.github/workflows/publish-github.yml +++ b/.github/workflows/publish-github.yml @@ -23,6 +23,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import + MAVEN_GPG_KEY_FINGERPRINT: "58117AFA1F85B3EEC154677D615D449FE6E6A235" - name: Slack Notification uses: rtCamp/action-slack-notify@v2 env: