-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
314 additions
and
339 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
225 changes: 225 additions & 0 deletions
225
src/com/subgraph/orchid/directory/DirectoryStoreFile.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.