diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index 57cbc58c25..dba056009d 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -329,6 +329,7 @@
org.sleuthkit.autopsy.guiutils
org.sleuthkit.autopsy.healthmonitor
org.sleuthkit.autopsy.ingest
+ org.sleuthkit.autopsy.ingest.events
org.sleuthkit.autopsy.keywordsearchservice
org.sleuthkit.autopsy.menuactions
org.sleuthkit.autopsy.modules.encryptiondetection
@@ -499,7 +500,7 @@
ext/xmpcore-5.1.3.jar
release/modules/ext/xmpcore-5.1.3.jar
-
+
ext/SparseBitSet-1.1.jar
release/modules/ext/SparseBitSet-1.1.jar
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java
index cca4ec0f93..76c3626512 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java
@@ -23,8 +23,10 @@ import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
@@ -53,11 +55,13 @@ import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javax.annotation.Nullable;
+import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.netbeans.api.progress.ProgressHandle;
import org.openide.util.Cancellable;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.Case.CaseType;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
@@ -66,6 +70,7 @@ import org.sleuthkit.autopsy.coreutils.History;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
+import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.autopsy.imagegallery.actions.UndoRedoManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
@@ -77,9 +82,11 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@@ -213,11 +220,15 @@ public final class ImageGalleryController {
listeningEnabled.addListener((observable, oldValue, newValue) -> {
try {
- //if we just turned on listening and a case is open and that case is not up to date
- if (newValue && !oldValue && ImageGalleryModule.isDrawableDBStale(Case.getCurrentCaseThrows())) {
+ // if we just turned on listening and a single-user case is open and that case is not up to date, then rebuild it
+ // For multiuser cases, we defer DB rebuild till the user actually opens Image Gallery
+ if ( newValue && !oldValue &&
+ ImageGalleryModule.isDrawableDBStale(Case.getCurrentCaseThrows()) &&
+ (Case.getCurrentCaseThrows().getCaseType() == CaseType.SINGLE_USER_CASE) ) {
//populate the db
- queueDBTask(new CopyAnalyzedFiles(instance, db, sleuthKitCase));
+ this.rebuildDB();
}
+
} catch (NoCurrentCaseException ex) {
LOGGER.log(Level.WARNING, "Exception while getting open case.", ex);
}
@@ -386,6 +397,14 @@ public final class ImageGalleryController {
}
}
+ /**
+ * Rebuilds the DrawableDB database.
+ *
+ */
+ public void rebuildDB() {
+ queueDBTask(new CopyAnalyzedFiles(instance, db, sleuthKitCase));
+ }
+
/**
* reset the state of the controller (eg if the case is closed)
*/
@@ -411,6 +430,56 @@ public final class ImageGalleryController {
db = null;
}
+ /**
+ * Checks if the datasources table in drawable DB is stale.
+ *
+ * @return true if datasources table is stale
+ */
+ boolean isDataSourcesTableStale() {
+
+ // no current case open to check
+ if ((null == getDatabase()) || (null == getSleuthKitCase())) {
+ return false;
+ }
+
+ try {
+ Set knownDataSourceIds= getDatabase().getDataSourceIds();
+ List dataSources = getSleuthKitCase().getDataSources();
+ Set caseDataSourceIds = new HashSet<>();
+ dataSources.forEach((dataSource) -> {
+ caseDataSourceIds.add(dataSource.getId());
+ });
+
+ return !(knownDataSourceIds.containsAll(caseDataSourceIds) && caseDataSourceIds.containsAll(knownDataSourceIds));
+ }
+ catch (TskCoreException ex) {
+ LOGGER.log(Level.SEVERE, "Image Gallery failed to check if datasources table is stale.", ex);
+ return false;
+ }
+
+ }
+
+ /**
+ * Update the datasources table in drawable DB.
+ *
+ */
+ private void updateDataSourcesTable() {
+ // no current case open to update
+ if ((null == getDatabase()) || (null == getSleuthKitCase())) {
+ return;
+ }
+
+ try {
+ List dataSources = getSleuthKitCase().getDataSources();
+ dataSources.forEach((dataSource) -> {
+ getDatabase().insertDataSource(dataSource.getId());
+ });
+ }
+ catch (TskCoreException ex) {
+ LOGGER.log(Level.SEVERE, "Image Gallery failed to update data_sources table.", ex);
+ }
+ }
+
synchronized private void shutDownDBExecutor() {
if (dbExecutor != null) {
dbExecutor.shutdownNow();
@@ -483,8 +552,8 @@ public final class ImageGalleryController {
void onStart() {
Platform.setImplicitExit(false);
LOGGER.info("setting up ImageGallery listeners"); //NON-NLS
- //TODO can we do anything usefull in an InjestJobEventListener?
- //IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> {});
+
+ IngestManager.getInstance().addIngestJobEventListener( new IngestJobEventListener());
IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener());
Case.addPropertyChangeListener(new CaseEventListener());
}
@@ -659,12 +728,12 @@ public final class ImageGalleryController {
"BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."})
/* Base abstract class for various methods of copying data into the Image gallery DB */
abstract static private class BulkTransferTask extends BackgroundTask {
-
+
static private final String FILE_EXTENSION_CLAUSE =
- "(name LIKE '%." //NON-NLS
- + String.join("' OR name LIKE '%.", FileTypeUtils.getAllSupportedExtensions()) //NON-NLS
- + "')";
-
+ "(extension LIKE '" //NON-NLS
+ + String.join("' OR extension LIKE '", FileTypeUtils.getAllSupportedExtensions()) //NON-NLS
+ + "') ";
+
static private final String MIMETYPE_CLAUSE =
"(mime_type LIKE '" //NON-NLS
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
@@ -683,6 +752,8 @@ public final class ImageGalleryController {
final SleuthkitCase tskCase;
ProgressHandle progressHandle;
+
+ private boolean taskCompletionStatus;
BulkTransferTask(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
this.controller = controller;
@@ -712,16 +783,22 @@ public final class ImageGalleryController {
progressHandle.switchToDeterminate(files.size());
updateProgress(0.0);
+
+ taskCompletionStatus = true;
//do in transaction
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
int workDone = 0;
for (final AbstractFile f : files) {
- if (isCancelled() || Thread.interrupted()) {
+ if (isCancelled()) {
LOGGER.log(Level.WARNING, "Task cancelled: not all contents may be transfered to drawable database."); //NON-NLS
progressHandle.finish();
break;
}
+
+ if (Thread.interrupted()) {
+ LOGGER.log(Level.WARNING, "BulkTransferTask interrupted. Ignoring it to update the contents of drawable database."); //NON-NLS
+ }
processFile(f, tr);
@@ -750,10 +827,14 @@ public final class ImageGalleryController {
updateMessage("");
updateProgress(-1.0);
}
- cleanup(true);
+ cleanup(taskCompletionStatus);
}
abstract ProgressHandle getInitialProgressHandle();
+
+ protected void setTaskCompletionStatus(boolean status) {
+ taskCompletionStatus = status;
+ }
}
/**
@@ -774,6 +855,7 @@ public final class ImageGalleryController {
@Override
protected void cleanup(boolean success) {
+ controller.updateDataSourcesTable();
controller.setStale(!success);
}
@@ -783,7 +865,7 @@ public final class ImageGalleryController {
}
@Override
- void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr) {
+ void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr) throws TskCoreException {
final boolean known = f.getKnown() == TskData.FileKnown.KNOWN;
if (known) {
@@ -791,13 +873,20 @@ public final class ImageGalleryController {
} else {
try {
- if (FileTypeUtils.hasDrawableMIMEType(f)) { //supported mimetype => analyzed
+ //supported mimetype => analyzed
+ if ( null != f.getMIMEType() && FileTypeUtils.hasDrawableMIMEType(f)) {
taskDB.updateFile(DrawableFile.create(f, true, false), tr);
- } else { //unsupported mimtype => analyzed but shouldn't include
+ }
+ else { //unsupported mimtype => analyzed but shouldn't include
+
+ // if mimetype of the file hasn't been ascertained, ingest might not have completed yet.
+ if (null == f.getMIMEType()) {
+ this.setTaskCompletionStatus(false);
+ }
taskDB.removeFile(f.getId(), tr);
}
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
- throw new RuntimeException(ex);
+ throw new TskCoreException("Failed to initialize FileTypeDetector.", ex);
}
}
}
@@ -890,28 +979,32 @@ public final class ImageGalleryController {
AbstractFile file = (AbstractFile) evt.getNewValue();
- if (isListeningEnabled()) {
- if (file.isFile()) {
- try {
- synchronized (ImageGalleryController.this) {
- if (ImageGalleryModule.isDrawableAndNotKnown(file)) {
- //this file should be included and we don't already know about it from hash sets (NSRL)
- queueDBTask(new UpdateFileTask(file, db));
- } else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) {
- //doing this check results in fewer tasks queued up, and faster completion of db update
- //this file would have gotten scooped up in initial grab, but actually we don't need it
- queueDBTask(new RemoveFileTask(file, db));
+ // only process individual files in realtime on the node that is running the ingest
+ // on a remote node, image files are processed enblock when ingest is complete
+ if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
+ if (isListeningEnabled()) {
+ if (file.isFile()) {
+ try {
+ synchronized (ImageGalleryController.this) {
+ if (ImageGalleryModule.isDrawableAndNotKnown(file)) {
+ //this file should be included and we don't already know about it from hash sets (NSRL)
+ queueDBTask(new UpdateFileTask(file, db));
+ } else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) {
+ //doing this check results in fewer tasks queued up, and faster completion of db update
+ //this file would have gotten scooped up in initial grab, but actually we don't need it
+ queueDBTask(new RemoveFileTask(file, db));
+ }
}
+ } catch (TskCoreException | FileTypeDetector.FileTypeDetectorInitException ex) {
+ //TODO: What to do here?
+ LOGGER.log(Level.SEVERE, "Unable to determine if file is drawable and not known. Not making any changes to DB", ex); //NON-NLS
+ MessageNotifyUtil.Notify.error("Image Gallery Error",
+ "Unable to determine if file is drawable and not known. Not making any changes to DB. See the logs for details.");
}
- } catch (TskCoreException | FileTypeDetector.FileTypeDetectorInitException ex) {
- //TODO: What to do here?
- LOGGER.log(Level.SEVERE, "Unable to determine if file is drawable and not known. Not making any changes to DB", ex); //NON-NLS
- MessageNotifyUtil.Notify.error("Image Gallery Error",
- "Unable to determine if file is drawable and not known. Not making any changes to DB. See the logs for details.");
}
+ } else { //TODO: keep track of what we missed for later
+ setStale(true);
}
- } else { //TODO: keep track of what we missed for later
- setStale(true);
}
break;
}
@@ -943,12 +1036,14 @@ public final class ImageGalleryController {
}
break;
case DATA_SOURCE_ADDED:
- //copy all file data to drawable databse
- Content newDataSource = (Content) evt.getNewValue();
- if (isListeningEnabled()) {
- queueDBTask(new PrePopulateDataSourceFiles(newDataSource, ImageGalleryController.this, getDatabase(), getSleuthKitCase()));
- } else {//TODO: keep track of what we missed for later
- setStale(true);
+ //For a data source added on the local node, prepopulate all file data to drawable database
+ if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
+ Content newDataSource = (Content) evt.getNewValue();
+ if (isListeningEnabled()) {
+ queueDBTask(new PrePopulateDataSourceFiles(newDataSource, ImageGalleryController.this, getDatabase(), getSleuthKitCase()));
+ } else {//TODO: keep track of what we missed for later
+ setStale(true);
+ }
}
break;
case CONTENT_TAG_ADDED:
@@ -966,4 +1061,62 @@ public final class ImageGalleryController {
}
}
}
+
+
+ /**
+ * Listener for Ingest Job events.
+ */
+ private class IngestJobEventListener implements PropertyChangeListener {
+
+ @NbBundle.Messages({
+ "ImageGalleryController.dataSourceAnalyzed.confDlg.msg= A new data source was added and finished ingest.\n" +
+ "The image / video database may be out of date. " +
+ "Do you want to update the database with ingest results?\n",
+ "ImageGalleryController.dataSourceAnalyzed.confDlg.title=Image Gallery"
+ })
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) {
+ String eventName = evt.getPropertyName();
+ if ( eventName.equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED.toString())) {
+ if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.REMOTE) {
+ // A remote node added a new data source and just finished ingest on it.
+ //drawable db is stale, and if ImageGallery is open, ask user what to do
+ setStale(true);
+
+ SwingUtilities.invokeLater(() -> {
+ if (isListeningEnabled() && ImageGalleryTopComponent.isImageGalleryOpen()) {
+
+ int answer = JOptionPane.showConfirmDialog(ImageGalleryTopComponent.getTopComponent(),
+ Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_msg(),
+ Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_title(),
+ JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
+
+ switch (answer) {
+ case JOptionPane.YES_OPTION:
+ rebuildDB();
+ break;
+ case JOptionPane.NO_OPTION:
+ case JOptionPane.CANCEL_OPTION:
+ default:
+ break; //do nothing
+ }
+ }
+ });
+ } else {
+ // received event from local node
+ // add the datasource to drawable db
+ long dsObjId = 0;
+ DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt;
+ if(event.getDataSource() != null) {
+ dsObjId = event.getDataSource().getId();
+ db.insertDataSource(dsObjId);
+ // All files for the data source have been analyzed.
+ setStale(false);
+ } else {
+ LOGGER.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent does not contain a dataSource object"); //NON-NLS
+ }
+ }
+ }
+ }
+ }
}
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java
index a5f13c1d9a..a6b57e5779 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java
@@ -86,7 +86,9 @@ public class ImageGalleryModule {
public static boolean isDrawableDBStale(Case c) {
if (c != null) {
String stale = new PerCaseProperties(c).getConfigSetting(ImageGalleryModule.MODULE_NAME, PerCaseProperties.STALE);
- return StringUtils.isNotBlank(stale) ? Boolean.valueOf(stale) : true;
+
+ return ( ImageGalleryController.getDefault().isDataSourcesTableStale() ||
+ (StringUtils.isNotBlank(stale) ? Boolean.valueOf(stale) : true) );
} else {
return false;
}
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java
index a6ce70c3ac..ce06c52893 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java
@@ -39,6 +39,7 @@ import org.openide.windows.RetainLocation;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.gui.StatusBar;
import org.sleuthkit.autopsy.imagegallery.gui.SummaryTablePane;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
@@ -88,6 +89,29 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
private VBox leftPane;
private Scene myScene;
+ /**
+ * Returns whether the ImageGallery window is open or not.
+ *
+ * @return true, if Image gallery is opened, false otherwise
+ */
+ public static boolean isImageGalleryOpen() {
+
+ final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
+ if (topComponent != null) {
+ return topComponent.isOpened();
+ }
+ return false;
+ }
+
+ /**
+ * Returns the top component window.
+ *
+ * @return Image gallery top component window, null if it's not open
+ */
+ public static TopComponent getTopComponent() {
+ return WindowManager.getDefault().findTopComponent(PREFERRED_ID);
+ }
+
public static void openTopComponent() {
//TODO:eventually move to this model, throwing away everything and rebuilding controller groupmanager etc for each case.
// synchronized (OpenTimelineAction.class) {
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java
index e10a679792..2fbc9b3758 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java
@@ -172,7 +172,18 @@ public final class OpenAction extends CallableSystemAction {
switch (answer) {
case JOptionPane.YES_OPTION:
- ImageGalleryController.getDefault().setListeningEnabled(true);
+
+ // For a single-user case, we favor user experience, and rebuild the database
+ // as soon as Image Gallery is enabled for the case.
+ // For a multi-user case, we favor overall performance and user experience, not every user may want to review images,
+ // so we rebuild the database only when a user launches Image Gallery
+ if (currentCase.getCaseType() == Case.CaseType.SINGLE_USER_CASE) {
+ ImageGalleryController.getDefault().setListeningEnabled(true);
+ }
+ else {
+ ImageGalleryController.getDefault().rebuildDB();
+ }
+
//fall through
case JOptionPane.NO_OPTION:
ImageGalleryTopComponent.openTopComponent();
diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java
index 634a7dc784..a578641c22 100644
--- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java
+++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java
@@ -47,7 +47,6 @@ import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy;
import javax.swing.SortOrder;
import org.apache.commons.lang3.StringUtils;
-import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
@@ -102,6 +101,8 @@ public final class DrawableDB {
private final PreparedStatement insertHashHitStmt;
+ private final PreparedStatement insertDataSourceStmt;
+
private final PreparedStatement updateFileStmt;
private final PreparedStatement insertFileStmt;
@@ -208,6 +209,10 @@ public final class DrawableDB {
"INSERT OR IGNORE INTO drawable_files (obj_id , path, name, created_time, modified_time, make, model, analyzed) " //NON-NLS
+ "VALUES (?,?,?,?,?,?,?,?)"); //NON-NLS
+ insertDataSourceStmt = prepareStatement(
+ "INSERT OR IGNORE INTO datasources (ds_obj_id) " //NON-NLS
+ + "VALUES (?)"); //NON-NLS
+
removeFileStmt = prepareStatement("DELETE FROM drawable_files WHERE obj_id = ?"); //NON-NLS
pathGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE path = ? ", DrawableAttribute.PATH); //NON-NLS
@@ -348,6 +353,17 @@ public final class DrawableDB {
LOGGER.log(Level.SEVERE, "problem accessing database", ex); //NON-NLS
return false;
}
+
+ try (Statement stmt = con.createStatement()) {
+ String sql = "CREATE TABLE if not exists datasources " //NON-NLS
+ + "( id INTEGER PRIMARY KEY, " //NON-NLS
+ + " ds_obj_id integer UNIQUE NOT NULL)"; //NON-NLS
+ stmt.execute(sql);
+ } catch (SQLException ex) {
+ LOGGER.log(Level.SEVERE, "problem creating datasources table", ex); //NON-NLS
+ return false;
+ }
+
try (Statement stmt = con.createStatement()) {
String sql = "CREATE TABLE if not exists drawable_files " //NON-NLS
+ "( obj_id INTEGER PRIMARY KEY, " //NON-NLS
@@ -688,6 +704,67 @@ public final class DrawableDB {
}
}
+
+ /**
+ * Gets all data source object ids from datasources table
+ *
+ * @return list of known data source object ids
+ */
+ public Set getDataSourceIds() throws TskCoreException {
+ Statement statement = null;
+ ResultSet rs = null;
+ Set ret = new HashSet<>();
+ dbReadLock();
+ try {
+ statement = con.createStatement();
+ rs = statement.executeQuery("SELECT ds_obj_id FROM datasources "); //NON-NLS
+ while (rs.next()) {
+ ret.add(rs.getLong(1));
+ }
+ } catch (SQLException e) {
+ throw new TskCoreException("SQLException while getting data source object ids", e);
+ } finally {
+ if (rs != null) {
+ try {
+ rs.close();
+ } catch (SQLException ex) {
+ LOGGER.log(Level.SEVERE, "Error closing resultset", ex); //NON-NLS
+ }
+ }
+ if (statement != null) {
+ try {
+ statement.close();
+ } catch (SQLException ex) {
+ LOGGER.log(Level.SEVERE, "Error closing statement ", ex); //NON-NLS
+ }
+ }
+ dbReadUnlock();
+ }
+ return ret;
+ }
+
+
+ /**
+ * Insert given data source object id into datasources table
+ *
+ * If the object id exists in the table already, it does nothing.
+ *
+ * @param dsObjectId data source object id to insert
+ */
+ public void insertDataSource(long dsObjectId) {
+ dbWriteLock();
+ try {
+ // "INSERT OR IGNORE/ INTO datasources (ds_obj_id)"
+ insertDataSourceStmt.setLong(1,dsObjectId);
+
+ insertDataSourceStmt.executeUpdate();
+ } catch (SQLException | NullPointerException ex) {
+ LOGGER.log(Level.SEVERE, "failed to insert/update datasources table", ex); //NON-NLS
+ } finally {
+ dbWriteUnlock();
+ }
+ }
+
public DrawableTransaction beginTransaction() {
return new DrawableTransaction();
}