Skip to content

Commit

Permalink
Merge pull request #164 from cryptomator/feature/caffeine
Browse files Browse the repository at this point in the history
migrated from Guava Cache to Coffeine
  • Loading branch information
infeo authored Mar 30, 2023
2 parents dc0e99f + cb9a4ff commit 32650a9
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 58 deletions.
31 changes: 16 additions & 15 deletions src/main/java/org/cryptomator/cryptofs/CryptoPathMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
*******************************************************************************/
package org.cryptomator.cryptofs;

import com.google.common.base.Throwables;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.io.BaseEncoding;
import org.cryptomator.cryptofs.common.CiphertextFileType;
import org.cryptomator.cryptofs.common.Constants;
Expand All @@ -22,6 +20,7 @@

import javax.inject.Inject;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
Expand All @@ -32,7 +31,6 @@
import java.time.Duration;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutionException;

import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME;

Expand Down Expand Up @@ -61,8 +59,8 @@ public class CryptoPathMapper {
this.dirIdProvider = dirIdProvider;
this.longFileNameProvider = longFileNameProvider;
this.vaultConfig = vaultConfig;
this.ciphertextNames = CacheBuilder.newBuilder().maximumSize(MAX_CACHED_CIPHERTEXT_NAMES).build(CacheLoader.from(this::getCiphertextFileName));
this.ciphertextDirectories = CacheBuilder.newBuilder().maximumSize(MAX_CACHED_DIR_PATHS).expireAfterWrite(MAX_CACHE_AGE).build();
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).build();
this.rootDirectory = resolveDirectory(Constants.ROOT_DIR_ID);
}

Expand Down Expand Up @@ -127,7 +125,7 @@ public CiphertextFilePath getCiphertextFilePath(CryptoPath cleartextPath) throws
}

