Skip to content

Commit

Permalink
added metadata caching
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Stenzel committed May 29, 2015
1 parent 49646aa commit 03cdf1f
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
Expand All @@ -15,6 +16,8 @@
import java.util.Map;
import java.util.UUID;

import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.apache.commons.collections4.map.LRUMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
Expand All @@ -23,16 +26,19 @@
import org.cryptomator.crypto.exceptions.DecryptFailedException;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

class FilenameTranslator implements FileConstants {

private static final int MAX_CACHED_DIRECTORY_IDS = 5000;
private static final int MAX_CACHED_METADATA_FILES = 1000;

private final Cryptor cryptor;
private final Path dataRoot;
private final Path metadataRoot;
private final ObjectMapper objectMapper = new ObjectMapper();
private final Map<Pair<Path, FileTime>, String> directoryIdCache = new LRUMap<>(MAX_CACHED_DIRECTORY_IDS); // <directoryFile, directoryId>
private final Map<Pair<Path, FileTime>, LongFilenameMetadata> metadataCache = new LRUMap<>(MAX_CACHED_METADATA_FILES); // <metadataFile, metadata>

public FilenameTranslator(Cryptor cryptor, Path vaultRoot) {
this.cryptor = cryptor;
Expand Down Expand Up @@ -121,39 +127,100 @@ public String getCleartextFilename(String encryptedFilename) throws DecryptFaile
return cryptor.decryptFilename(ciphertext);
}

/* Locked I/O */

private void writeAllBytesAtomically(Path path, byte[] bytes) throws IOException {
try (final FileChannel c = FileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC); final FileLock lock = c.lock()) {
c.write(ByteBuffer.wrap(bytes));
}
}

private byte[] readAllBytesAtomically(Path path) throws IOException {
try (final FileChannel c = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.DSYNC); final FileLock lock = c.lock(0L, Long.MAX_VALUE, true)) {
final ByteBuffer buffer = ByteBuffer.allocate((int) c.size());
c.read(buffer);
return buffer.array();
}
}

/* Long name metadata files */

private void writeMetadata(String metadataGroup, LongFilenameMetadata metadata) throws IOException {
final Path metadataDir = metadataRoot.resolve(metadataGroup.substring(0, 2));
Files.createDirectories(metadataDir);
final Path metadataFile = metadataDir.resolve(metadataGroup.substring(2));

// evict previously cached entries:
try {
final Pair<Path, FileTime> key = ImmutablePair.of(metadataFile, Files.getLastModifiedTime(metadataFile));
metadataCache.remove(key);
} catch (FileNotFoundException | NoSuchFileException e) {
// didn't exist yet? then we don't need to do anything anyway.
}

// write:
final byte[] metadataContent = objectMapper.writeValueAsBytes(metadata);
writeAllBytesAtomically(metadataFile, metadataContent);

// add to cache:
final Pair<Path, FileTime> key = ImmutablePair.of(metadataFile, Files.getLastModifiedTime(metadataFile));
metadataCache.put(key, metadata);
}

private LongFilenameMetadata readMetadata(String metadataGroup) throws IOException {
final Path metadataDir = metadataRoot.resolve(metadataGroup.substring(0, 2));
final Path metadataFile = metadataDir.resolve(metadataGroup.substring(2));
if (!Files.isReadable(metadataFile)) {
try {
// use cached metadata, if possible:
final Pair<Path, FileTime> key = ImmutablePair.of(metadataFile, Files.getLastModifiedTime(metadataFile));
LongFilenameMetadata metadata = metadataCache.get(key);
// else read from filesystem:
if (metadata == null) {
final byte[] metadataContent = readAllBytesAtomically(metadataFile);
metadata = objectMapper.readValue(metadataContent, LongFilenameMetadata.class);
metadataCache.put(key, metadata);
}
return metadata;
} catch (FileNotFoundException | NoSuchFileException e) {
// not yet existing:
return new LongFilenameMetadata();
} else {
final byte[] metadataContent = readAllBytesAtomically(metadataFile);
return objectMapper.readValue(metadataContent, LongFilenameMetadata.class);
}
}

private void writeAllBytesAtomically(Path path, byte[] bytes) throws IOException {
try (final FileChannel c = FileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC); final FileLock lock = c.lock()) {
c.write(ByteBuffer.wrap(bytes));
private static class LongFilenameMetadata implements Serializable {

private static final long serialVersionUID = 6214509403824421320L;

@JsonDeserialize(as = DualHashBidiMap.class)
private BidiMap<UUID, String> encryptedFilenames = new DualHashBidiMap<>();

/* Getter/Setter */

public synchronized String getEncryptedFilenameForUUID(final UUID uuid) {
return encryptedFilenames.get(uuid);
}
}

private byte[] readAllBytesAtomically(Path path) throws IOException {
try (final FileChannel c = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.DSYNC); final FileLock lock = c.lock(0L, Long.MAX_VALUE, true)) {
final ByteBuffer buffer = ByteBuffer.allocate((int) c.size());
c.read(buffer);
return buffer.array();
public synchronized UUID getOrCreateUuidForEncryptedFilename(String encryptedFilename) {
UUID uuid = encryptedFilenames.getKey(encryptedFilename);
if (uuid == null) {
uuid = UUID.randomUUID();
encryptedFilenames.put(uuid, encryptedFilename);
}
return uuid;
}

// used by jackson
@SuppressWarnings("unused")
public BidiMap<UUID, String> getEncryptedFilenames() {
return encryptedFilenames;
}

// used by jackson
@SuppressWarnings("unused")
public void setEncryptedFilenames(BidiMap<UUID, String> encryptedFilenames) {
this.encryptedFilenames = encryptedFilenames;
}

}

}

This file was deleted.

0 comments on commit 03cdf1f

Please sign in to comment.