diff --git a/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java b/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java index 50f0cc17e2f1ee67d2c8ded378dcd7e24b97e831..db07ed5d4d6a8188fd687f8ff55f33dbbba5f76a 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java +++ b/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java @@ -21,6 +21,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; +import java.lang.ref.SoftReference; import java.sql.SQLException; import java.sql.Statement; import java.text.MessageFormat; @@ -118,8 +119,9 @@ public abstract class AbstractFile extends AbstractContent { private volatile String uniquePath; private volatile FileSystem parentFileSystem; - private ContentStream contentStream; private boolean tryContentStream; + private Object contentStreamLock = new Object(); + private SoftReference<ContentStream> contentStreamRef = null; /** * Initializes common fields used by AbstactFile implementations (objects in @@ -1070,44 +1072,54 @@ short getMetaFlagsAsInt() { return TSK_FS_META_FLAG_ENUM.toInt(metaFlags); } - /** - * Attempts to load the content stream for this file. If none exists, returns false. - * @return False if no content stream exists for this file. - * @throws TskCoreException - */ - private boolean loadContentStream() throws TskCoreException { - if (contentStream != null) { - return true; - } else if (tryContentStream) { - // only attempt to load if the flag indicates it should be tried - contentStream = getSleuthkitCase().getContentProvider().getContentStream(this).orElse(null); - - if (contentStream == null) { - // if no content stream could be loaded, mark tryContentStream as false so load - // isn't attempted again - tryContentStream = false; - return false; - } else { - return true; - } - } else { - return false; + + + private boolean hasContentStream() { + ContentStream contentStream = null; + try { + contentStream = getContentStream(); + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "An error occurred while loading content stream for file with id: " + getId(), ex); } + return contentStream != null; } /** - * @return True if the custom content provider for the case should be used to fetch content bytes. + * Attempts to load the content stream for this file. If none exists, returns null. + * @return The content stream for this file or null if none exists. + * @throws TskCoreException */ - private boolean useContentProvider() { - return this.getCollected() == CollectedStatus.YES_REPO && getSleuthkitCase().getContentProvider() != null; + private ContentStream getContentStream() throws TskCoreException { + ContentStream contentStream = null; + if (tryContentStream) { + try { + synchronized (contentStreamLock) { + // try to get soft reference content stream + contentStream = contentStreamRef == null ? null : contentStreamRef.get(); + // load if not cached and then cache if present + if (contentStream == null) { + contentStream = getSleuthkitCase().getContentProvider().getContentStream(this).orElse(null); + if (contentStream != null) { + this.contentStreamRef = new SoftReference<>(contentStream); + } + } + } + } finally { + if (contentStream == null) { + // don't try to load the content stream again if it fails to load (either through exception or not existing) + tryContentStream = false; + } + } + } + return contentStream; } - @Override public final int read(byte[] buf, long offset, long len) throws TskCoreException { - //template method - if (useContentProvider() && loadContentStream()) { - return this.contentStream.read(buf, offset, len); + // try to use content stream if present + ContentStream contentStream = getContentStream(); + if (contentStream != null) { + return contentStream.read(buf, offset, len); } //if localPath is set, use local, otherwise, use readCustom() supplied by derived class @@ -1283,13 +1295,8 @@ final void setEncodingType(TskData.EncodingType encodingType) { * @return true if the file exists, false otherwise */ public boolean exists() { - if (useContentProvider()) { - try { - return loadContentStream(); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, ex.getMessage()); - return false; - } + if (hasContentStream()) { + return true; } if (!localPathSet) { @@ -1313,13 +1320,8 @@ public boolean exists() { * @return true if the file is readable */ public boolean canRead() { - if (useContentProvider()) { - try { - return loadContentStream(); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, ex.getMessage()); - return false; - } + if (hasContentStream()) { + return true; } if (!localPathSet) { diff --git a/bindings/java/src/org/sleuthkit/datamodel/ContentStream.java b/bindings/java/src/org/sleuthkit/datamodel/ContentStream.java index 8037219c322d936593574874536f4f9f93ba2cac..00055d0a78188f1dada7745736c29747fdeecccb 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/ContentStream.java +++ b/bindings/java/src/org/sleuthkit/datamodel/ContentStream.java @@ -23,7 +23,7 @@ /** * Custom provider for content bytes. */ -public interface ContentStream { +public interface ContentStream extends AutoCloseable { /** * Reads data that this content object is associated with (file contents,