public CiphertextFilePath getCiphertextFilePath(Path parentCiphertextDir, String parentDirId, String cleartextName) {
String ciphertextName = ciphertextNames.getUnchecked(new DirIdAndName(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);
Expand Down Expand Up @@ -160,13 +158,16 @@ public CiphertextDirectory getCiphertextDir(CryptoPath cleartextPath) throws IOE
return rootDirectory;
} else {
try {
return ciphertextDirectories.get(cleartextPath, () -> {
Path dirFile = getCiphertextFilePath(cleartextPath).getDirFilePath();
return resolveDirectory(dirFile);
return ciphertextDirectories.get(cleartextPath, p -> {
try {
Path dirFile = getCiphertextFilePath(p).getDirFilePath();
return resolveDirectory(dirFile);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
} catch (ExecutionException e) {
Throwables.throwIfInstanceOf(e.getCause(), IOException.class);
throw new IOException("Unexpected exception", e);
} catch (UncheckedIOException e) {
throw new IOException(e);
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions src/main/java/org/cryptomator/cryptofs/DirectoryIdLoader.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package org.cryptomator.cryptofs;

import com.google.common.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.CacheLoader;

import javax.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
Expand All @@ -14,7 +15,7 @@
import java.util.UUID;

@CryptoFileSystemScoped
class DirectoryIdLoader extends CacheLoader<Path, String> {
class DirectoryIdLoader implements CacheLoader<Path, String> {

private static final int MAX_DIR_ID_LENGTH = 1000;

Expand All @@ -23,7 +24,7 @@ public DirectoryIdLoader() {
}

@Override
public String load(Path dirFilePath) throws IOException {
public String load(Path dirFilePath) throws UncheckedIOException {
try (FileChannel ch = FileChannel.open(dirFilePath, StandardOpenOption.READ);
InputStream in = Channels.newInputStream(ch)) {
long size = ch.size();
Expand All @@ -39,6 +40,8 @@ public String load(Path dirFilePath) throws IOException {
}
} catch (NoSuchFileException e) {
return UUID.randomUUID().toString();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

Expand Down
17 changes: 9 additions & 8 deletions src/main/java/org/cryptomator/cryptofs/DirectoryIdProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
*******************************************************************************/
package org.cryptomator.cryptofs;

import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.ExecutionException;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;

import javax.inject.Inject;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.LoadingCache;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;

@CryptoFileSystemScoped
class DirectoryIdProvider {
Expand All @@ -26,13 +26,13 @@ class DirectoryIdProvider {

@Inject
public DirectoryIdProvider(DirectoryIdLoader directoryIdLoader) {
ids = CacheBuilder.newBuilder().maximumSize(MAX_CACHE_SIZE).build(directoryIdLoader);
this.ids = Caffeine.newBuilder().maximumSize(MAX_CACHE_SIZE).build(directoryIdLoader);
}

public String load(Path dirFilePath) throws IOException {
try {
return ids.get(dirFilePath);
} catch (ExecutionException e) {
} catch (UncheckedIOException e) {
throw new IOException("Failed to load contents of directory file at path " + dirFilePath, e);
}
}
Expand Down Expand Up @@ -62,4 +62,5 @@ public void move(Path srcDirFilePath, Path dstDirFilePath) {
}
}


}
44 changes: 19 additions & 25 deletions src/main/java/org/cryptomator/cryptofs/LongFileNameProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
*******************************************************************************/
package org.cryptomator.cryptofs;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.io.BaseEncoding;
import org.cryptomator.cryptolib.common.MessageDigestSupplier;

Expand All @@ -24,7 +23,6 @@
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.time.Duration;
import java.util.concurrent.ExecutionException;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.cryptomator.cryptofs.common.Constants.DEFLATED_FILE_SUFFIX;
Expand All @@ -39,31 +37,28 @@ public class LongFileNameProvider {
private static final Duration MAX_CACHE_AGE = Duration.ofMinutes(1);

private final ReadonlyFlag readonlyFlag;
private final LoadingCache<Path, String> longNames; // Maps from c9s paths to inflated filenames
private final Cache<Path, String> longNames; // Maps from c9s paths to inflated filenames

@Inject
public LongFileNameProvider(ReadonlyFlag readonlyFlag) {
this.readonlyFlag = readonlyFlag;
this.longNames = CacheBuilder.newBuilder().expireAfterAccess(MAX_CACHE_AGE).build(new Loader());
this.longNames = Caffeine.newBuilder().expireAfterAccess(MAX_CACHE_AGE).build();
}

private class Loader extends CacheLoader<Path, String> {

@Override
public String load(Path c9sPath) throws IOException {
Path longNameFile = c9sPath.resolve(INFLATED_FILE_NAME);
try (SeekableByteChannel ch = Files.newByteChannel(longNameFile, StandardOpenOption.READ)) {
if (ch.size() > MAX_FILENAME_BUFFER_SIZE) {
throw new IOException("Unexpectedly large file: " + longNameFile);
}
assert ch.size() <= MAX_FILENAME_BUFFER_SIZE;
ByteBuffer buf = ByteBuffer.allocate((int) ch.size());
ch.read(buf);
buf.flip();
return UTF_8.decode(buf).toString();
private String load(Path c9sPath) throws UncheckedIOException {
Path longNameFile = c9sPath.resolve(INFLATED_FILE_NAME);
try (SeekableByteChannel ch = Files.newByteChannel(longNameFile, StandardOpenOption.READ)) {
if (ch.size() > MAX_FILENAME_BUFFER_SIZE) {
throw new IOException("Unexpectedly large file: " + longNameFile);
}
assert ch.size() <= MAX_FILENAME_BUFFER_SIZE;
ByteBuffer buf = ByteBuffer.allocate((int) ch.size());
ch.read(buf);
buf.flip();
return UTF_8.decode(buf).toString();
} catch (IOException e) {
throw new UncheckedIOException(e);
}

}

public boolean isDeflated(String possiblyDeflatedFileName) {
Expand All @@ -72,10 +67,9 @@ public boolean isDeflated(String possiblyDeflatedFileName) {

public String inflate(Path c9sPath) throws IOException {
try {
return longNames.get(c9sPath);
} catch (ExecutionException e) {
Throwables.throwIfInstanceOf(e.getCause(), IOException.class);
throw new IllegalStateException("Unexpected exception", e);
return longNames.get(c9sPath, this::load);
} catch (UncheckedIOException e) {
throw e.getCause(); // rethrow original to keep exception types such as NoSuchFileException
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.mockito.Mockito;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.FileSystem;
Expand Down Expand Up @@ -83,10 +84,10 @@ public void testIOExceptionWhenExistingFileIsEmpty() throws IOException {
when(provider.newFileChannel(eq(dirFilePath), any())).thenReturn(channel);
when(channel.size()).thenReturn(0l);

IOException e = Assertions.assertThrows(IOException.class, () -> {
UncheckedIOException e = Assertions.assertThrows(UncheckedIOException.class, () -> {
inTest.load(dirFilePath);
});
MatcherAssert.assertThat(e.getMessage(), containsString("Invalid, empty directory file"));
MatcherAssert.assertThat(e.getCause().getMessage(), containsString("Invalid, empty directory file"));
}

@Test
Expand All @@ -95,10 +96,10 @@ public void testIOExceptionWhenExistingFileIsTooLarge() throws IOException {
when(provider.newFileChannel(eq(dirFilePath), any())).thenReturn(channel);
when(channel.size()).thenReturn((long) Integer.MAX_VALUE);

IOException e = Assertions.assertThrows(IOException.class, () -> {
UncheckedIOException e = Assertions.assertThrows(UncheckedIOException.class, () -> {
inTest.load(dirFilePath);
});
MatcherAssert.assertThat(e.getMessage(), containsString("Unexpectedly large directory file"));
MatcherAssert.assertThat(e.getCause().getMessage(), containsString("Unexpectedly large directory file"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.mockito.verification.VerificationMode;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.concurrent.ExecutionException;

Expand Down Expand Up @@ -38,14 +39,14 @@ public void testLoadInvokesLoader() throws IOException {
}

@Test
public void testIOExceptionFromLoaderIsWrappedAndRethrown() throws IOException {
public void testIOExceptionFromLoaderIsWrappedAndRethrown() {
IOException originalIoException = new IOException();
when(loader.load(aPath)).thenThrow(originalIoException);
when(loader.load(aPath)).thenThrow(new UncheckedIOException(originalIoException));

IOException e = Assertions.assertThrows(IOException.class, () -> {
inTest.load(aPath);
});
Assertions.assertTrue(e.getCause() instanceof ExecutionException);
Assertions.assertTrue(e.getCause() instanceof UncheckedIOException);
Assertions.assertEquals(originalIoException, e.getCause().getCause());
}

Expand Down

0 comments on commit 32650a9

Please sign in to comment.