Skip to content

Commit

Permalink
Refactor of DirectoryStore
Browse files Browse the repository at this point in the history
  • Loading branch information
brl committed Sep 26, 2013
1 parent e31be91 commit cfaf826
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 339 deletions.
36 changes: 27 additions & 9 deletions src/com/subgraph/orchid/DirectoryStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,32 @@
import java.util.List;

public interface DirectoryStore {
ByteBuffer loadCertificates();
void saveCertificates(List<KeyCertificate> certificates);
ByteBuffer loadConsensus();
void saveConsensus(ConsensusDocument consensus);
void saveRouterDescriptors(List<RouterDescriptor> descriptors);
ByteBuffer loadRouterDescriptors();
enum CacheFile {
CERTIFICATES("certificates"),
CONSENSUS("consensus"),
CONSENSUS_MICRODESC("consensus-microdesc"),
MICRODESCRIPTOR_CACHE("cached-microdescs"),
MICRODESCRIPTOR_JOURNAL("cached-microdescs.new"),
DESCRIPTORS("routers"),
STATE("state");

void writeMicrodescriptorCache(List<RouterMicrodescriptor> descriptors, boolean removeJournal);
void appendMicrodescriptorsToJournal(List<RouterMicrodescriptor> descriptors);
ByteBuffer[] loadMicrodescriptorCache();
final private String filename;

CacheFile(String filename) {
this.filename = filename;
}

public String getFilename() {
return filename;
}
}

ByteBuffer loadCacheFile(CacheFile cacheFile);
void writeData(CacheFile cacheFile, ByteBuffer data);
void writeDocument(CacheFile cacheFile, Document document);
void writeDocumentList(CacheFile cacheFile, List<? extends Document> documents);
void appendDocumentList(CacheFile cacheFile, List<? extends Document> documents);

void removeCacheFile(CacheFile cacheFile);
void removeAllCacheFiles();
}
28 changes: 19 additions & 9 deletions src/com/subgraph/orchid/directory/DirectoryImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.subgraph.orchid.ConsensusDocument;
import com.subgraph.orchid.ConsensusDocument.ConsensusFlavor;
import com.subgraph.orchid.ConsensusDocument.RequiredCertificate;
import com.subgraph.orchid.DirectoryStore.CacheFile;
import com.subgraph.orchid.Directory;
import com.subgraph.orchid.DirectoryServer;
import com.subgraph.orchid.GuardEntry;
Expand Down Expand Up @@ -107,16 +108,16 @@ public void loadFromStore() {
boolean useMicrodescriptors = config.getUseMicrodescriptors() != AutoBoolValue.FALSE;
last = System.currentTimeMillis();
logger.info("Loading certificates");
loadCertificates(store.loadCertificates());
loadCertificates(store.loadCacheFile(CacheFile.CERTIFICATES));
logElapsed();

logger.info("Loading consensus");
loadConsensus(store.loadConsensus());
loadConsensus(store.loadCacheFile(useMicrodescriptors ? CacheFile.CONSENSUS_MICRODESC : CacheFile.CONSENSUS));
logElapsed();

if(!useMicrodescriptors) {
logger.info("Loading descriptors");
loadRouterDescriptors(store.loadRouterDescriptors());
loadRouterDescriptors(store.loadCacheFile(CacheFile.DESCRIPTORS));
logElapsed();
} else {
logger.info("Loading microdescriptor cache");
Expand All @@ -126,7 +127,7 @@ public void loadFromStore() {
}

logger.info("loading state file");
stateFile.parseBuffer(store.loadStateFile());
stateFile.parseBuffer(store.loadCacheFile(CacheFile.STATE));
logElapsed();

isLoaded = true;
Expand Down Expand Up @@ -258,7 +259,7 @@ public void storeCertificates() {
for(DirectoryServer ds: TrustedAuthorities.getInstance().getAuthorityServers()) {
certs.addAll(ds.getCertificates());
}
store.saveCertificates(certs);
store.writeDocumentList(CacheFile.CERTIFICATES, certs);
}
}

Expand All @@ -267,8 +268,13 @@ public void addRouterDescriptor(RouterDescriptor router) {
}

public void storeConsensus() {
if(currentConsensus != null)
store.saveConsensus(currentConsensus);
if(currentConsensus != null) {
if(currentConsensus.getFlavor() == ConsensusFlavor.MICRODESC) {
store.writeDocument(CacheFile.CONSENSUS_MICRODESC, currentConsensus);
} else {
store.writeDocument(CacheFile.CONSENSUS, currentConsensus);
}
}
}

public synchronized void storeDescriptors() {
Expand All @@ -281,7 +287,7 @@ public synchronized void storeDescriptors() {
descriptors.add(descriptor);
}
}
store.saveRouterDescriptors(descriptors);
store.writeDocumentList(CacheFile.DESCRIPTORS, descriptors);
descriptorsDirty = false;
}

Expand Down Expand Up @@ -326,7 +332,11 @@ public synchronized void addConsensusDocument(ConsensusDocument consensus, boole
currentConsensus = consensus;

if(!fromCache) {
store.saveConsensus(consensus);
if(consensus.getFlavor() == ConsensusFlavor.MICRODESC) {
store.writeDocument(CacheFile.CONSENSUS_MICRODESC, consensus);
} else {
store.writeDocument(CacheFile.CONSENSUS, consensus);
}
}
if(currentConsensus.getFlavor() != ConsensusFlavor.MICRODESC) {
storeDescriptors();
Expand Down
225 changes: 225 additions & 0 deletions src/com/subgraph/orchid/directory/DirectoryStoreFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package com.subgraph.orchid.directory;

import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.util.List;
import java.util.logging.Logger;

import com.subgraph.orchid.Document;
import com.subgraph.orchid.TorConfig;
import com.subgraph.orchid.crypto.TorRandom;

public class DirectoryStoreFile {
private final static Logger logger = Logger.getLogger(DirectoryStoreFile.class.getName());
private final static ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
private final static TorRandom random = new TorRandom();

private final TorConfig config;
private final String cacheFilename;

private RandomAccessFile openFile;

private boolean openFileFailed;
private boolean directoryCreationFailed;

DirectoryStoreFile(TorConfig config, String cacheFilename) {
this.config = config;
this.cacheFilename = cacheFilename;
}

public void writeData(ByteBuffer data) {
final File tempFile = createTempFile();
final FileOutputStream fos = openFileOutputStream(tempFile);
if(fos == null) {
return;
}
try {
writeAllToChannel(fos.getChannel(), data);
quietClose(fos);
installTempFile(tempFile);
} catch (IOException e) {
logger.warning("I/O error writing to temporary cache file "+ tempFile + " : "+ e);
return;
} finally {
quietClose(fos);
tempFile.delete();
}
}

public void writeDocuments(List<? extends Document> documents) {
final File tempFile = createTempFile();
final FileOutputStream fos = openFileOutputStream(tempFile);
if(fos == null) {
return;
}
try {
writeDocumentsToChannel(fos.getChannel(), documents);
quietClose(fos);
installTempFile(tempFile);
} catch (IOException e) {
logger.warning("I/O error writing to temporary cache file "+ tempFile + " : "+ e);
return;
} finally {
quietClose(fos);
tempFile.delete();
}
}

private FileOutputStream openFileOutputStream(File file) {
try {
return new FileOutputStream(file);
} catch (FileNotFoundException e) {
logger.warning("Failed to open file "+ file + " : "+ e);
return null;
}
}

public void appendDocuments(List<? extends Document> documents) {
if(!ensureOpened()) {
return;
}
try {
final FileChannel channel = openFile.getChannel();
channel.position(channel.size());
writeDocumentsToChannel(channel, documents);
channel.force(true);
} catch (IOException e) {
logger.warning("I/O error writing to cache file "+ cacheFilename);
return;
}
}

public ByteBuffer loadContents() {
if(!(fileExists() && ensureOpened())) {
return EMPTY_BUFFER;
}

try {
return readAllFromChannel(openFile.getChannel());
} catch (IOException e) {
logger.warning("I/O error reading cache file "+ cacheFilename + " : "+ e);
return EMPTY_BUFFER;
}
}

private ByteBuffer readAllFromChannel(FileChannel channel) throws IOException {
channel.position(0);
final ByteBuffer buffer = createBufferForChannel(channel);
while(buffer.hasRemaining()) {
if(channel.read(buffer) == -1) {
logger.warning("Unexpected EOF reading from cache file");
return EMPTY_BUFFER;
}
}
buffer.rewind();
return buffer;
}

private ByteBuffer createBufferForChannel(FileChannel channel) throws IOException {
final int sz = (int) (channel.size() & 0xFFFFFFFF);
return ByteBuffer.allocateDirect(sz);
}

void close() {
if(openFile != null) {
quietClose(openFile);
openFile = null;
}
}

private boolean fileExists() {
final File file = getFile();
return file.exists();
}

private boolean ensureOpened() {
if(openFileFailed) {
return false;
}
if(openFile != null) {
return true;
}
openFile = openFile();
return openFile != null;
}

private RandomAccessFile openFile() {
try {
final File f = new File(config.getDataDirectory(), cacheFilename);
createDirectoryIfMissing();
return new RandomAccessFile(f, "rw");
} catch (FileNotFoundException e) {
openFileFailed = true;
logger.warning("Failed to open cache file "+ cacheFilename);
return null;
}
}

private void installTempFile(File tempFile) {
close();
final File target = getFile();
if(target.exists() && !target.delete()) {
logger.warning("Failed to delete file "+ target);
}
if(!tempFile.renameTo(target)) {
logger.warning("Failed to rename temp file "+ tempFile +" to "+ target);
}
tempFile.delete();
ensureOpened();
}

private File createTempFile() {
final long n = random.nextLong();
final File f = new File(config.getDataDirectory(), cacheFilename + Long.toString(n));
f.deleteOnExit();
return f;
}

private void writeDocumentsToChannel(FileChannel channel, List<? extends Document> documents) throws IOException {
for(Document d: documents) {
writeAllToChannel(channel, d.getRawDocumentBytes());
}
}

private void writeAllToChannel(WritableByteChannel channel, ByteBuffer data) throws IOException {
data.rewind();
while(data.hasRemaining()) {
channel.write(data);
}
}

private void quietClose(Closeable closeable) {
try {
closeable.close();
} catch (IOException e) {}
}

private File getFile() {
return new File(config.getDataDirectory(), cacheFilename);
}

public void remove() {
close();
getFile().delete();
}

private void createDirectoryIfMissing() {
if(directoryCreationFailed) {
return;
}
final File dd = config.getDataDirectory();
if(!dd.exists()) {
if(!dd.mkdirs()) {
directoryCreationFailed = true;
logger.warning("Failed to create data directory "+ dd);
}
}
}
}
Loading

0 comments on commit cfaf826

Please sign in to comment.