From 063d0a6b2f480a7152618398c238977e44c72364 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 28 Jun 2018 14:48:25 -0400 Subject: [PATCH 01/84] 999: Migrate groups table to CaseDB --- .../imagegallery/datamodel/DrawableDB.java | 121 ++++++++++-------- .../datamodel/grouping/GroupManager.java | 15 ++- 2 files changed, 83 insertions(+), 53 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 51d58c7f1e..01185d4288 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,6 +47,7 @@ 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; @@ -62,8 +63,11 @@ import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.DBAccessManager; +import org.sleuthkit.datamodel.DBAccessQueryCallback; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData.DbType; import org.sqlite.SQLiteJDBCLoader; /** @@ -84,19 +88,15 @@ public final class DrawableDB { private static final String OBJ_ID = "obj_id"; //NON-NLS private static final String HASH_SET_NAME = "hash_set_name"; //NON-NLS + + private static final String GROUPS_TABLENAME = "ig_groups"; //NON-NLS private final PreparedStatement insertHashSetStmt; - private final PreparedStatement groupSeenQueryStmt; - - private final PreparedStatement insertGroupStmt; - private final List preparedStatements = new ArrayList<>(); private final PreparedStatement removeFileStmt; - private final PreparedStatement updateGroupStmt; - private final PreparedStatement selectHashSetStmt; private final PreparedStatement selectHashSetNamesStmt; @@ -220,11 +220,6 @@ public final class DrawableDB { analyzedGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE analyzed = ?", DrawableAttribute.ANALYZED); //NON-NLS hashSetGroupStmt = prepareStatement("SELECT drawable_files.obj_id AS obj_id, analyzed FROM drawable_files , hash_sets , hash_set_hits WHERE drawable_files.obj_id = hash_set_hits.obj_id AND hash_sets.hash_set_id = hash_set_hits.hash_set_id AND hash_sets.hash_set_name = ?", DrawableAttribute.HASHSET); //NON-NLS - updateGroupStmt = prepareStatement("insert or replace into groups (seen, value, attribute) values( ?, ? , ?)"); //NON-NLS - insertGroupStmt = prepareStatement("insert or ignore into groups (value, attribute) values (?,?)"); //NON-NLS - - groupSeenQueryStmt = prepareStatement("SELECT seen FROM groups WHERE value = ? AND attribute = ?"); //NON-NLS - selectHashSetNamesStmt = prepareStatement("SELECT DISTINCT hash_set_name FROM hash_sets"); //NON-NLS insertHashSetStmt = prepareStatement("INSERT OR IGNORE INTO hash_sets (hash_set_name) VALUES (?)"); //NON-NLS selectHashSetStmt = prepareStatement("SELECT hash_set_id FROM hash_sets WHERE hash_set_name = ?"); //NON-NLS @@ -370,16 +365,20 @@ public final class DrawableDB { return false; } - try (Statement stmt = con.createStatement()) { - String sql = "CREATE TABLE if not exists groups " //NON-NLS - + "(group_id INTEGER PRIMARY KEY, " //NON-NLS + // The ig_groups table is created in the Case Database + try { + String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "SERIAL" : "INTEGER" ; + String tableSchema = + "( group_id " + autogenKeyType + " PRIMARY KEY, " //NON-NLS + " value VARCHAR(255) not null, " //NON-NLS + " attribute VARCHAR(255) not null, " //NON-NLS + " seen integer DEFAULT 0, " //NON-NLS + " UNIQUE(value, attribute) )"; //NON-NLS - stmt.execute(sql); - } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "problem creating groups table", ex); //NON-NLS + + tskCase.getDBAccessManager().createTable(GROUPS_TABLENAME, tableSchema); + } + catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "problem creating groups table", ex); //NON-NLS return false; } @@ -528,38 +527,52 @@ public final class DrawableDB { } public boolean isGroupSeen(GroupKey groupKey) { - dbReadLock(); - try { - groupSeenQueryStmt.clearParameters(); - groupSeenQueryStmt.setString(1, groupKey.getValueDisplayName()); - groupSeenQueryStmt.setString(2, groupKey.getAttribute().attrName.toString()); - try (ResultSet rs = groupSeenQueryStmt.executeQuery()) { - while (rs.next()) { - return rs.getBoolean("seen"); //NON-NLS + + // Callback to process result of seen query + class GroupSeenQueryResultProcessor implements DBAccessQueryCallback { + private boolean seen = false; + + boolean getGroupSeen() { + return seen; + } + + @Override + public void process(ResultSet resultSet) { + try { + if (resultSet != null) { + while (resultSet.next()) { + seen = resultSet.getBoolean("seen"); //NON-NLSrn; + return; + } + } + } catch (SQLException ex) { + LOGGER.log(Level.WARNING, "failed to get hash set names", ex); //NON-NLS } } - } catch (SQLException ex) { + } + + try { + String groupSeenQueryStmt = String.format("seen FROM " + GROUPS_TABLENAME + " WHERE value = \'%s\' AND attribute = \'%s\'", groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString() ); + GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor(); + + tskCase.getDBAccessManager().select(groupSeenQueryStmt, queryResultProcessor); + return queryResultProcessor.getGroupSeen(); + } + catch (TskCoreException ex) { String msg = String.format("Failed to get is group seen for group key %s", groupKey.getValueDisplayName()); //NON-NLS LOGGER.log(Level.WARNING, msg, ex); - } finally { - dbReadUnlock(); } + return false; } public void markGroupSeen(GroupKey gk, boolean seen) { - dbWriteLock(); try { - //PreparedStatement updateGroup = con.prepareStatement("update groups set seen = ? where value = ? and attribute = ?"); - updateGroupStmt.clearParameters(); - updateGroupStmt.setBoolean(1, seen); - updateGroupStmt.setString(2, gk.getValueDisplayName()); - updateGroupStmt.setString(3, gk.getAttribute().attrName.toString()); - updateGroupStmt.execute(); - } catch (SQLException ex) { + String updateSQL = String.format("set seen = %d where value = \'%s\' and attribute = \'%s\'", seen ? 1 : 0, + gk.getValueDisplayName(), gk.getAttribute().attrName.toString() ); + tskCase.getDBAccessManager().update(GROUPS_TABLENAME, updateSQL); + } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS - } finally { - dbWriteUnlock(); } } @@ -921,21 +934,21 @@ public final class DrawableDB { * @param groupBy Type of the grouping (CATEGORY, MAKE, etc.) */ private void insertGroup(final String value, DrawableAttribute groupBy) { - dbWriteLock(); - + String insertSQL = ""; try { - //PreparedStatement insertGroup = con.prepareStatement("insert or replace into groups (value, attribute, seen) values (?,?,0)"); - insertGroupStmt.clearParameters(); - insertGroupStmt.setString(1, value); - insertGroupStmt.setString(2, groupBy.attrName.toString()); - insertGroupStmt.execute(); - } catch (SQLException sQLException) { + insertSQL = String.format(" (value, attribute) VALUES (\'%s\', \'%s\')", value, groupBy.attrName.toString());; + + if (DbType.POSTGRESQL == tskCase.getDatabaseType()) { + insertSQL += String.format(" ON CONFLICT (value, attribute) DO UPDATE SET value = \'%s\', attribute=\'%s\'", value, groupBy.attrName.toString()); + } + + tskCase.getDBAccessManager().insertOrUpdate(GROUPS_TABLENAME, insertSQL); + } catch (TskCoreException ex) { // Don't need to report it if the case was closed if (Case.isCaseOpen()) { - LOGGER.log(Level.SEVERE, "Unable to insert group", sQLException); //NON-NLS + LOGGER.log(Level.SEVERE, "Unable to insert group", ex); //NON-NLS + } - } finally { - dbWriteUnlock(); } } @@ -1176,9 +1189,15 @@ public final class DrawableDB { * * @param fileIDs the the files ids to count within * - * @return the number of files with Cat-0 + * @return the number of files in the given set with Cat-0 */ public long getUncategorizedCount(Collection fileIDs) { + + // if the fileset is empty, return count as 0 + if (fileIDs.isEmpty()) { + return 0; + } + DrawableTagsManager tagsManager = controller.getTagsManager(); // get a comma seperated list of TagName ids for non zero categories diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 7dddfa5ca8..31c01224ab 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -82,6 +82,7 @@ import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData.DbType; /** * Provides an abstraction layer on top of {@link DrawableDB} ( and to some @@ -351,11 +352,21 @@ public class GroupManager { case MIME_TYPE: if (nonNull(db)) { HashSet types = new HashSet<>(); - try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery("select group_concat(obj_id), mime_type from tsk_files group by mime_type "); //NON-NLS + // Use the group_concat function to get a list of files for each mime type. + // This has sifferent syntax on Postgres vs SQLite + String groupConcatClause; + if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) { + groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; + } + else { + groupConcatClause = "select group_concat(obj_id) as object_ids"; + } + String querySQL = "select " + groupConcatClause + ", mime_type from tsk_files group by mime_type "; + try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(querySQL); //NON-NLS ResultSet resultSet = executeQuery.getResultSet();) { while (resultSet.next()) { final String mimeType = resultSet.getString("mime_type"); //NON-NLS - String objIds = resultSet.getString("group_concat(obj_id)"); //NON-NLS + String objIds = resultSet.getString("object_ids"); //NON-NLS Pattern.compile(",").splitAsStream(objIds) .map(Long::valueOf) From d86c5143fbbaf5d011336aa81580333b339e2ad0 Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 29 Jun 2018 06:41:56 -0400 Subject: [PATCH 02/84] 999: Migrate groups table to CaseDB Fixed SQLite SQL for a query. --- .../autopsy/imagegallery/datamodel/grouping/GroupManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 31c01224ab..b7310008d2 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -359,7 +359,7 @@ public class GroupManager { groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; } else { - groupConcatClause = "select group_concat(obj_id) as object_ids"; + groupConcatClause = " group_concat(obj_id) as object_ids"; } String querySQL = "select " + groupConcatClause + ", mime_type from tsk_files group by mime_type "; try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(querySQL); //NON-NLS From 49e7ed70363789fc4cad9eac58738933e21ff81d Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 25 Jul 2018 19:51:57 -0400 Subject: [PATCH 03/84] 1001: Image Gallery for multi-user cases --- Core/nbproject/project.xml | 3 +- .../imagegallery/ImageGalleryController.java | 216 +++++++++++++++--- .../imagegallery/ImageGalleryModule.java | 4 +- .../ImageGalleryTopComponent.java | 27 +++ .../imagegallery/actions/OpenAction.java | 10 +- .../imagegallery/datamodel/DrawableDB.java | 76 ++++++ 6 files changed, 299 insertions(+), 37 deletions(-) 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..d54d7014eb 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,10 +220,14 @@ 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())) { - //populate the db - queueDBTask(new CopyAnalyzedFiles(instance, db, sleuthKitCase)); + // rebuild drawable db automatically only for single-user cases. + // For multiuser cases, we defer DB rebuild till the user actually opens Image Gallery + if (Case.getCurrentCaseThrows().getCaseType() == CaseType.SINGLE_USER_CASE) { + //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())) { + //populate the db + 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,57 @@ 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 == db) || (null == sleuthKitCase)) { + return false; + } + + try { + Set knownDataSourceIds= db.getKnownDataSourceIds(); + List dataSources = sleuthKitCase.getDataSources(); + Set caseDataSourceIds = new HashSet<>(); + dataSources.forEach((ds) -> { + caseDataSourceIds.add(ds.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. + * + * @return true if data_sources table is stale + */ + void updateDataSourcesTable() { + // no current case open to update + if ((null == db) || (null == sleuthKitCase)) { + return ; + } + + try { + List dataSources = sleuthKitCase.getDataSources(); + dataSources.forEach((ds) -> { + db.insertDataSource(ds.getId()); + }); + } + catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "Image Gallery failed to update data_sources table.", ex); + } + } + synchronized private void shutDownDBExecutor() { if (dbExecutor != null) { dbExecutor.shutdownNow(); @@ -483,8 +553,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()); } @@ -683,6 +753,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 +784,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 +828,14 @@ public final class ImageGalleryController { updateMessage(""); updateProgress(-1.0); } - cleanup(true); + cleanup(taskCompletionStatus); } abstract ProgressHandle getInitialProgressHandle(); + + void setTaskCompletionStatus(boolean status) { + taskCompletionStatus = status; + } } /** @@ -774,6 +856,7 @@ public final class ImageGalleryController { @Override protected void cleanup(boolean success) { + controller.updateDataSourcesTable(); controller.setStale(!success); } @@ -783,7 +866,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) { @@ -794,10 +877,14 @@ public final class ImageGalleryController { if (FileTypeUtils.hasDrawableMIMEType(f)) { //supported mimetype => analyzed taskDB.updateFile(DrawableFile.create(f, true, false), tr); } else { //unsupported mimtype => analyzed but shouldn't include + // if mimetype of the file hasnt 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 +977,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 +1034,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 +1059,59 @@ public final class ImageGalleryController { } } } + + + 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) { + switch (IngestManager.IngestJobEvent.valueOf(evt.getPropertyName())) { + case DATA_SOURCE_ANALYSIS_COMPLETED: + 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: + 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); + } else { + LOGGER.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent does not contain a dataSource object"); //NON-NLS + } + } + break; + } + } + + } } 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..0f98b69e89 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,32 @@ 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 + */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + public static boolean isImageGalleryOpen() { + + final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); + if (tc != null) { + return tc.isOpened(); + } + return false; + } + + /** + * Returns the top component window. + * + * @return Image gallery top component window + */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + public static TopComponent getTopComponent() { + final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); + return tc; + } + 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..2b55be7e49 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java @@ -172,7 +172,15 @@ public final class OpenAction extends CallableSystemAction { switch (answer) { case JOptionPane.YES_OPTION: - ImageGalleryController.getDefault().setListeningEnabled(true); + + if (currentCase.getCaseType() == Case.CaseType.SINGLE_USER_CASE) { + // toggling listening to ON automatically triggers a rebuild + 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 01185d4288..89376b12a6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -103,6 +103,8 @@ public final class DrawableDB { private final PreparedStatement insertHashHitStmt; + private final PreparedStatement insertDataSourceStmt; + private final PreparedStatement updateFileStmt; private final PreparedStatement insertFileStmt; @@ -209,6 +211,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 @@ -349,6 +355,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 @@ -689,6 +706,65 @@ public final class DrawableDB { } } + + /** + * Gets all known data source object ids from data_sources table + * + * @return list of known data source object ids + */ + public Set getKnownDataSourceIds() 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 thrown when calling 'DrawableDB.getKnownDataSourceIds()", e); + } finally { + if (rs != null) { + try { + rs.close(); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Error closing result set after executing getKnownDataSourceIds", ex); //NON-NLS + } + } + if (statement != null) { + try { + statement.close(); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Error closing statement after executing getKnownDataSourceIds", ex); //NON-NLS + } + } + dbReadUnlock(); + } + return ret; + } + + + /** + * Insert given data source object id into data_sources table + * + * @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(); } From 9a5461fd6c6ebfdca0a6ca05c48bdecd6b8e56d5 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 26 Jul 2018 08:38:48 -0400 Subject: [PATCH 04/84] 1001: Image Gallery for multi-user cases - Address Codacy comment in previous commit. --- .../imagegallery/ImageGalleryController.java | 93 ++++++++++--------- .../ImageGalleryTopComponent.java | 10 +- .../imagegallery/datamodel/DrawableDB.java | 2 - 3 files changed, 52 insertions(+), 53 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index d54d7014eb..f13abdd2d4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -220,15 +220,15 @@ public final class ImageGalleryController { listeningEnabled.addListener((observable, oldValue, newValue) -> { try { - // rebuild drawable db automatically only for single-user cases. + // 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 (Case.getCurrentCaseThrows().getCaseType() == CaseType.SINGLE_USER_CASE) { - //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())) { - //populate the db - this.rebuildDB(); - } + if ( newValue && !oldValue && + ImageGalleryModule.isDrawableDBStale(Case.getCurrentCaseThrows()) && + (Case.getCurrentCaseThrows().getCaseType() == CaseType.SINGLE_USER_CASE) ) { + //populate the db + this.rebuildDB(); } + } catch (NoCurrentCaseException ex) { LOGGER.log(Level.WARNING, "Exception while getting open case.", ex); } @@ -446,8 +446,8 @@ public final class ImageGalleryController { Set knownDataSourceIds= db.getKnownDataSourceIds(); List dataSources = sleuthKitCase.getDataSources(); Set caseDataSourceIds = new HashSet<>(); - dataSources.forEach((ds) -> { - caseDataSourceIds.add(ds.getId()); + dataSources.forEach((dataSource) -> { + caseDataSourceIds.add(dataSource.getId()); }); return !(knownDataSourceIds.containsAll(caseDataSourceIds) && caseDataSourceIds.containsAll(knownDataSourceIds)); @@ -1060,7 +1060,10 @@ public final class ImageGalleryController { } } - + + /** + * Listener for Ingest Job events. + */ private class IngestJobEventListener implements PropertyChangeListener { @NbBundle.Messages({ @@ -1071,47 +1074,45 @@ public final class ImageGalleryController { }) @Override public void propertyChange(PropertyChangeEvent evt) { - switch (IngestManager.IngestJobEvent.valueOf(evt.getPropertyName())) { - case DATA_SOURCE_ANALYSIS_COMPLETED: - 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); + 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); - switch (answer) { - case JOptionPane.YES_OPTION: - rebuildDB(); - break; - case JOptionPane.NO_OPTION: - case JOptionPane.CANCEL_OPTION: - break; //do nothing - } + 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); - } else { - LOGGER.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent does not contain a dataSource object"); //NON-NLS } + }); + } 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); + } else { + LOGGER.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent does not contain a dataSource object"); //NON-NLS } - break; + } } } - } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 0f98b69e89..fbb3f2b8a4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -97,9 +97,9 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl @ThreadConfined(type = ThreadConfined.ThreadType.JFX) public static boolean isImageGalleryOpen() { - final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); - if (tc != null) { - return tc.isOpened(); + final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID); + if (topComponent != null) { + return topComponent.isOpened(); } return false; } @@ -111,8 +111,8 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) public static TopComponent getTopComponent() { - final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); - return tc; + final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID); + return topComponent; } public static void openTopComponent() { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 89376b12a6..29473df5b8 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; @@ -63,7 +62,6 @@ import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.DBAccessManager; import org.sleuthkit.datamodel.DBAccessQueryCallback; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; From 36c638514f280a63e80ec0a5792bf6c7fc729f6a Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 26 Jul 2018 09:09:20 -0400 Subject: [PATCH 05/84] 1001: Image gallery for muti-user cases - More Codacy comments addressed. --- .../autopsy/imagegallery/ImageGalleryController.java | 4 ++-- .../autopsy/imagegallery/ImageGalleryTopComponent.java | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index f13abdd2d4..cc3d757ab6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -472,8 +472,8 @@ public final class ImageGalleryController { try { List dataSources = sleuthKitCase.getDataSources(); - dataSources.forEach((ds) -> { - db.insertDataSource(ds.getId()); + dataSources.forEach((dataSource) -> { + db.insertDataSource(dataSource.getId()); }); } catch (TskCoreException ex) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index fbb3f2b8a4..1add37a2b7 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -107,12 +107,11 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl /** * Returns the top component window. * - * @return Image gallery top component window + * @return Image gallery top component window, null if it's not open */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) public static TopComponent getTopComponent() { - final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID); - return topComponent; + return WindowManager.getDefault().findTopComponent(PREFERRED_ID); } public static void openTopComponent() { From 7149b3b9d06af5d0752bf31dcded245f1bff7480 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 26 Jul 2018 12:30:08 -0400 Subject: [PATCH 06/84] 999: Migrate Image Gallery groups table to CaseDB - Address review comments. --- .../imagegallery/datamodel/DrawableDB.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 01185d4288..c607da1a68 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -63,8 +63,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.DBAccessManager; -import org.sleuthkit.datamodel.DBAccessQueryCallback; +import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData.DbType; @@ -89,7 +88,7 @@ public final class DrawableDB { private static final String HASH_SET_NAME = "hash_set_name"; //NON-NLS - private static final String GROUPS_TABLENAME = "ig_groups"; //NON-NLS + private static final String GROUPS_TABLENAME = "image_gallery_groups"; //NON-NLS private final PreparedStatement insertHashSetStmt; @@ -375,7 +374,7 @@ public final class DrawableDB { + " seen integer DEFAULT 0, " //NON-NLS + " UNIQUE(value, attribute) )"; //NON-NLS - tskCase.getDBAccessManager().createTable(GROUPS_TABLENAME, tableSchema); + tskCase.getCaseDbAccessManager().createTable(GROUPS_TABLENAME, tableSchema); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "problem creating groups table", ex); //NON-NLS @@ -529,7 +528,7 @@ public final class DrawableDB { public boolean isGroupSeen(GroupKey groupKey) { // Callback to process result of seen query - class GroupSeenQueryResultProcessor implements DBAccessQueryCallback { + class GroupSeenQueryResultProcessor implements CaseDbAccessQueryCallback { private boolean seen = false; boolean getGroupSeen() { @@ -541,12 +540,12 @@ public final class DrawableDB { try { if (resultSet != null) { while (resultSet.next()) { - seen = resultSet.getBoolean("seen"); //NON-NLSrn; + seen = resultSet.getBoolean("seen"); //NON-NLS; return; } } } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "failed to get hash set names", ex); //NON-NLS + LOGGER.log(Level.WARNING, "failed to get group seen", ex); //NON-NLS } } } @@ -555,7 +554,7 @@ public final class DrawableDB { String groupSeenQueryStmt = String.format("seen FROM " + GROUPS_TABLENAME + " WHERE value = \'%s\' AND attribute = \'%s\'", groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString() ); GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor(); - tskCase.getDBAccessManager().select(groupSeenQueryStmt, queryResultProcessor); + tskCase.getCaseDbAccessManager().select(groupSeenQueryStmt, queryResultProcessor); return queryResultProcessor.getGroupSeen(); } catch (TskCoreException ex) { @@ -570,7 +569,7 @@ public final class DrawableDB { try { String updateSQL = String.format("set seen = %d where value = \'%s\' and attribute = \'%s\'", seen ? 1 : 0, gk.getValueDisplayName(), gk.getAttribute().attrName.toString() ); - tskCase.getDBAccessManager().update(GROUPS_TABLENAME, updateSQL); + tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS } @@ -942,7 +941,7 @@ public final class DrawableDB { insertSQL += String.format(" ON CONFLICT (value, attribute) DO UPDATE SET value = \'%s\', attribute=\'%s\'", value, groupBy.attrName.toString()); } - tskCase.getDBAccessManager().insertOrUpdate(GROUPS_TABLENAME, insertSQL); + tskCase.getCaseDbAccessManager().insertOrUpdate(GROUPS_TABLENAME, insertSQL); } catch (TskCoreException ex) { // Don't need to report it if the case was closed if (Case.isCaseOpen()) { From 6b11132bedf6caf9c3ef2b20820a1dd624cabd68 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 26 Jul 2018 21:53:03 -0400 Subject: [PATCH 07/84] Modify the extension query in BulkTransferTask to use the extension column instead of substring match on name column. --- .../autopsy/imagegallery/ImageGalleryController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index cc3d757ab6..42fc603fd4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -729,12 +729,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 From 834506324458874c459d250c7c0a0e467e8d686d Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 27 Jul 2018 09:19:18 -0400 Subject: [PATCH 08/84] Mark DB is not stale when a local ingest finishes. --- .../sleuthkit/autopsy/imagegallery/ImageGalleryController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 42fc603fd4..3c45229e78 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -1108,6 +1108,8 @@ public final class ImageGalleryController { 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 } From dd6e3abafedb7e7f2c1afa8ec1f86f634b7c9ae0 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 27 Jul 2018 12:35:49 -0400 Subject: [PATCH 09/84] Update ImageGalleryController.java --- .../sleuthkit/autopsy/imagegallery/ImageGalleryController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 3c45229e78..22ebc8df9e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -477,7 +477,7 @@ public final class ImageGalleryController { }); } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "Image Gallery failed to update data_sources table.", ex); + LOGGER.log(Level.SEVERE, "Image Gallery failed to update data_sources table.", ex); } } From 157124a03892f7c06bf4c89da155493eb0f4f07d Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 27 Jul 2018 12:52:19 -0400 Subject: [PATCH 10/84] Update DrawableDB.java --- .../sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index c607da1a68..634a7dc784 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -545,7 +545,7 @@ public final class DrawableDB { } } } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "failed to get group seen", ex); //NON-NLS + LOGGER.log(Level.SEVERE, "failed to get group seen", ex); //NON-NLS } } } From 8918f96a75c45fca6f10268dc82a02988ca85adb Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 27 Jul 2018 13:10:21 -0400 Subject: [PATCH 11/84] Add missing final specifiers to ingest.events package --- .../autopsy/ingest/events/DataSourceAnalysisCompletedEvent.java | 2 +- .../autopsy/ingest/events/DataSourceAnalysisStartedEvent.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/events/DataSourceAnalysisCompletedEvent.java b/Core/src/org/sleuthkit/autopsy/ingest/events/DataSourceAnalysisCompletedEvent.java index 0df66fabb6..4626b6858a 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/events/DataSourceAnalysisCompletedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/events/DataSourceAnalysisCompletedEvent.java @@ -26,7 +26,7 @@ import org.sleuthkit.datamodel.Content; * Event published when analysis (ingest) of a data source included in an ingest * job is completed. */ -public class DataSourceAnalysisCompletedEvent extends DataSourceAnalysisEvent implements Serializable { +public final class DataSourceAnalysisCompletedEvent extends DataSourceAnalysisEvent implements Serializable { /** * The reason why the analysis of the data source completed. diff --git a/Core/src/org/sleuthkit/autopsy/ingest/events/DataSourceAnalysisStartedEvent.java b/Core/src/org/sleuthkit/autopsy/ingest/events/DataSourceAnalysisStartedEvent.java index 6975120eae..b64a224da0 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/events/DataSourceAnalysisStartedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/events/DataSourceAnalysisStartedEvent.java @@ -26,7 +26,7 @@ import org.sleuthkit.datamodel.Content; * Event published when analysis (ingest) of a data source included in an ingest * job is started. */ -public class DataSourceAnalysisStartedEvent extends DataSourceAnalysisEvent implements Serializable { +public final class DataSourceAnalysisStartedEvent extends DataSourceAnalysisEvent implements Serializable { private static final long serialVersionUID = 1L; From 1d15ac3622e9e9f089265084c277829c3a090f79 Mon Sep 17 00:00:00 2001 From: Raman Date: Mon, 30 Jul 2018 08:04:16 -0400 Subject: [PATCH 12/84] 1001: Image gallery supports multi user cases - addressed review comments on previous commit. --- .../imagegallery/ImageGalleryController.java | 28 ++++++++++--------- .../ImageGalleryTopComponent.java | 2 -- .../imagegallery/actions/OpenAction.java | 5 +++- .../imagegallery/datamodel/DrawableDB.java | 14 ++++++---- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 3c45229e78..5f53ca1bc5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -438,13 +438,13 @@ public final class ImageGalleryController { boolean isDataSourcesTableStale() { // no current case open to check - if ((null == db) || (null == sleuthKitCase)) { + if ((null == getDatabase()) || (null == getSleuthKitCase())) { return false; } try { - Set knownDataSourceIds= db.getKnownDataSourceIds(); - List dataSources = sleuthKitCase.getDataSources(); + Set knownDataSourceIds= getDatabase().getDataSourceIds(); + List dataSources = getSleuthKitCase().getDataSources(); Set caseDataSourceIds = new HashSet<>(); dataSources.forEach((dataSource) -> { caseDataSourceIds.add(dataSource.getId()); @@ -462,18 +462,17 @@ public final class ImageGalleryController { /** * Update the datasources table in drawable DB. * - * @return true if data_sources table is stale */ - void updateDataSourcesTable() { + private void updateDataSourcesTable() { // no current case open to update - if ((null == db) || (null == sleuthKitCase)) { - return ; + if ((null == getDatabase()) || (null == getSleuthKitCase())) { + return; } try { - List dataSources = sleuthKitCase.getDataSources(); + List dataSources = getSleuthKitCase().getDataSources(); dataSources.forEach((dataSource) -> { - db.insertDataSource(dataSource.getId()); + getDatabase().insertDataSource(dataSource.getId()); }); } catch (TskCoreException ex) { @@ -833,7 +832,7 @@ public final class ImageGalleryController { abstract ProgressHandle getInitialProgressHandle(); - void setTaskCompletionStatus(boolean status) { + protected void setTaskCompletionStatus(boolean status) { taskCompletionStatus = status; } } @@ -874,10 +873,13 @@ 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 - // if mimetype of the file hasnt been ascertained, ingest might not have completed yet. + } + 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); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 1add37a2b7..ce06c52893 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -94,7 +94,6 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl * * @return true, if Image gallery is opened, false otherwise */ - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) public static boolean isImageGalleryOpen() { final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID); @@ -109,7 +108,6 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl * * @return Image gallery top component window, null if it's not open */ - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) public static TopComponent getTopComponent() { return WindowManager.getDefault().findTopComponent(PREFERRED_ID); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java index 2b55be7e49..2fbc9b3758 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java @@ -173,8 +173,11 @@ public final class OpenAction extends CallableSystemAction { switch (answer) { case JOptionPane.YES_OPTION: + // 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) { - // toggling listening to ON automatically triggers a rebuild ImageGalleryController.getDefault().setListeningEnabled(true); } else { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index edb0f8cb29..9adf35312d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -706,11 +706,11 @@ public final class DrawableDB { /** - * Gets all known data source object ids from data_sources table + * Gets all data source object ids from datasources table * * @return list of known data source object ids */ - public Set getKnownDataSourceIds() throws TskCoreException { + public Set getDataSourceIds() throws TskCoreException { Statement statement = null; ResultSet rs = null; Set ret = new HashSet<>(); @@ -722,20 +722,20 @@ public final class DrawableDB { ret.add(rs.getLong(1)); } } catch (SQLException e) { - throw new TskCoreException("SQLException thrown when calling 'DrawableDB.getKnownDataSourceIds()", 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 result set after executing getKnownDataSourceIds", ex); //NON-NLS + 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 after executing getKnownDataSourceIds", ex); //NON-NLS + LOGGER.log(Level.SEVERE, "Error closing statement ", ex); //NON-NLS } } dbReadUnlock(); @@ -745,7 +745,9 @@ public final class DrawableDB { /** - * Insert given data source object id into data_sources table + * 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 */ From eeace0490415ebb23106757fbd77944882edb1ca Mon Sep 17 00:00:00 2001 From: Raman Date: Tue, 31 Jul 2018 11:44:11 -0400 Subject: [PATCH 13/84] 1003: Add transactions support in CaseDbAccessManager api. --- .../imagegallery/ImageGalleryController.java | 15 +++-- .../imagegallery/datamodel/DrawableDB.java | 56 +++++++++++++------ 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 76c3626512..53544f891a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -88,6 +88,7 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -769,7 +770,7 @@ public final class ImageGalleryController { abstract List getFiles() throws TskCoreException; - abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) throws TskCoreException; + abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) throws TskCoreException; @Override public void run() { @@ -788,6 +789,7 @@ public final class ImageGalleryController { //do in transaction DrawableDB.DrawableTransaction tr = taskDB.beginTransaction(); + CaseDbTransaction caseDbTransaction = tskCase.beginTransaction(); int workDone = 0; for (final AbstractFile f : files) { if (isCancelled()) { @@ -800,7 +802,7 @@ public final class ImageGalleryController { LOGGER.log(Level.WARNING, "BulkTransferTask interrupted. Ignoring it to update the contents of drawable database."); //NON-NLS } - processFile(f, tr); + processFile(f, tr, caseDbTransaction); workDone++; progressHandle.progress(f.getName(), workDone); @@ -815,6 +817,7 @@ public final class ImageGalleryController { progressHandle.start(); taskDB.commitTransaction(tr, true); + caseDbTransaction.commit(); } catch (TskCoreException ex) { progressHandle.progress(Bundle.BulkTask_stopCopy_status()); @@ -865,7 +868,7 @@ public final class ImageGalleryController { } @Override - void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr) throws TskCoreException { + void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDbTransaction) throws TskCoreException { final boolean known = f.getKnown() == TskData.FileKnown.KNOWN; if (known) { @@ -875,7 +878,7 @@ public final class ImageGalleryController { try { //supported mimetype => analyzed if ( null != f.getMIMEType() && FileTypeUtils.hasDrawableMIMEType(f)) { - taskDB.updateFile(DrawableFile.create(f, true, false), tr); + taskDB.updateFile(DrawableFile.create(f, true, false), tr, caseDbTransaction ); } else { //unsupported mimtype => analyzed but shouldn't include @@ -927,8 +930,8 @@ public final class ImageGalleryController { } @Override - void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) { - taskDB.insertFile(DrawableFile.create(f, false, false), tr); + void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) { + taskDB.insertFile(DrawableFile.create(f, false, false), tr, caseDBTransaction); } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index a578641c22..3f913a5221 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -63,6 +63,7 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData.DbType; @@ -230,9 +231,16 @@ public final class DrawableDB { insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, obj_id) VALUES (?,?)"); //NON-NLS - for (DhsImageCategory cat : DhsImageCategory.values()) { - insertGroup(cat.getDisplayName(), DrawableAttribute.CATEGORY); + try { + CaseDbTransaction caseDbTransaction = tskCase.beginTransaction(); + for (DhsImageCategory cat : DhsImageCategory.values()) { + insertGroup(cat.getDisplayName(), DrawableAttribute.CATEGORY, caseDbTransaction); + } + caseDbTransaction.commit(); + } catch (TskCoreException ex) { + throw new ExceptionInInitializerError(ex); } + initializeImageList(); } else { throw new ExceptionInInitializerError(); @@ -599,23 +607,38 @@ public final class DrawableDB { } public void updateFile(DrawableFile f) { - DrawableTransaction trans = beginTransaction(); - updateFile(f, trans); - commitTransaction(trans, true); + try { + DrawableTransaction trans = beginTransaction(); + CaseDbTransaction caseDbTransaction = tskCase.beginTransaction(); + updateFile(f, trans, caseDbTransaction); + commitTransaction(trans, true); + caseDbTransaction.commit(); + } + catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error updating file", ex); //NON-NLS + } + } public void insertFile(DrawableFile f) { - DrawableTransaction trans = beginTransaction(); - insertFile(f, trans); - commitTransaction(trans, true); + try { + DrawableTransaction trans = beginTransaction(); + CaseDbTransaction caseDbTransaction = this.tskCase.beginTransaction(); + insertFile(f, trans, caseDbTransaction); + commitTransaction(trans, true); + caseDbTransaction.commit(); + } + catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error inserting file", ex); //NON-NLS + } } - public void insertFile(DrawableFile f, DrawableTransaction tr) { - insertOrUpdateFile(f, tr, insertFileStmt); + public void insertFile(DrawableFile f, DrawableTransaction tr, CaseDbTransaction caseDbTransaction) { + insertOrUpdateFile(f, tr, insertFileStmt, caseDbTransaction); } - public void updateFile(DrawableFile f, DrawableTransaction tr) { - insertOrUpdateFile(f, tr, updateFileStmt); + public void updateFile(DrawableFile f, DrawableTransaction tr, CaseDbTransaction caseDbTransaction) { + insertOrUpdateFile(f, tr, updateFileStmt, caseDbTransaction); } /** @@ -631,7 +654,7 @@ public final class DrawableDB { * @param tr a transaction to use, must not be null * @param stmt the statement that does the actull inserting */ - private void insertOrUpdateFile(DrawableFile f, @Nonnull DrawableTransaction tr, @Nonnull PreparedStatement stmt) { + private void insertOrUpdateFile(DrawableFile f, @Nonnull DrawableTransaction tr, @Nonnull PreparedStatement stmt, @Nonnull CaseDbTransaction caseDbTransaction) { if (tr.isClosed()) { throw new IllegalArgumentException("can't update database with closed transaction"); @@ -683,7 +706,7 @@ public final class DrawableDB { for (Comparable val : vals) { //use empty string for null values (mime_type), this shouldn't happen! if (null != val) { - insertGroup(val.toString(), attr); + insertGroup(val.toString(), attr, caseDbTransaction); } } } @@ -1008,8 +1031,9 @@ public final class DrawableDB { * Insert new group into DB * @param value Value of the group (unique to the type) * @param groupBy Type of the grouping (CATEGORY, MAKE, etc.) + * @param caseDbTransaction transaction to use for CaseDB insert/updates */ - private void insertGroup(final String value, DrawableAttribute groupBy) { + private void insertGroup(final String value, DrawableAttribute groupBy, CaseDbTransaction caseDbTransaction) { String insertSQL = ""; try { insertSQL = String.format(" (value, attribute) VALUES (\'%s\', \'%s\')", value, groupBy.attrName.toString());; @@ -1018,7 +1042,7 @@ public final class DrawableDB { insertSQL += String.format(" ON CONFLICT (value, attribute) DO UPDATE SET value = \'%s\', attribute=\'%s\'", value, groupBy.attrName.toString()); } - tskCase.getCaseDbAccessManager().insertOrUpdate(GROUPS_TABLENAME, insertSQL); + tskCase.getCaseDbAccessManager().insertOrUpdate(GROUPS_TABLENAME, insertSQL, caseDbTransaction); } catch (TskCoreException ex) { // Don't need to report it if the case was closed if (Case.isCaseOpen()) { From a0c6d71f93bdb5e6791ff1147a1b9057ace7f795 Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 1 Aug 2018 12:57:07 -0400 Subject: [PATCH 14/84] 1003: Add transaction support to CaseDbAccessManager api. - address review comments in previous commit. --- .../imagegallery/ImageGalleryController.java | 50 ++++++++++------- .../imagegallery/datamodel/DrawableDB.java | 53 ++++++++++++++++--- 2 files changed, 78 insertions(+), 25 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 53544f891a..fa172f0919 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -778,6 +778,9 @@ public final class ImageGalleryController { progressHandle.start(); updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status()); + + DrawableDB.DrawableTransaction drawableDbTransaction = null; + CaseDbTransaction caseDbTransaction = null; try { //grab all files with supported extension or detected mime types final List files = getFiles(); @@ -785,24 +788,23 @@ public final class ImageGalleryController { updateProgress(0.0); - taskCompletionStatus = true; - - //do in transaction - DrawableDB.DrawableTransaction tr = taskDB.beginTransaction(); - CaseDbTransaction caseDbTransaction = tskCase.beginTransaction(); + taskCompletionStatus = true; int workDone = 0; + + //do in transaction + drawableDbTransaction = taskDB.beginTransaction(); + caseDbTransaction = tskCase.beginTransaction(); for (final AbstractFile f : files) { - if (isCancelled()) { - LOGGER.log(Level.WARNING, "Task cancelled: not all contents may be transfered to drawable database."); //NON-NLS + if (isCancelled() || Thread.interrupted()) { + LOGGER.log(Level.WARNING, "Task cancelled or interrupted: not all contents may be transfered to drawable database."); //NON-NLS + taskCompletionStatus = false; 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, caseDbTransaction); + processFile(f, drawableDbTransaction, caseDbTransaction); workDone++; progressHandle.progress(f.getName(), workDone); @@ -816,10 +818,20 @@ public final class ImageGalleryController { updateProgress(1.0); progressHandle.start(); - taskDB.commitTransaction(tr, true); + taskDB.commitTransaction(drawableDbTransaction, true); caseDbTransaction.commit(); - } catch (TskCoreException ex) { + } catch (TskCoreException ex) { + if (null != drawableDbTransaction) { + taskDB.rollbackTransaction(drawableDbTransaction); + } + if (null != caseDbTransaction) { + try { + caseDbTransaction.rollback(); + } catch (TskCoreException ex2) { + LOGGER.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS + } + } progressHandle.progress(Bundle.BulkTask_stopCopy_status()); LOGGER.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS MessageNotifyUtil.Notify.warn(Bundle.BulkTask_errPopulating_errMsg(), ex.getMessage()); @@ -880,13 +892,14 @@ public final class ImageGalleryController { if ( null != f.getMIMEType() && FileTypeUtils.hasDrawableMIMEType(f)) { taskDB.updateFile(DrawableFile.create(f, true, false), tr, caseDbTransaction ); } - else { //unsupported mimtype => analyzed but shouldn't include - + else { // if mimetype of the file hasn't been ascertained, ingest might not have completed yet. if (null == f.getMIMEType()) { this.setTaskCompletionStatus(false); + } else { + //unsupported mimtype => analyzed but shouldn't include + taskDB.removeFile(f.getId(), tr); } - taskDB.removeFile(f.getId(), tr); } } catch (FileTypeDetector.FileTypeDetectorInitException ex) { throw new TskCoreException("Failed to initialize FileTypeDetector.", ex); @@ -999,13 +1012,12 @@ public final class ImageGalleryController { } } } 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 + } else { setStale(true); } } @@ -1044,7 +1056,7 @@ public final class ImageGalleryController { 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 + } else { setStale(true); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 3f913a5221..6221de8947 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -231,13 +231,22 @@ public final class DrawableDB { insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, obj_id) VALUES (?,?)"); //NON-NLS + CaseDbTransaction caseDbTransaction = null; try { - CaseDbTransaction caseDbTransaction = tskCase.beginTransaction(); + caseDbTransaction = tskCase.beginTransaction(); for (DhsImageCategory cat : DhsImageCategory.values()) { insertGroup(cat.getDisplayName(), DrawableAttribute.CATEGORY, caseDbTransaction); } caseDbTransaction.commit(); } catch (TskCoreException ex) { + if (null != caseDbTransaction) { + try { + caseDbTransaction.rollback(); + } + catch(TskCoreException ex2) { + LOGGER.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); + } + } throw new ExceptionInInitializerError(ex); } @@ -607,28 +616,53 @@ public final class DrawableDB { } public void updateFile(DrawableFile f) { - try { - DrawableTransaction trans = beginTransaction(); - CaseDbTransaction caseDbTransaction = tskCase.beginTransaction(); + DrawableTransaction trans = null; + CaseDbTransaction caseDbTransaction = null; + + try { + trans = beginTransaction(); + caseDbTransaction = tskCase.beginTransaction(); updateFile(f, trans, caseDbTransaction); commitTransaction(trans, true); caseDbTransaction.commit(); } catch (TskCoreException ex) { + if (null != trans) { + rollbackTransaction(trans); + } + if (null != caseDbTransaction) { + try { + caseDbTransaction.rollback(); + } catch (TskCoreException ex2) { + LOGGER.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS + } + } LOGGER.log(Level.SEVERE, "Error updating file", ex); //NON-NLS } } public void insertFile(DrawableFile f) { + DrawableTransaction trans = null; + CaseDbTransaction caseDbTransaction = null; try { - DrawableTransaction trans = beginTransaction(); - CaseDbTransaction caseDbTransaction = this.tskCase.beginTransaction(); + trans = beginTransaction(); + caseDbTransaction = tskCase.beginTransaction(); insertFile(f, trans, caseDbTransaction); commitTransaction(trans, true); caseDbTransaction.commit(); } catch (TskCoreException ex) { + if (null != trans) { + rollbackTransaction(trans); + } + if (null != caseDbTransaction) { + try { + caseDbTransaction.rollback(); + } catch (TskCoreException ex2) { + LOGGER.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS + } + } LOGGER.log(Level.SEVERE, "Error inserting file", ex); //NON-NLS } } @@ -799,6 +833,13 @@ public final class DrawableDB { tr.commit(notify); } + public void rollbackTransaction(DrawableTransaction tr) { + if (tr.isClosed()) { + throw new IllegalArgumentException("can't rollback already closed transaction"); + } + tr.rollback(); + } + public Boolean isFileAnalyzed(DrawableFile f) { return isFileAnalyzed(f.getId()); } From 9248d8496b0b6a4bbdb6f3c64e61b1e2e13220fe Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Thu, 2 Aug 2018 17:07:20 -0400 Subject: [PATCH 15/84] change default stale value to false to prevent exceesive rebuilds --- .../org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java index a6b57e5779..4ae1903de6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java @@ -88,7 +88,7 @@ public class ImageGalleryModule { String stale = new PerCaseProperties(c).getConfigSetting(ImageGalleryModule.MODULE_NAME, PerCaseProperties.STALE); return ( ImageGalleryController.getDefault().isDataSourcesTableStale() || - (StringUtils.isNotBlank(stale) ? Boolean.valueOf(stale) : true) ); + (StringUtils.isNotBlank(stale) ? Boolean.valueOf(stale) : false) ); } else { return false; } From 4d7eb77c41c5c6239ae51f25810d33a31d1d8ca9 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Thu, 2 Aug 2018 17:09:36 -0400 Subject: [PATCH 16/84] Add DS at beginning of jobs, update when stale is set --- .../imagegallery/ImageGalleryController.java | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index fa172f0919..07c57b0fe6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -82,7 +82,6 @@ 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; @@ -219,11 +218,12 @@ public final class ImageGalleryController { private ImageGalleryController() { - listeningEnabled.addListener((observable, oldValue, newValue) -> { + // listener for the boolean property about when IG is listening / enabled + listeningEnabled.addListener((observable, wasPreviouslyEnabled, isEnabled) -> { try { // 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 && + if ( isEnabled && !wasPreviouslyEnabled && ImageGalleryModule.isDrawableDBStale(Case.getCurrentCaseThrows()) && (Case.getCurrentCaseThrows().getCaseType() == CaseType.SINGLE_USER_CASE) ) { //populate the db @@ -870,12 +870,13 @@ public final class ImageGalleryController { @Override protected void cleanup(boolean success) { - controller.updateDataSourcesTable(); + // processFile will set success to fail if files are missing MIME types controller.setStale(!success); } @Override List getFiles() throws TskCoreException { + controller.updateDataSourcesTable(); return tskCase.findAllFilesWhere(DRAWABLE_QUERY); } @@ -895,6 +896,7 @@ public final class ImageGalleryController { else { // if mimetype of the file hasn't been ascertained, ingest might not have completed yet. if (null == f.getMIMEType()) { + // set to false to force the DB to be marked as stale this.setTaskCompletionStatus(false); } else { //unsupported mimtype => analyzed but shouldn't include @@ -950,6 +952,7 @@ public final class ImageGalleryController { @Override List getFiles() throws TskCoreException { long datasourceID = dataSource.getDataSource().getId(); + taskDB.insertDataSource(datasourceID); return tskCase.findAllFilesWhere("data_source_obj_id = " + datasourceID + " AND " + DRAWABLE_QUERY); } @@ -1060,7 +1063,11 @@ public final class ImageGalleryController { setStale(true); } } + else { + setStale(true); + } break; + case CONTENT_TAG_ADDED: final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt; if (getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) { @@ -1117,19 +1124,6 @@ public final class ImageGalleryController { } } }); - } 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 - } } } } From 2c3d515201e28f4ec9d5f37f049b9afd48099d90 Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 10 Aug 2018 15:29:19 -0400 Subject: [PATCH 17/84] 1009: Rebuild IG at data-source level --- .../imagegallery/ImageGalleryController.java | 175 ++++++++++-------- .../imagegallery/ImageGalleryModule.java | 6 +- .../imagegallery/datamodel/DrawableDB.java | 47 +++-- 3 files changed, 126 insertions(+), 102 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 07c57b0fe6..4894a3cb35 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -25,6 +25,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executors; @@ -74,6 +75,7 @@ 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; +import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB.DrawableDbBuildStatusEnum; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; import org.sleuthkit.autopsy.imagegallery.datamodel.HashSetManager; @@ -200,11 +202,6 @@ public final class ImageGalleryController { Platform.runLater(() -> { stale.set(b); }); - try { - new PerCaseProperties(Case.getCurrentCaseThrows()).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.STALE, b.toString()); - } catch (NoCurrentCaseException ex) { - Logger.getLogger(ImageGalleryController.class.getName()).log(Level.WARNING, "Exception while getting open case."); //NON-NLS - } } public ReadOnlyBooleanProperty stale() { @@ -403,7 +400,15 @@ public final class ImageGalleryController { * */ public void rebuildDB() { - queueDBTask(new CopyAnalyzedFiles(instance, db, sleuthKitCase)); + + // queue a rebuild task for each stale data source + Set staleDataSources = getStaleDataSourceIds(); + if (!staleDataSources.isEmpty()) { + staleDataSources.forEach((id) -> { + queueDBTask(new CopyAnalyzedFiles(id, instance, db, sleuthKitCase)); + }); + + } } /** @@ -437,48 +442,57 @@ public final class ImageGalleryController { * @return true if datasources table is stale */ boolean isDataSourcesTableStale() { + return (getStaleDataSourceIds().isEmpty() == false); + } + + /** + * Returns a set of data source object ids that are stale. + * + * This includes any data sources already in the table, that are not in COMPLETE status, + * or any data sources that might have been added to the case, but are not in the datasources table. + * + * @return list of data source object ids that are stale. + */ + Set getStaleDataSourceIds() { + + Set staleDataSourceIds = new HashSet<>(); // no current case open to check if ((null == getDatabase()) || (null == getSleuthKitCase())) { - return false; + return staleDataSourceIds; } try { - Set knownDataSourceIds= getDatabase().getDataSourceIds(); + Map knownDataSourceIds= getDatabase().getDataSourceDbBuildStatus(); + List dataSources = getSleuthKitCase().getDataSources(); Set caseDataSourceIds = new HashSet<>(); dataSources.forEach((dataSource) -> { caseDataSourceIds.add(dataSource.getId()); }); - return !(knownDataSourceIds.containsAll(caseDataSourceIds) && caseDataSourceIds.containsAll(knownDataSourceIds)); + // collect all data sources already in the table, that are not yet COMPLETE + knownDataSourceIds.entrySet().stream().forEach((Map.Entry t) -> { + DrawableDbBuildStatusEnum status = t.getValue(); + if (DrawableDbBuildStatusEnum.COMPLETE != status) { + staleDataSourceIds.add(t.getKey()); + } + }); + + // collect any new data sources in the case. + caseDataSourceIds.forEach((Long id) -> { + if (!knownDataSourceIds.containsKey(id)) { + staleDataSourceIds.add(id); + } + }); + + return staleDataSourceIds; } 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; + return staleDataSourceIds; } - 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() { @@ -727,7 +741,10 @@ public final class ImageGalleryController { @NbBundle.Messages({"BulkTask.committingDb.status=committing image/video database", "BulkTask.stopCopy.status=Stopping copy to drawable db task.", "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 */ + /** + * Base abstract class for various methods of copying image files data, + * for a given data source, into the Image gallery DB. + */ abstract static private class BulkTransferTask extends BackgroundTask { static private final String FILE_EXTENSION_CLAUSE = @@ -739,27 +756,35 @@ public final class ImageGalleryController { "(mime_type LIKE '" //NON-NLS + String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS + "') "; - - static final String DRAWABLE_QUERY = - //grab files with supported extension - "(" + FILE_EXTENSION_CLAUSE - //grab files with supported mime-types - + " OR " + MIMETYPE_CLAUSE //NON-NLS - //grab files with image or video mime-types even if we don't officially support them - + " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS - + + final String DRAWABLE_QUERY; + final String DATASOURCE_CLAUSE; + final ImageGalleryController controller; final DrawableDB taskDB; final SleuthkitCase tskCase; + final long dataSourceObjId; ProgressHandle progressHandle; - private boolean taskCompletionStatus; - BulkTransferTask(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { + BulkTransferTask(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { this.controller = controller; this.taskDB = taskDB; this.tskCase = tskCase; + this.dataSourceObjId = dataSourceObjId; + + DATASOURCE_CLAUSE = " (data_source_obj_id = " + dataSourceObjId + ") "; + + DRAWABLE_QUERY = + DATASOURCE_CLAUSE + + " AND ( " + + //grab files with supported extension + FILE_EXTENSION_CLAUSE + //grab files with supported mime-types + + " OR " + MIMETYPE_CLAUSE //NON-NLS + //grab files with image or video mime-types even if we don't officially support them + + " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS } /** @@ -768,8 +793,16 @@ public final class ImageGalleryController { */ abstract void cleanup(boolean success); - abstract List getFiles() throws TskCoreException; - + /** + * Gets a list of files to process. + * + * @return list of files to process + * @throws TskCoreException + */ + List getFiles() throws TskCoreException { + return tskCase.findAllFilesWhere(DRAWABLE_QUERY); + } + abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) throws TskCoreException; @Override @@ -786,8 +819,9 @@ public final class ImageGalleryController { final List files = getFiles(); progressHandle.switchToDeterminate(files.size()); - updateProgress(0.0); + taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS); + updateProgress(0.0); taskCompletionStatus = true; int workDone = 0; @@ -839,6 +873,9 @@ public final class ImageGalleryController { return; } finally { progressHandle.finish(); + if (taskCompletionStatus) { + taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.COMPLETE); + } updateMessage(""); updateProgress(-1.0); } @@ -862,23 +899,20 @@ public final class ImageGalleryController { @NbBundle.Messages({"CopyAnalyzedFiles.committingDb.status=committing image/video database", "CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.", "CopyAnalyzedFiles.errPopulating.errMsg=There was an error populating Image Gallery database."}) - static private class CopyAnalyzedFiles extends BulkTransferTask { + private class CopyAnalyzedFiles extends BulkTransferTask { - CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { - super(controller, taskDB, tskCase); + CopyAnalyzedFiles(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { + super(dataSourceObjId, controller, taskDB, tskCase); } @Override protected void cleanup(boolean success) { - // processFile will set success to fail if files are missing MIME types - controller.setStale(!success); + // at the end of the task, set the stale status based on the + // cumulative status of all data sources + controller.setStale(isDataSourcesTableStale()); } - @Override - List getFiles() throws TskCoreException { - controller.updateDataSourcesTable(); - return tskCase.findAllFilesWhere(DRAWABLE_QUERY); - } + @Override void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDbTransaction) throws TskCoreException { @@ -928,16 +962,13 @@ public final class ImageGalleryController { static private class PrePopulateDataSourceFiles extends BulkTransferTask { private static final Logger LOGGER = Logger.getLogger(PrePopulateDataSourceFiles.class.getName()); - - private final Content dataSource; - + /** * * @param dataSourceId Data source object ID */ - PrePopulateDataSourceFiles(Content dataSource, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { - super(controller, taskDB, tskCase); - this.dataSource = dataSource; + PrePopulateDataSourceFiles(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { + super(dataSourceObjId, controller, taskDB, tskCase); } @Override @@ -949,13 +980,6 @@ public final class ImageGalleryController { taskDB.insertFile(DrawableFile.create(f, false, false), tr, caseDBTransaction); } - @Override - List getFiles() throws TskCoreException { - long datasourceID = dataSource.getDataSource().getId(); - taskDB.insertDataSource(datasourceID); - return tskCase.findAllFilesWhere("data_source_obj_id = " + datasourceID + " AND " + DRAWABLE_QUERY); - } - @Override @NbBundle.Messages({"PrePopulateDataSourceFiles.prepopulatingDb.status=prepopulating image/video database",}) ProgressHandle getInitialProgressHandle() { @@ -1020,9 +1044,7 @@ public final class ImageGalleryController { "Unable to determine if file is drawable and not known. Not making any changes to DB. See the logs for details."); } } - } else { - setStale(true); - } + } } break; } @@ -1058,13 +1080,8 @@ public final class ImageGalleryController { if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) { Content newDataSource = (Content) evt.getNewValue(); if (isListeningEnabled()) { - queueDBTask(new PrePopulateDataSourceFiles(newDataSource, ImageGalleryController.this, getDatabase(), getSleuthKitCase())); - } else { - setStale(true); - } - } - else { - setStale(true); + queueDBTask(new PrePopulateDataSourceFiles(newDataSource.getId(), ImageGalleryController.this, getDatabase(), getSleuthKitCase())); + } } break; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java index 4ae1903de6..01b20eed77 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.imagegallery; import java.nio.file.Path; import java.nio.file.Paths; -import org.apache.commons.lang3.StringUtils; import static org.apache.commons.lang3.StringUtils.isNotBlank; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; @@ -85,10 +84,7 @@ public class ImageGalleryModule { */ public static boolean isDrawableDBStale(Case c) { if (c != null) { - String stale = new PerCaseProperties(c).getConfigSetting(ImageGalleryModule.MODULE_NAME, PerCaseProperties.STALE); - - return ( ImageGalleryController.getDefault().isDataSourcesTableStale() || - (StringUtils.isNotBlank(stale) ? Boolean.valueOf(stale) : false) ); + return ImageGalleryController.getDefault().isDataSourcesTableStale(); } else { return false; } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 6221de8947..dc756dcefb 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -102,7 +102,7 @@ public final class DrawableDB { private final PreparedStatement insertHashHitStmt; - private final PreparedStatement insertDataSourceStmt; + private final PreparedStatement updateDataSourceStmt; private final PreparedStatement updateFileStmt; private final PreparedStatement insertFileStmt; @@ -149,6 +149,15 @@ public final class DrawableDB { private final SleuthkitCase tskCase; private final ImageGalleryController controller; + /** + * Enum to track Image gallery db rebuild status for a data source + */ + public enum DrawableDbBuildStatusEnum { + UNKNOWN, /// no known status + IN_PROGRESS, /// drawable db rebuild has been started for the data source + COMPLETE; /// drawable db rebuild is complete for the data source + } + //////////////general database logic , mostly borrowed from sleuthkitcase /** * Lock to protect against concurrent write accesses to case database and to @@ -210,9 +219,9 @@ 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 + updateDataSourceStmt = prepareStatement( + "INSERT OR REPLACE INTO datasources (ds_obj_id, drawable_db_build_status) " //NON-NLS + + " VALUES (?,?)"); //NON-NLS removeFileStmt = prepareStatement("DELETE FROM drawable_files WHERE obj_id = ?"); //NON-NLS @@ -374,7 +383,8 @@ public final class DrawableDB { 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 + + " ds_obj_id integer UNIQUE NOT NULL, " + + " drawable_db_build_status VARCHAR(128) )"; //NON-NLS stmt.execute(sql); } catch (SQLException ex) { LOGGER.log(Level.SEVERE, "problem creating datasources table", ex); //NON-NLS @@ -763,20 +773,20 @@ public final class DrawableDB { /** - * Gets all data source object ids from datasources table + * Gets all data source object ids from datasources table, and their DrawableDbBuildStatusEnum * - * @return list of known data source object ids + * @return map of known data source object ids, and their db status */ - public Set getDataSourceIds() throws TskCoreException { + public Map getDataSourceDbBuildStatus() throws TskCoreException { Statement statement = null; ResultSet rs = null; - Set ret = new HashSet<>(); + Map map = new HashMap<>(); dbReadLock(); try { statement = con.createStatement(); - rs = statement.executeQuery("SELECT ds_obj_id FROM datasources "); //NON-NLS + rs = statement.executeQuery("SELECT ds_obj_id, drawable_db_build_status FROM datasources "); //NON-NLS while (rs.next()) { - ret.add(rs.getLong(1)); + map.put(rs.getLong(1), DrawableDbBuildStatusEnum.valueOf(rs.getString(2))); } } catch (SQLException e) { throw new TskCoreException("SQLException while getting data source object ids", e); @@ -797,24 +807,25 @@ public final class DrawableDB { } dbReadUnlock(); } - return ret; + return map; } /** - * Insert given data source object id into datasources table + * Insert/update given data source object id and it's DB rebuild status in the datasources table. * - * If the object id exists in the table already, it does nothing. + * If the object id exists in the table already, it updates the status * * @param dsObjectId data source object id to insert */ - public void insertDataSource(long dsObjectId) { + public void insertOrUpdateDataSource(long dsObjectId, DrawableDbBuildStatusEnum status ) { dbWriteLock(); try { - // "INSERT OR IGNORE/ INTO datasources (ds_obj_id)" - insertDataSourceStmt.setLong(1,dsObjectId); + // "INSERT OR REPLACE INTO datasources (ds_obj_id, drawable_db_build_status) " //NON-NLS + updateDataSourceStmt.setLong(1,dsObjectId); + updateDataSourceStmt.setString(2, status.name()); - insertDataSourceStmt.executeUpdate(); + updateDataSourceStmt.executeUpdate(); } catch (SQLException | NullPointerException ex) { LOGGER.log(Level.SEVERE, "failed to insert/update datasources table", ex); //NON-NLS } finally { From d2f75af454582760d23d0ac45cfd6b36e0f23b29 Mon Sep 17 00:00:00 2001 From: Raman Date: Mon, 13 Aug 2018 08:21:55 -0400 Subject: [PATCH 18/84] Addressed review comment. --- .../autopsy/imagegallery/ImageGalleryController.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 4894a3cb35..4c2be3909b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -400,15 +400,11 @@ public final class ImageGalleryController { * */ public void rebuildDB() { - // queue a rebuild task for each stale data source Set staleDataSources = getStaleDataSourceIds(); - if (!staleDataSources.isEmpty()) { - staleDataSources.forEach((id) -> { - queueDBTask(new CopyAnalyzedFiles(id, instance, db, sleuthKitCase)); - }); - - } + staleDataSources.forEach((id) -> { + queueDBTask(new CopyAnalyzedFiles(id, instance, db, sleuthKitCase)); + }); } /** From 63860fd551bd20059365fd2277c88c56cc1a62f0 Mon Sep 17 00:00:00 2001 From: Raman Date: Mon, 13 Aug 2018 10:27:52 -0400 Subject: [PATCH 19/84] Addressed Codacy comment --- .../autopsy/imagegallery/ImageGalleryController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 4c2be3909b..29ad69f149 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -402,8 +402,8 @@ public final class ImageGalleryController { public void rebuildDB() { // queue a rebuild task for each stale data source Set staleDataSources = getStaleDataSourceIds(); - staleDataSources.forEach((id) -> { - queueDBTask(new CopyAnalyzedFiles(id, instance, db, sleuthKitCase)); + staleDataSources.forEach((dataSourceObjId) -> { + queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, instance, db, sleuthKitCase)); }); } From ae5621db1637f9ee1e19c011553f1d84c6831d5e Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 15 Aug 2018 12:01:01 -0400 Subject: [PATCH 20/84] Address review comments --- .../sleuthkit/autopsy/imagegallery/ImageGalleryController.java | 3 +-- .../org/sleuthkit/autopsy/imagegallery/PerCaseProperties.java | 2 -- .../sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 29ad69f149..0781a358dd 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -401,8 +401,7 @@ public final class ImageGalleryController { */ public void rebuildDB() { // queue a rebuild task for each stale data source - Set staleDataSources = getStaleDataSourceIds(); - staleDataSources.forEach((dataSourceObjId) -> { + getStaleDataSourceIds().forEach((dataSourceObjId) -> { queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, instance, db, sleuthKitCase)); }); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/PerCaseProperties.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/PerCaseProperties.java index d87b13e880..56811352f0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/PerCaseProperties.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/PerCaseProperties.java @@ -40,8 +40,6 @@ class PerCaseProperties { public static final String ENABLED = "enabled"; //NON-NLS - public static final String STALE = "stale"; //NON-NLS - private final Case theCase; PerCaseProperties(Case c) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index dc756dcefb..99b5e2bd63 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -786,7 +786,7 @@ public final class DrawableDB { statement = con.createStatement(); rs = statement.executeQuery("SELECT ds_obj_id, drawable_db_build_status FROM datasources "); //NON-NLS while (rs.next()) { - map.put(rs.getLong(1), DrawableDbBuildStatusEnum.valueOf(rs.getString(2))); + map.put(rs.getLong("ds_obj_id"), DrawableDbBuildStatusEnum.valueOf(rs.getString("drawable_db_build_status"))); } } catch (SQLException e) { throw new TskCoreException("SQLException while getting data source object ids", e); From 5f823efc1f4d3343b4e63af4e980d46b336bb4fe Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 17 Aug 2018 15:20:40 +0200 Subject: [PATCH 21/84] add Data Source chooser to Toolbar --- .../autopsy/imagegallery/gui/Toolbar.fxml | 43 +++++++---- .../autopsy/imagegallery/gui/Toolbar.java | 72 ++++++++++++++---- .../imagegallery/images/datasource.png | Bin 0 -> 1286 bytes 3 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/images/datasource.png diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml index a8ced6deda..d8e6c8c231 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml @@ -1,6 +1,7 @@ + @@ -12,29 +13,41 @@ - + - - - + + + + + + + + + + + - - - - - + + + + + + + + + + + - + - + diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 8f4fd3e05d..a7f3db4ded 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -21,18 +21,18 @@ package org.sleuthkit.autopsy.imagegallery.gui; import com.google.common.collect.Lists; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.DoubleProperty; -import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.geometry.Insets; import javafx.scene.Cursor; import javafx.scene.Node; -import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.ListCell; @@ -71,45 +71,36 @@ public class Toolbar extends ToolBar { private static final int SIZE_SLIDER_DEFAULT = 100; @FXML - private CheckBox filterDataSourcesCheckBox; - - @FXML - private ComboBox dataSourceComboBox; - + private ComboBox> dataSourceComboBox; @FXML private ImageView sortHelpImageView; - @FXML private ComboBox> groupByBox; - @FXML private Slider sizeSlider; - @FXML private SplitMenuButton catGroupMenuButton; - @FXML private SplitMenuButton tagGroupMenuButton; - @FXML private Label groupByLabel; - @FXML private Label tagImageViewLabel; - @FXML private Label categoryImageViewLabel; - @FXML private Label thumbnailSizeLabel; - private final ImageGalleryController controller; private SortChooser sortChooser; + private final ImageGalleryController controller; + private final ObservableList> dataSources = FXCollections.observableArrayList(); + private final InvalidationListener queryInvalidationListener = new InvalidationListener() { @Override public void invalidated(Observable invalidated) { controller.getGroupManager().regroup( + //dataSourceComboBox.getSelectionModel().getSelectedItem(), TODO-1010/7: incorporate the selected datasource into this call. groupByBox.getSelectionModel().getSelectedItem(), sortChooser.getComparator(), sortChooser.getSortOrder(), @@ -117,10 +108,6 @@ public class Toolbar extends ToolBar { } }; - public DoubleProperty thumbnailSizeProperty() { - return sizeSlider.valueProperty(); - } - @FXML @NbBundle.Messages({"Toolbar.groupByLabel=Group By:", "Toolbar.sortByLabel=Sort By:", @@ -133,7 +120,6 @@ public class Toolbar extends ToolBar { "Toolbar.sortHelpTitle=Group Sorting",}) void initialize() { assert groupByBox != null : "fx:id=\"groupByBox\" was not injected: check your FXML file 'Toolbar.fxml'."; - assert filterDataSourcesCheckBox != null : "fx:id=\"filterDataSourcesCheckBox\" was not injected: check your FXML file 'Toolbar.fxml'."; assert dataSourceComboBox != null : "fx:id=\"dataSourceComboBox\" was not injected: check your FXML file 'Toolbar.fxml'."; assert sortHelpImageView != null : "fx:id=\"sortHelpImageView\" was not injected: check your FXML file 'Toolbar.fxml'."; assert tagImageViewLabel != null : "fx:id=\"tagImageViewLabel\" was not injected: check your FXML file 'Toolbar.fxml'."; @@ -147,23 +133,21 @@ public class Toolbar extends ToolBar { Platform.runLater(() -> syncGroupControlsEnabledState(newViewState)); }); syncGroupControlsEnabledState(controller.viewState().get()); - dataSourceComboBox.disableProperty().bind(filterDataSourcesCheckBox.selectedProperty().not()); + dataSourceComboBox.setCellFactory(param -> new DataSourceCell()); dataSourceComboBox.setButtonCell(new DataSourceCell()); + dataSourceComboBox.setItems(dataSources); - dataSourceComboBox.getSelectionModel().selectedItemProperty() - .addListener((ObservableValue observable, DataSource oldValue, DataSource newValue) -> { - /* TODO-1010/7: record newly selected datasource(id) - * somewhere? controller? probably setting that property - * will trigger a refresh... - */ - }); try { /* - * TODO-1005: Getting the datasources and tShe tagnames are Db + * TODO-1005: Getting the datasources and the tagnames are Db * querries. We should probably push them off to a BG thread. */ - dataSourceComboBox.setItems(FXCollections.observableArrayList(controller.getSleuthKitCase().getDataSources())); + dataSources.add(Optional.empty()); + controller.getSleuthKitCase().getDataSources() + .forEach(dataSource -> dataSources.add(Optional.of(dataSource))); + /* TODO: 1010/7 push data source selected in dialog into UI */ + dataSourceComboBox.getSelectionModel().selectFirst(); TagGroupAction followUpGroupAction = new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller); tagGroupMenuButton.setOnAction(followUpGroupAction); @@ -213,7 +197,7 @@ public class Toolbar extends ToolBar { groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs())); groupByBox.getSelectionModel().select(DrawableAttribute.PATH); - groupByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener); + groupByBox.disableProperty().bind(ImageGalleryController.getDefault().regroupDisabled()); groupByBox.setCellFactory(listView -> new AttributeListCell()); groupByBox.setButtonCell(new AttributeListCell()); @@ -228,7 +212,6 @@ public class Toolbar extends ToolBar { queryInvalidationListener.invalidated(observable); }); - sortChooser.sortOrderProperty().addListener(queryInvalidationListener); sortChooser.setComparator(controller.getGroupManager().getSortBy()); getItems().add(2, sortChooser); sortHelpImageView.setCursor(Cursor.HAND); @@ -240,6 +223,15 @@ public class Toolbar extends ToolBar { Bundle.Toolbar_sortHelpTitle(), sortHelpImageView.getImage(), text); }); + + + dataSourceComboBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener); + groupByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener); + sortChooser.sortOrderProperty().addListener(queryInvalidationListener); + } + + public DoubleProperty thumbnailSizeProperty() { + return sizeSlider.valueProperty(); } /** @@ -291,15 +283,15 @@ public class Toolbar extends ToolBar { /** * Cell used to represent a DataSource in the dataSourceComboBoc */ - static private class DataSourceCell extends ListCell { + static private class DataSourceCell extends ListCell> { @Override - protected void updateItem(DataSource item, boolean empty) { + protected void updateItem(Optional item, boolean empty) { super.updateItem(item, empty); if (empty || item == null) { - setText("All Data Sources"); + setText(""); } else { - setText(item.getName()); + setText(item.map(DataSource::getName).orElse("All")); } } } From 9fa3e30a1d3c15712649c6468ebd5b1eaa7499fe Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 21 Aug 2018 16:32:35 +0200 Subject: [PATCH 23/84] add parens for readability, and javadoc. --- .../sleuthkit/autopsy/imagegallery/gui/Toolbar.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index a7f3db4ded..689dea792d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -223,8 +223,7 @@ public class Toolbar extends ToolBar { Bundle.Toolbar_sortHelpTitle(), sortHelpImageView.getImage(), text); }); - - + dataSourceComboBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener); groupByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener); sortChooser.sortOrderProperty().addListener(queryInvalidationListener); @@ -261,8 +260,16 @@ public class Toolbar extends ToolBar { popOver.show(owner); } + /** + * Disable the tag and catagory controls if and only if there is no group + * selected. + * + * @param newViewState The GroupViewState to use as a source of the + * selection. + */ private void syncGroupControlsEnabledState(GroupViewState newViewState) { - boolean noGroupSelected = newViewState == null || newViewState.getGroup() == null; + boolean noGroupSelected = (null == newViewState) + || (null == newViewState.getGroup()); tagGroupMenuButton.setDisable(noGroupSelected); catGroupMenuButton.setDisable(noGroupSelected); From 5bf5db7a9283aec9cbe0d37e1d37d6a0a13fc4f8 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 23 Aug 2018 15:30:35 +0200 Subject: [PATCH 24/84] sync datasources in IG toobar as they are added/removed --- .../autopsy/imagegallery/gui/GuiUtils.java | 4 +- .../autopsy/imagegallery/gui/Toolbar.fxml | 5 +- .../autopsy/imagegallery/gui/Toolbar.java | 174 ++++++++++++------ 3 files changed, 125 insertions(+), 58 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java index 2ff6f24d90..857aae31a6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java @@ -25,13 +25,13 @@ import org.controlsfx.control.action.Action; /** * Static utility methods for working with GUI components */ -public class GuiUtils { +public final class GuiUtils { private GuiUtils() { } /** - * create a MenuItem that performs the given action and also set the Action + * Create a MenuItem that performs the given action and also set the Action * as the action for the given Button. Usefull to have a SplitMenuButton * remember the last chosen menu item as its action. * diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml index a036ec0bd6..45028da397 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml @@ -1,7 +1,6 @@ - @@ -17,7 +16,7 @@ - diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 689dea792d..3bd16d1a5b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -18,10 +18,19 @@ */ package org.sleuthkit.autopsy.imagegallery.gui; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.Executors; import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.InvalidationListener; @@ -45,10 +54,15 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.scene.text.Text; +import javafx.util.StringConverter; +import org.controlsfx.control.Notifications; import org.controlsfx.control.PopOver; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; +import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED; +import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_DELETED; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; @@ -59,7 +73,6 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.TskCoreException; /** * Controller for the ToolBar @@ -67,6 +80,9 @@ import org.sleuthkit.datamodel.TskCoreException; public class Toolbar extends ToolBar { private static final Logger LOGGER = Logger.getLogger(Toolbar.class.getName()); + ListeningExecutorService exec + = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder().setNameFormat("Image Gallery Toolbar BG Thread").build())); private static final int SIZE_SLIDER_DEFAULT = 100; @@ -94,13 +110,15 @@ public class Toolbar extends ToolBar { private SortChooser sortChooser; private final ImageGalleryController controller; + + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private final ObservableList> dataSources = FXCollections.observableArrayList(); private final InvalidationListener queryInvalidationListener = new InvalidationListener() { @Override public void invalidated(Observable invalidated) { controller.getGroupManager().regroup( - //dataSourceComboBox.getSelectionModel().getSelectedItem(), TODO-1010/7: incorporate the selected datasource into this call. + //dataSourceComboBox.getSelectionModel().getSelectedItem(), TODO-1017: incorporate the selected datasource into this call. groupByBox.getSelectionModel().getSelectedItem(), sortChooser.getComparator(), sortChooser.getSortOrder(), @@ -109,15 +127,17 @@ public class Toolbar extends ToolBar { }; @FXML - @NbBundle.Messages({"Toolbar.groupByLabel=Group By:", - "Toolbar.sortByLabel=Sort By:", - "Toolbar.ascRadio=Ascending", - "Toolbar.descRadio=Descending", - "Toolbar.tagImageViewLabel=Tag Group's Files:", - "Toolbar.categoryImageViewLabel=Categorize Group's Files:", - "Toolbar.thumbnailSizeLabel=Thumbnail Size (px):", - "Toolbar.sortHelp=The sort direction (ascending/descending) affects the queue of unseen groups that Image Gallery maintains, but changes to this queue aren't apparent until the \"Next Unseen Group\" button is pressed.", - "Toolbar.sortHelpTitle=Group Sorting",}) + @NbBundle.Messages( + {"Toolbar.groupByLabel=Group By:", + "Toolbar.sortByLabel=Sort By:", + "Toolbar.ascRadio=Ascending", + "Toolbar.descRadio=Descending", + "Toolbar.tagImageViewLabel=Tag Group's Files:", + "Toolbar.categoryImageViewLabel=Categorize Group's Files:", + "Toolbar.thumbnailSizeLabel=Thumbnail Size (px):", + "Toolbar.sortHelp=The sort direction (ascending/descending) affects the queue of unseen groups that Image Gallery maintains, but changes to this queue aren't apparent until the \"Next Unseen Group\" button is pressed.", + "Toolbar.sortHelpTitle=Group Sorting", + "Toolbar.getDataSources.errMessage=Unable to get datasources for current case."}) void initialize() { assert groupByBox != null : "fx:id=\"groupByBox\" was not injected: check your FXML file 'Toolbar.fxml'."; assert dataSourceComboBox != null : "fx:id=\"dataSourceComboBox\" was not injected: check your FXML file 'Toolbar.fxml'."; @@ -136,47 +156,33 @@ public class Toolbar extends ToolBar { dataSourceComboBox.setCellFactory(param -> new DataSourceCell()); dataSourceComboBox.setButtonCell(new DataSourceCell()); - dataSourceComboBox.setItems(dataSources); - - try { - /* - * TODO-1005: Getting the datasources and the tagnames are Db - * querries. We should probably push them off to a BG thread. - */ - dataSources.add(Optional.empty()); - controller.getSleuthKitCase().getDataSources() - .forEach(dataSource -> dataSources.add(Optional.of(dataSource))); - /* TODO: 1010/7 push data source selected in dialog into UI */ - dataSourceComboBox.getSelectionModel().selectFirst(); - - TagGroupAction followUpGroupAction = new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller); - tagGroupMenuButton.setOnAction(followUpGroupAction); - tagGroupMenuButton.setText(followUpGroupAction.getText()); - tagGroupMenuButton.setGraphic(followUpGroupAction.getGraphic()); - - } catch (TskCoreException ex) { - /* - * The problem appears to be a timing issue where a case is closed - * before this initialization is completed, which It appears to be - * harmless, so we are temporarily changing this log message to a - * WARNING. - * - * TODO (JIRA-3010): SEVERE error logged by image Gallery UI - */ - if (Case.isCaseOpen()) { - LOGGER.log(Level.WARNING, "Could not create Follow Up tag menu item", ex); //NON-NLS - } else { - // don't add stack trace to log because it makes looking for real errors harder - LOGGER.log(Level.INFO, "Unable to get tag name. Case is closed."); //NON-NLS + dataSourceComboBox.setConverter(new StringConverter>() { + @Override + public String toString(Optional object) { + return object.map(DataSource::getName).orElse("All"); } - } - tagGroupMenuButton.showingProperty().addListener(showing -> { - if (tagGroupMenuButton.isShowing()) { - List selTagMenues = Lists.transform(controller.getTagsManager().getNonCategoryTagNames(), - tagName -> GuiUtils.createAutoAssigningMenuItem(tagGroupMenuButton, new TagGroupAction(tagName, controller))); - tagGroupMenuButton.getItems().setAll(selTagMenues); + + @Override + public Optional fromString(String string) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } }); + dataSourceComboBox.setItems(dataSources); + + Case.addEventTypeSubscriber(ImmutableSet.of(DATA_SOURCE_ADDED, DATA_SOURCE_DELETED), + evt -> { + Platform.runLater(() -> { + Optional selectedItem = dataSourceComboBox.getSelectionModel().getSelectedItem(); + syncDataSources() + .addListener(() -> dataSourceComboBox.getSelectionModel().select(selectedItem), Platform::runLater); + }); + }); + syncDataSources(); + + /* TODO: 1010/7 push data source selected in dialog into UI */ + dataSourceComboBox.getSelectionModel().selectFirst(); + + initTagMenuButton(); CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(DhsImageCategory.FIVE, controller); catGroupMenuButton.setOnAction(cat5GroupAction); @@ -229,6 +235,67 @@ public class Toolbar extends ToolBar { sortChooser.sortOrderProperty().addListener(queryInvalidationListener); } + private void initTagMenuButton() { + ListenableFuture future = exec.submit(() -> new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller)); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(TagGroupAction followUpGroupAction) { + tagGroupMenuButton.setOnAction(followUpGroupAction); + tagGroupMenuButton.setText(followUpGroupAction.getText()); + tagGroupMenuButton.setGraphic(followUpGroupAction.getGraphic()); + } + + @Override + public void onFailure(Throwable t) { + /* + * The problem appears to be a timing issue where a case is + * closed before this initialization is completed, which It + * appears to be harmless, so we are temporarily changing this + * log message to a WARNING. + * + * TODO (JIRA-3010): SEVERE error logged by image Gallery UI + */ + if (Case.isCaseOpen()) { + LOGGER.log(Level.WARNING, "Could not create Follow Up tag menu item", t); //NON-NLS + } else { + // don't add stack trace to log because it makes looking for real errors harder + LOGGER.log(Level.INFO, "Unable to get tag name. Case is closed."); //NON-NLS + } + } + }, Platform::runLater); + + tagGroupMenuButton.showingProperty().addListener(showing -> { + if (tagGroupMenuButton.isShowing()) { + List selTagMenues = Lists.transform(controller.getTagsManager().getNonCategoryTagNames(), + tagName -> GuiUtils.createAutoAssigningMenuItem(tagGroupMenuButton, new TagGroupAction(tagName, controller))); + tagGroupMenuButton.getItems().setAll(selTagMenues); + } + }); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.ANY) + private ListenableFuture> syncDataSources() { + ListenableFuture> future = exec.submit(controller.getSleuthKitCase()::getDataSources); + Futures.addCallback(future, new FutureCallback>() { + @Override + public void onSuccess(List result) { + dataSources.setAll(Collections.singleton(Optional.empty())); + result.forEach(dataSource -> dataSources.add(Optional.of(dataSource))); + } + + @Override + public void onFailure(Throwable t) { + LOGGER.log(Level.SEVERE, "Unable to get datasources for current case.", t); //NON-NLS + Notifications.create().owner(getScene().getRoot()) + .title("Image Gallery Error") + .text(Bundle.Toolbar_getDataSources_errMessage()) + .showError(); + } + }, Platform::runLater); + + return future; + } + public DoubleProperty thumbnailSizeProperty() { return sizeSlider.valueProperty(); } @@ -270,9 +337,10 @@ public class Toolbar extends ToolBar { private void syncGroupControlsEnabledState(GroupViewState newViewState) { boolean noGroupSelected = (null == newViewState) || (null == newViewState.getGroup()); - - tagGroupMenuButton.setDisable(noGroupSelected); - catGroupMenuButton.setDisable(noGroupSelected); + Platform.runLater(() -> { + tagGroupMenuButton.setDisable(noGroupSelected); + catGroupMenuButton.setDisable(noGroupSelected); + }); } public void reset() { @@ -296,7 +364,7 @@ public class Toolbar extends ToolBar { protected void updateItem(Optional item, boolean empty) { super.updateItem(item, empty); if (empty || item == null) { - setText(""); + setText("All"); } else { setText(item.map(DataSource::getName).orElse("All")); } From 81a0fa511d966286771c200fcc1a5f63a489b9cb Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 23 Aug 2018 17:06:58 +0200 Subject: [PATCH 25/84] datasource popup and WIP wiring of selected values --- .../autopsy/timeline/PromptDialogManager.java | 8 +- .../imagegallery/ImageGalleryController.java | 193 +++++++------- .../ImageGalleryTopComponent.java | 50 +++- .../imagegallery/actions/OpenAction.java | 49 +--- .../datamodel/grouping/GroupKey.java | 36 ++- .../datamodel/grouping/GroupManager.java | 235 ++++++++++-------- .../autopsy/imagegallery/gui/GuiUtils.java | 33 +++ .../autopsy/imagegallery/gui/Toolbar.java | 72 +++--- 8 files changed, 368 insertions(+), 308 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java index cc58dfd80a..f9f1e12cfa 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015-17 Basis Technology Corp. + * Copyright 2015-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,9 +56,7 @@ public final class PromptDialogManager { @NbBundle.Messages("PrompDialogManager.buttonType.update=Update DB") private static final ButtonType UPDATE = new ButtonType(Bundle.PrompDialogManager_buttonType_update(), ButtonBar.ButtonData.OK_DONE); - /** - * Image to use as title bar icon in dialogs - */ + /** Image to use as title bar icon in dialogs */ private static final Image AUTOPSY_ICON; static { @@ -222,7 +220,7 @@ public final class PromptDialogManager { dialog.setHeaderText(Bundle.PromptDialogManager_showTooManyFiles_headerText()); dialog.showAndWait(); } - + @NbBundle.Messages({ "PromptDialogManager.showTimeLineDisabledMessage.contentText=" + "Timeline functionality is not available yet." diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 0781a358dd..b290699ef6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -220,9 +220,9 @@ public final class ImageGalleryController { try { // 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 ( isEnabled && !wasPreviouslyEnabled && - ImageGalleryModule.isDrawableDBStale(Case.getCurrentCaseThrows()) && - (Case.getCurrentCaseThrows().getCaseType() == CaseType.SINGLE_USER_CASE) ) { + if (isEnabled && !wasPreviouslyEnabled + && ImageGalleryModule.isDrawableDBStale(Case.getCurrentCaseThrows()) + && (Case.getCurrentCaseThrows().getCaseType() == CaseType.SINGLE_USER_CASE)) { //populate the db this.rebuildDB(); } @@ -255,8 +255,8 @@ public final class ImageGalleryController { regroupDisabled.addListener(observable -> checkForGroups()); IngestManager ingestManager = IngestManager.getInstance(); - PropertyChangeListener ingestEventHandler = - propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled); + PropertyChangeListener ingestEventHandler + = propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled); ingestManager.addIngestModuleEventListener(ingestEventHandler); ingestManager.addIngestJobEventListener(ingestEventHandler); @@ -300,7 +300,7 @@ public final class ImageGalleryController { */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) @NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. " - + " No groups will be available until ingest is finished and listening is re-enabled.", + + " No groups will be available until ingest is finished and listening is re-enabled.", "ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.", "ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.", "ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. " @@ -397,15 +397,15 @@ public final class ImageGalleryController { /** * Rebuilds the DrawableDB database. - * + * */ public void rebuildDB() { // queue a rebuild task for each stale data source getStaleDataSourceIds().forEach((dataSourceObjId) -> { queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, instance, db, sleuthKitCase)); - }); + }); } - + /** * reset the state of the controller (eg if the case is closed) */ @@ -433,63 +433,63 @@ public final class ImageGalleryController { /** * Checks if the datasources table in drawable DB is stale. - * + * * @return true if datasources table is stale */ boolean isDataSourcesTableStale() { return (getStaleDataSourceIds().isEmpty() == false); } - + /** * Returns a set of data source object ids that are stale. - * - * This includes any data sources already in the table, that are not in COMPLETE status, - * or any data sources that might have been added to the case, but are not in the datasources table. - * + * + * This includes any data sources already in the table, that are not in + * COMPLETE status, or any data sources that might have been added to the + * case, but are not in the datasources table. + * * @return list of data source object ids that are stale. */ Set getStaleDataSourceIds() { - + Set staleDataSourceIds = new HashSet<>(); - + // no current case open to check if ((null == getDatabase()) || (null == getSleuthKitCase())) { return staleDataSourceIds; } - + try { - Map knownDataSourceIds= getDatabase().getDataSourceDbBuildStatus(); - + Map knownDataSourceIds = getDatabase().getDataSourceDbBuildStatus(); + List dataSources = getSleuthKitCase().getDataSources(); Set caseDataSourceIds = new HashSet<>(); dataSources.forEach((dataSource) -> { caseDataSourceIds.add(dataSource.getId()); }); - + // collect all data sources already in the table, that are not yet COMPLETE - knownDataSourceIds.entrySet().stream().forEach((Map.Entry t) -> { - DrawableDbBuildStatusEnum status = t.getValue(); - if (DrawableDbBuildStatusEnum.COMPLETE != status) { - staleDataSourceIds.add(t.getKey()); - } - }); - - // collect any new data sources in the case. - caseDataSourceIds.forEach((Long id) -> { - if (!knownDataSourceIds.containsKey(id)) { - staleDataSourceIds.add(id); + knownDataSourceIds.entrySet().stream().forEach((Map.Entry t) -> { + DrawableDbBuildStatusEnum status = t.getValue(); + if (DrawableDbBuildStatusEnum.COMPLETE != status) { + staleDataSourceIds.add(t.getKey()); } - }); - + }); + + // collect any new data sources in the case. + caseDataSourceIds.forEach((Long id) -> { + if (!knownDataSourceIds.containsKey(id)) { + staleDataSourceIds.add(id); + } + }); + return staleDataSourceIds; - } - catch (TskCoreException ex) { + } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Image Gallery failed to check if datasources table is stale.", ex); return staleDataSourceIds; } - + } - + synchronized private void shutDownDBExecutor() { if (dbExecutor != null) { dbExecutor.shutdownNow(); @@ -562,8 +562,8 @@ public final class ImageGalleryController { void onStart() { Platform.setImplicitExit(false); LOGGER.info("setting up ImageGallery listeners"); //NON-NLS - - IngestManager.getInstance().addIngestJobEventListener( new IngestJobEventListener()); + + IngestManager.getInstance().addIngestJobEventListener(new IngestJobEventListener()); IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener()); Case.addPropertyChangeListener(new CaseEventListener()); } @@ -736,25 +736,25 @@ public final class ImageGalleryController { @NbBundle.Messages({"BulkTask.committingDb.status=committing image/video database", "BulkTask.stopCopy.status=Stopping copy to drawable db task.", "BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."}) - /** - * Base abstract class for various methods of copying image files data, - * for a given data source, into the Image gallery DB. + /** + * Base abstract class for various methods of copying image files data, for + * a given data source, into the Image gallery DB. */ abstract static private class BulkTransferTask extends BackgroundTask { - - static private final String FILE_EXTENSION_CLAUSE = - "(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 - + "') "; - + + static private final String FILE_EXTENSION_CLAUSE + = "(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 + + "') "; + final String DRAWABLE_QUERY; final String DATASOURCE_CLAUSE; - + final ImageGalleryController controller; final DrawableDB taskDB; final SleuthkitCase tskCase; @@ -768,36 +768,37 @@ public final class ImageGalleryController { this.taskDB = taskDB; this.tskCase = tskCase; this.dataSourceObjId = dataSourceObjId; - + DATASOURCE_CLAUSE = " (data_source_obj_id = " + dataSourceObjId + ") "; - - DRAWABLE_QUERY = - DATASOURCE_CLAUSE + - " AND ( " + - //grab files with supported extension + + DRAWABLE_QUERY + = DATASOURCE_CLAUSE + + " AND ( " + + //grab files with supported extension FILE_EXTENSION_CLAUSE - //grab files with supported mime-types - + " OR " + MIMETYPE_CLAUSE //NON-NLS - //grab files with image or video mime-types even if we don't officially support them - + " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS + //grab files with supported mime-types + + " OR " + MIMETYPE_CLAUSE //NON-NLS + //grab files with image or video mime-types even if we don't officially support them + + " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS } /** - * + * * @param success true if the transfer was successful */ abstract void cleanup(boolean success); /** * Gets a list of files to process. - * + * * @return list of files to process - * @throws TskCoreException + * + * @throws TskCoreException */ List getFiles() throws TskCoreException { return tskCase.findAllFilesWhere(DRAWABLE_QUERY); } - + abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) throws TskCoreException; @Override @@ -806,7 +807,6 @@ public final class ImageGalleryController { progressHandle.start(); updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status()); - DrawableDB.DrawableTransaction drawableDbTransaction = null; CaseDbTransaction caseDbTransaction = null; try { @@ -815,11 +815,11 @@ public final class ImageGalleryController { progressHandle.switchToDeterminate(files.size()); taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS); - + updateProgress(0.0); taskCompletionStatus = true; int workDone = 0; - + //do in transaction drawableDbTransaction = taskDB.beginTransaction(); caseDbTransaction = tskCase.beginTransaction(); @@ -828,10 +828,9 @@ public final class ImageGalleryController { LOGGER.log(Level.WARNING, "Task cancelled or interrupted: not all contents may be transfered to drawable database."); //NON-NLS taskCompletionStatus = false; progressHandle.finish(); - + break; } - processFile(f, drawableDbTransaction, caseDbTransaction); @@ -850,7 +849,7 @@ public final class ImageGalleryController { taskDB.commitTransaction(drawableDbTransaction, true); caseDbTransaction.commit(); - } catch (TskCoreException ex) { + } catch (TskCoreException ex) { if (null != drawableDbTransaction) { taskDB.rollbackTransaction(drawableDbTransaction); } @@ -858,7 +857,7 @@ public final class ImageGalleryController { try { caseDbTransaction.rollback(); } catch (TskCoreException ex2) { - LOGGER.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS + LOGGER.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS } } progressHandle.progress(Bundle.BulkTask_stopCopy_status()); @@ -869,7 +868,7 @@ public final class ImageGalleryController { } finally { progressHandle.finish(); if (taskCompletionStatus) { - taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.COMPLETE); + taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.COMPLETE); } updateMessage(""); updateProgress(-1.0); @@ -878,7 +877,7 @@ public final class ImageGalleryController { } abstract ProgressHandle getInitialProgressHandle(); - + protected void setTaskCompletionStatus(boolean status) { taskCompletionStatus = status; } @@ -907,8 +906,6 @@ public final class ImageGalleryController { controller.setStale(isDataSourcesTableStale()); } - - @Override void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDbTransaction) throws TskCoreException { final boolean known = f.getKnown() == TskData.FileKnown.KNOWN; @@ -919,15 +916,14 @@ public final class ImageGalleryController { try { //supported mimetype => analyzed - if ( null != f.getMIMEType() && FileTypeUtils.hasDrawableMIMEType(f)) { - taskDB.updateFile(DrawableFile.create(f, true, false), tr, caseDbTransaction ); - } - else { + if (null != f.getMIMEType() && FileTypeUtils.hasDrawableMIMEType(f)) { + taskDB.updateFile(DrawableFile.create(f, true, false), tr, caseDbTransaction); + } else { // if mimetype of the file hasn't been ascertained, ingest might not have completed yet. if (null == f.getMIMEType()) { // set to false to force the DB to be marked as stale this.setTaskCompletionStatus(false); - } else { + } else { //unsupported mimtype => analyzed but shouldn't include taskDB.removeFile(f.getId(), tr); } @@ -957,7 +953,7 @@ public final class ImageGalleryController { static private class PrePopulateDataSourceFiles extends BulkTransferTask { private static final Logger LOGGER = Logger.getLogger(PrePopulateDataSourceFiles.class.getName()); - + /** * * @param dataSourceId Data source object ID @@ -1039,7 +1035,7 @@ public final class ImageGalleryController { "Unable to determine if file is drawable and not known. Not making any changes to DB. See the logs for details."); } } - } + } } break; } @@ -1076,10 +1072,10 @@ public final class ImageGalleryController { Content newDataSource = (Content) evt.getNewValue(); if (isListeningEnabled()) { queueDBTask(new PrePopulateDataSourceFiles(newDataSource.getId(), ImageGalleryController.this, getDatabase(), getSleuthKitCase())); - } + } } break; - + case CONTENT_TAG_ADDED: final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt; if (getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) { @@ -1095,23 +1091,22 @@ 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.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 (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 @@ -1120,15 +1115,15 @@ public final class ImageGalleryController { SwingUtilities.invokeLater(() -> { if (isListeningEnabled() && ImageGalleryTopComponent.isImageGalleryOpen()) { - int answer = JOptionPane.showConfirmDialog(ImageGalleryTopComponent.getTopComponent(), + int answer = JOptionPane.showConfirmDialog(ImageGalleryTopComponent.getTopComponent(), Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_msg(), - Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_title(), + Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_title(), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); switch (answer) { case JOptionPane.YES_OPTION: - rebuildDB(); - break; + rebuildDB(); + break; case JOptionPane.NO_OPTION: case JOptionPane.CANCEL_OPTION: default: diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index ce06c52893..4e4dd2fbd4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -19,17 +19,21 @@ package org.sleuthkit.autopsy.imagegallery; import java.util.List; +import java.util.Optional; import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.embed.swing.JFXPanel; import javafx.scene.Scene; +import javafx.scene.control.ChoiceDialog; +import javafx.scene.control.Dialog; import javafx.scene.control.SplitPane; import javafx.scene.control.TabPane; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; +import javafx.stage.Modality; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; import org.openide.util.Lookup; @@ -39,7 +43,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.GuiUtils; import org.sleuthkit.autopsy.imagegallery.gui.StatusBar; import org.sleuthkit.autopsy.imagegallery.gui.SummaryTablePane; import org.sleuthkit.autopsy.imagegallery.gui.Toolbar; @@ -47,6 +51,8 @@ import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.GroupPane; import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.MetaDataPane; import org.sleuthkit.autopsy.imagegallery.gui.navpanel.GroupTree; import org.sleuthkit.autopsy.imagegallery.gui.navpanel.HashHitGroupList; +import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.TskCoreException; /** * Top component which displays ImageGallery interface. @@ -70,7 +76,7 @@ import org.sleuthkit.autopsy.imagegallery.gui.navpanel.HashHitGroupList; public final class ImageGalleryTopComponent extends TopComponent implements ExplorerManager.Provider, Lookup.Provider { public final static String PREFERRED_ID = "ImageGalleryTopComponent"; // NON-NLS - private static final Logger LOGGER = Logger.getLogger(ImageGalleryTopComponent.class.getName()); + private static final Logger logger = Logger.getLogger(ImageGalleryTopComponent.class.getName()); private static volatile boolean topComponentInitialized = false; private final ExplorerManager em = new ExplorerManager(); @@ -91,27 +97,27 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl /** * 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); + + final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID); if (topComponent != null) { return topComponent.isOpened(); } - return false; + 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); + 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) { @@ -121,7 +127,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl // } // } // timeLineController.openTimeLine(); - final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); + final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); if (tc != null) { topComponentInitialized = true; if (tc.isOpened() == false) { @@ -139,7 +145,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl try { etc.close(); } catch (Exception e) { - LOGGER.log(Level.SEVERE, "failed to close " + PREFERRED_ID, e); // NON-NLS + logger.log(Level.SEVERE, "failed to close " + PREFERRED_ID, e); // NON-NLS } } } @@ -149,10 +155,28 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl setName(Bundle.CTL_ImageGalleryTopComponent()); initComponents(); - Platform.runLater(() -> {//initialize jfx ui + Platform.runLater(() -> { + + //initialize jfx ui fullUIStack = new StackPane(); //this is passed into controller myScene = new Scene(fullUIStack); jfxPanel.setScene(myScene); + try { + + List dataSources = controller.getSleuthKitCase().getDataSources(); + Dialog d = new ChoiceDialog<>(null, dataSources); + d.setTitle("Image Gallery"); + d.setHeaderText("Choose a data source to view."); + d.setContentText("Data source:"); + d.initOwner(jfxPanel.getScene().getWindow()); + d.initModality(Modality.WINDOW_MODAL); + GuiUtils.setDialogIcons(d); + + Optional dataSource = d.showAndWait(); + dataSource.ifPresent(ds -> controller.getGroupManager().setDataSource(ds)); + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException); + } groupPane = new GroupPane(controller); centralStack = new StackPane(groupPane); //this is passed into controller fullUIStack.getChildren().add(borderPane); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java index 2fbc9b3758..418b62f30d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java @@ -53,38 +53,24 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule; import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent; +import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils; import org.sleuthkit.datamodel.TskCoreException; @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.imagegallery.OpenAction") @ActionReferences(value = { - @ActionReference(path = "Menu/Tools", position = 101), + @ActionReference(path = "Menu/Tools", position = 101) + , @ActionReference(path = "Toolbars/Case", position = 101)}) @ActionRegistration(displayName = "#CTL_OpenAction", lazy = false) @Messages({"CTL_OpenAction=Images/Videos", - "OpenAction.stale.confDlg.msg=The image / video database may be out of date. " + - "Do you want to update and listen for further ingest results?\n" + - "Choosing 'yes' will update the database and enable listening to future ingests.", + "OpenAction.stale.confDlg.msg=The image / video database may be out of date. " + + "Do you want to update and listen for further ingest results?\n" + + "Choosing 'yes' will update the database and enable listening to future ingests.", "OpenAction.stale.confDlg.title=Image Gallery"}) public final class OpenAction extends CallableSystemAction { private static final Logger logger = Logger.getLogger(OpenAction.class.getName()); private static final String VIEW_IMAGES_VIDEOS = Bundle.CTL_OpenAction(); - - /** - * Image to use as title bar icon in dialogs - */ - private static final Image AUTOPSY_ICON; - - static { - Image tempImg = null; - try { - tempImg = new Image(new URL("nbresloc:/org/netbeans/core/startup/frame.gif").openStream()); //NON-NLS - } catch (IOException ex) { - logger.log(Level.WARNING, "Failed to load branded icon for progress dialog.", ex); //NON-NLS - } - AUTOPSY_ICON = tempImg; - } - private static final long FILE_LIMIT = 6_000_000; private final PropertyChangeListener pcl; @@ -146,9 +132,7 @@ public final class OpenAction extends CallableSystemAction { @Override @SuppressWarnings("fallthrough") - @NbBundle.Messages({ - "OpenAction.dialogTitle=Image Gallery" - }) + @NbBundle.Messages({"OpenAction.dialogTitle=Image Gallery"}) public void performAction() { //check case @@ -172,18 +156,17 @@ public final class OpenAction extends CallableSystemAction { switch (answer) { case JOptionPane.YES_OPTION: - + // 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 { + } else { ImageGalleryController.getDefault().rebuildDB(); } - + //fall through case JOptionPane.NO_OPTION: ImageGalleryTopComponent.openTopComponent(); @@ -209,16 +192,6 @@ public final class OpenAction extends CallableSystemAction { return false; } - /** - * Set the title bar icon for the given Dialog to be the Autopsy logo icon. - * - * @param dialog The dialog to set the title bar icon for. - */ - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - private static void setDialogIcons(Dialog dialog) { - ((Stage) dialog.getDialogPane().getScene().getWindow()).getIcons().setAll(AUTOPSY_ICON); - } - @NbBundle.Messages({ "ImageGallery.showTooManyFiles.contentText=" + "There are too many files in the DB to ensure reasonable performance." @@ -229,7 +202,7 @@ public final class OpenAction extends CallableSystemAction { Bundle.ImageGallery_showTooManyFiles_contentText(), ButtonType.OK); dialog.initModality(Modality.APPLICATION_MODAL); dialog.setTitle(Bundle.OpenAction_dialogTitle()); - setDialogIcons(dialog); + GuiUtils.setDialogIcons(dialog); dialog.setHeaderText(Bundle.ImageGallery_showTooManyFiles_headerText()); dialog.showAndWait(); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java index 9945a72313..80273b637e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java @@ -18,27 +18,30 @@ */ package org.sleuthkit.autopsy.imagegallery.datamodel.grouping; -import java.util.Map; import java.util.Objects; import javafx.scene.Node; import javax.annotation.concurrent.Immutable; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; +import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.TagName; /** - * key identifying information of a {@link Grouping}. Used to look up groups in - * {@link Map}s and from the db. + * Key identifying information of a DrawableGroup. Used to look up groups in + * Maps and from the db. + * + * @param The type of the values of the attribute this key uses. */ @Immutable public class GroupKey> implements Comparable> { private final T val; - private final DrawableAttribute attr; + private final DataSource dataSource; - public GroupKey(DrawableAttribute attr, T val) { + public GroupKey(DrawableAttribute attr, T val, DataSource dataSource) { this.attr = attr; this.val = val; + this.dataSource = dataSource; } public T getValue() { @@ -49,6 +52,10 @@ public class GroupKey> implements Comparable return attr; } + public DataSource getDataSource() { + return dataSource; + } + public String getValueDisplayName() { return Objects.equals(attr, DrawableAttribute.TAGS) ? ((TagName) getValue()).getDisplayName() @@ -63,13 +70,17 @@ public class GroupKey> implements Comparable @Override public int hashCode() { int hash = 5; - hash = 29 * hash + Objects.hashCode(this.val); - hash = 29 * hash + Objects.hashCode(this.attr); + hash = 79 * hash + Objects.hashCode(this.val); + hash = 79 * hash + Objects.hashCode(this.attr); + hash = 79 * hash + Objects.hashCode(this.dataSource); return hash; } @Override public boolean equals(Object obj) { + if (this == obj) { + return true; + } if (obj == null) { return false; } @@ -77,11 +88,16 @@ public class GroupKey> implements Comparable return false; } final GroupKey other = (GroupKey) obj; - if (this.attr != other.attr) { + if (!Objects.equals(this.val, other.val)) { return false; } - - return Objects.equals(this.val, other.val); + if (!Objects.equals(this.attr, other.attr)) { + return false; + } + if (!Objects.equals(this.dataSource, other.dataSource)) { + return false; + } + return true; } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index fb381160f7..ca2e976e45 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -70,8 +70,8 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; -import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.datamodel.DhsImageCategory; +import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB; @@ -79,6 +79,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; @@ -126,16 +127,18 @@ public class GroupManager { private volatile GroupSortBy sortBy = GroupSortBy.PRIORITY; private volatile DrawableAttribute groupBy = DrawableAttribute.PATH; private volatile SortOrder sortOrder = SortOrder.ASCENDING; + private volatile DataSource dataSource = null; private final ReadOnlyObjectWrapper< Comparator> sortByProp = new ReadOnlyObjectWrapper<>(sortBy); private final ReadOnlyObjectWrapper< DrawableAttribute> groupByProp = new ReadOnlyObjectWrapper<>(groupBy); private final ReadOnlyObjectWrapper sortOrderProp = new ReadOnlyObjectWrapper<>(sortOrder); + private final ReadOnlyObjectWrapper dataSourceProp = new ReadOnlyObjectWrapper<>(dataSource); private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper(); public void setDB(DrawableDB db) { this.db = db; - regroup(groupBy, sortBy, sortOrder, Boolean.TRUE); + regroup(dataSource, groupBy, sortBy, sortOrder, true); } @SuppressWarnings("ReturnOfCollectionOrArrayField") @@ -160,13 +163,14 @@ public class GroupManager { } /** - * using the current groupBy set for this manager, find groupkeys for all + * Using the current groupBy set for this manager, find groupkeys for all * the groups the given file is a part of * * @param file * - * @returna a set of {@link GroupKey}s representing the group(s) the given - * file is a part of + * + * @return A a set of GroupKeys representing the group(s) the given file is + * a part of. */ @SuppressWarnings({"rawtypes", "unchecked"}) synchronized public Set> getGroupKeysForFile(DrawableFile file) { @@ -174,10 +178,10 @@ public class GroupManager { for (Comparable val : groupBy.getValue(file)) { if (groupBy == DrawableAttribute.TAGS) { if (CategoryManager.isNotCategoryTagName((TagName) val)) { - resultSet.add(new GroupKey(groupBy, val)); + resultSet.add(new GroupKey(groupBy, val, dataSource)); } } else { - resultSet.add(new GroupKey(groupBy, val)); + resultSet.add(new GroupKey(groupBy, val, dataSource)); } } return resultSet; @@ -317,84 +321,6 @@ public class GroupManager { return group; } - /** - * find the distinct values for the given column (DrawableAttribute) - * - * These values represent the groups of files. - * - * @param groupBy - * - * @return - */ - @SuppressWarnings({"unchecked"}) - public > List findValuesForAttribute(DrawableAttribute groupBy) { - List values = Collections.emptyList(); - try { - switch (groupBy.attrName) { - //these cases get special treatment - case CATEGORY: - values = (List) Arrays.asList(DhsImageCategory.values()); - break; - case TAGS: - values = (List) controller.getTagsManager().getTagNamesInUse().stream() - .filter(CategoryManager::isNotCategoryTagName) - .collect(Collectors.toList()); - break; - case ANALYZED: - values = (List) Arrays.asList(false, true); - break; - case HASHSET: - if (nonNull(db)) { - TreeSet names = new TreeSet<>((Collection) db.getHashSetNames()); - values = new ArrayList<>(names); - } - break; - case MIME_TYPE: - if (nonNull(db)) { - HashSet types = new HashSet<>(); - - // Use the group_concat function to get a list of files for each mime type. - // This has different syntax on Postgres vs SQLite - String groupConcatClause; - if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) { - groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; - } - else { - groupConcatClause = " group_concat(obj_id) as object_ids"; - } - String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type "; - try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS - ResultSet resultSet = executeQuery.getResultSet();) { - while (resultSet.next()) { - final String mimeType = resultSet.getString("mime_type"); //NON-NLS - String objIds = resultSet.getString("object_ids"); //NON-NLS - - Pattern.compile(",").splitAsStream(objIds) - .map(Long::valueOf) - .filter(db::isInDB) - .findAny().ifPresent(obj_id -> types.add(mimeType)); - } - } catch (SQLException | TskCoreException ex) { - Exceptions.printStackTrace(ex); - } - values = new ArrayList<>((Collection) types); - } - break; - default: - //otherwise do straight db query - if (nonNull(db)) { - values = db.findValuesForAttribute(groupBy, sortBy, sortOrder); - } - } - - return values; - } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS - return Collections.emptyList(); - } - - } - public Set getFileIDsInGroup(GroupKey groupKey) throws TskCoreException { Set fileIDsToReturn = Collections.emptySet(); switch (groupKey.getAttribute().attrName) { @@ -516,23 +442,42 @@ public class GroupManager { return sortOrderProp.getReadOnlyProperty(); } + public DataSource getDataSource() { + return dataSource; + } + + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + Platform.runLater(() -> dataSourceProp.set(dataSource)); + } + + public ReadOnlyObjectProperty getDataSourceProperty() { + return dataSourceProp.getReadOnlyProperty(); + } + /** - * regroup all files in the database using given {@link DrawableAttribute} + * Regroup all files in the database using given {@link DrawableAttribute} * see {@link ReGroupTask} for more details. * + * @param The type of the values of the groupBy attriubte. + * @param dataSource The DataSource to show. Null for all data sources. * @param groupBy * @param sortBy * @param sortOrder - * @param force true to force a full db query regroup + * @param force true to force a full db query regroup */ - public synchronized > void regroup(final DrawableAttribute groupBy, final GroupSortBy sortBy, final SortOrder sortOrder, Boolean force) { + public synchronized > void regroup(DataSource dataSource, final DrawableAttribute groupBy, final GroupSortBy sortBy, final SortOrder sortOrder, Boolean force) { if (!Case.isCaseOpen()) { return; } - //only re-query the db if the group by attribute changed or it is forced - if (groupBy != getGroupBy() || force == true) { + //only re-query the db if the data source or group by attribute changed or it is forced + if (dataSource != getDataSource() + || groupBy != getGroupBy() + || force == true) { + + setDataSource(dataSource); setGroupBy(groupBy); setSortBy(sortBy); setSortOrder(sortOrder); @@ -540,7 +485,7 @@ public class GroupManager { groupByTask.cancel(true); } - groupByTask = new ReGroupTask<>(groupBy, sortBy, sortOrder); + groupByTask = new ReGroupTask<>(dataSource, groupBy, sortBy, sortOrder); Platform.runLater(() -> regroupProgress.bind(groupByTask.progressProperty())); regroupExecutor.submit(groupByTask); } else { @@ -568,14 +513,14 @@ public class GroupManager { GroupKey newGroupKey = null; final long fileID = evt.getAddedTag().getContent().getId(); if (groupBy == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(evt.getAddedTag().getName())) { - newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(evt.getAddedTag().getName())); + newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(evt.getAddedTag().getName()), dataSource); for (GroupKey oldGroupKey : groupMap.keySet()) { if (oldGroupKey.equals(newGroupKey) == false) { removeFromGroup(oldGroupKey, fileID); } } } else if (groupBy == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(evt.getAddedTag().getName())) { - newGroupKey = new GroupKey<>(DrawableAttribute.TAGS, evt.getAddedTag().getName()); + newGroupKey = new GroupKey<>(DrawableAttribute.TAGS, evt.getAddedTag().getName(), dataSource); } if (newGroupKey != null) { DrawableGroup g = getGroupForKey(newGroupKey); @@ -602,9 +547,9 @@ public class GroupManager { final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo(); final TagName tagName = deletedTagInfo.getName(); if (groupBy == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(tagName)) { - groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(tagName)); + groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(tagName), dataSource); } else if (groupBy == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(tagName)) { - groupKey = new GroupKey<>(DrawableAttribute.TAGS, tagName); + groupKey = new GroupKey<>(DrawableAttribute.TAGS, tagName, dataSource); } if (groupKey != null) { final long fileID = deletedTagInfo.getContentID(); @@ -688,8 +633,8 @@ public class GroupManager { } else { group = new DrawableGroup(groupKey, fileIDs, groupSeen); controller.getCategoryManager().registerListener(group); - group.seenProperty().addListener((o, oldSeen, newSeen) -> - Platform.runLater(() -> markGroupSeen(group, newSeen)) + group.seenProperty().addListener((o, oldSeen, newSeen) + -> Platform.runLater(() -> markGroupSeen(group, newSeen)) ); groupMap.put(groupKey, group); } @@ -699,7 +644,7 @@ public class GroupManager { analyzedGroups.add(group); if (Objects.isNull(task)) { FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy)); - } + } } markGroupSeen(group, groupSeen); }); @@ -739,7 +684,7 @@ public class GroupManager { /** * Task to query database for files in sorted groups and build - * {@link Groupings} for them + * DrawableGroups for them. */ @SuppressWarnings({"unchecked", "rawtypes"}) @NbBundle.Messages({"# {0} - groupBy attribute Name", @@ -751,17 +696,16 @@ public class GroupManager { "ReGroupTask.progressUpdate=regrouping files by {0} : {1}"}) private class ReGroupTask> extends LoggedTask { - private ProgressHandle groupProgress; - + private final DataSource dataSource; private final DrawableAttribute groupBy; - private final GroupSortBy sortBy; - private final SortOrder sortOrder; - ReGroupTask(DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder) { - super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), true); + private ProgressHandle groupProgress; + ReGroupTask(DataSource dataSource, DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder) { + super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), true); + this.dataSource = dataSource; this.groupBy = groupBy; this.sortBy = sortBy; this.sortOrder = sortOrder; @@ -786,7 +730,7 @@ public class GroupManager { }); // Get the list of group keys - final List vals = findValuesForAttribute(groupBy); + final List vals = findValuesForAttribute(); groupProgress.start(vals.size()); @@ -800,7 +744,7 @@ public class GroupManager { updateMessage(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val)); updateProgress(p, vals.size()); groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); - popuplateIfAnalyzed(new GroupKey<>(groupBy, val), this); + popuplateIfAnalyzed(new GroupKey<>(groupBy, val, dataSource), this); } Platform.runLater(() -> FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy))); @@ -816,6 +760,83 @@ public class GroupManager { groupProgress = null; } } + + /** + * find the distinct values for the given column (DrawableAttribute) + * + * These values represent the groups of files. + * + * @param groupBy + * + * @return + */ + @SuppressWarnings({"unchecked"}) + public List findValuesForAttribute() { + List values = Collections.emptyList(); + try { + switch (groupBy.attrName) { + //these cases get special treatment + case CATEGORY: + values = (List) Arrays.asList(DhsImageCategory.values()); + break; + case TAGS: + values = (List) controller.getTagsManager().getTagNamesInUse().stream() + .filter(CategoryManager::isNotCategoryTagName) + .collect(Collectors.toList()); + break; + case ANALYZED: + values = (List) Arrays.asList(false, true); + break; + case HASHSET: + if (nonNull(db)) { + TreeSet names = new TreeSet<>((Collection) db.getHashSetNames()); + values = new ArrayList<>(names); + } + break; + case MIME_TYPE: + if (nonNull(db)) { + HashSet types = new HashSet<>(); + + // Use the group_concat function to get a list of files for each mime type. + // This has different syntax on Postgres vs SQLite + String groupConcatClause; + if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) { + groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; + } else { + groupConcatClause = " group_concat(obj_id) as object_ids"; + } + String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type "; + try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS + ResultSet resultSet = executeQuery.getResultSet();) { + while (resultSet.next()) { + final String mimeType = resultSet.getString("mime_type"); //NON-NLS + String objIds = resultSet.getString("object_ids"); //NON-NLS + + Pattern.compile(",").splitAsStream(objIds) + .map(Long::valueOf) + .filter(db::isInDB) + .findAny().ifPresent(obj_id -> types.add(mimeType)); + } + } catch (SQLException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + } + values = new ArrayList<>((Collection) types); + } + break; + default: + //otherwise do straight db query + if (nonNull(db)) { + //TODO -1017: pass datasource in here as appropriate + values = db.findValuesForAttribute(groupBy, sortBy, sortOrder); + } + } + + return values; + } catch (TskCoreException ex) { + LOGGER.log(Level.WARNING, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS + return Collections.emptyList(); + } + } } private static Comparator applySortOrder(final SortOrder sortOrder, Comparator comparator) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java index 857aae31a6..91b55e5bf1 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GuiUtils.java @@ -18,15 +18,38 @@ */ package org.sleuthkit.autopsy.imagegallery.gui; +import java.io.IOException; +import java.net.URL; +import java.util.logging.Level; import javafx.scene.control.ButtonBase; +import javafx.scene.control.Dialog; import javafx.scene.control.MenuItem; +import javafx.scene.image.Image; +import javafx.stage.Stage; import org.controlsfx.control.action.Action; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; /** * Static utility methods for working with GUI components */ public final class GuiUtils { + private final static Logger logger = Logger.getLogger(GuiUtils.class.getName()); + + /** Image to use as title bar icon in dialogs */ + private static final Image AUTOPSY_ICON; + + static { + Image tempImg = null; + try { + tempImg = new Image(new URL("nbresloc:/org/netbeans/core/startup/frame.gif").openStream()); //NON-NLS + } catch (IOException ex) { + logger.log(Level.WARNING, "Failed to load branded icon for progress dialog.", ex); //NON-NLS + } + AUTOPSY_ICON = tempImg; + } + private GuiUtils() { } @@ -51,4 +74,14 @@ public final class GuiUtils { }); return menuItem; } + + /** + * Set the title bar icon for the given Dialog to be the Autopsy logo icon. + * + * @param dialog The dialog to set the title bar icon for. + */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + public static void setDialogIcons(Dialog dialog) { + ((Stage) dialog.getDialogPane().getScene().getWindow()).getIcons().setAll(AUTOPSY_ICON); + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 3bd16d1a5b..14f7804372 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -78,14 +78,14 @@ import org.sleuthkit.datamodel.DataSource; * Controller for the ToolBar */ public class Toolbar extends ToolBar { - + private static final Logger LOGGER = Logger.getLogger(Toolbar.class.getName()); ListeningExecutorService exec = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( new ThreadFactoryBuilder().setNameFormat("Image Gallery Toolbar BG Thread").build())); - + private static final int SIZE_SLIDER_DEFAULT = 100; - + @FXML private ComboBox> dataSourceComboBox; @FXML @@ -106,26 +106,26 @@ public class Toolbar extends ToolBar { private Label categoryImageViewLabel; @FXML private Label thumbnailSizeLabel; - + private SortChooser sortChooser; - + private final ImageGalleryController controller; - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private final ObservableList> dataSources = FXCollections.observableArrayList(); - + private final InvalidationListener queryInvalidationListener = new InvalidationListener() { @Override public void invalidated(Observable invalidated) { controller.getGroupManager().regroup( - //dataSourceComboBox.getSelectionModel().getSelectedItem(), TODO-1017: incorporate the selected datasource into this call. + dataSourceComboBox.getSelectionModel().getSelectedItem().orElse(null), groupByBox.getSelectionModel().getSelectedItem(), sortChooser.getComparator(), sortChooser.getSortOrder(), false); } }; - + @FXML @NbBundle.Messages( {"Toolbar.groupByLabel=Group By:", @@ -148,12 +148,12 @@ public class Toolbar extends ToolBar { assert catGroupMenuButton != null : "fx:id=\"catGroupMenuButton\" was not injected: check your FXML file 'Toolbar.fxml'."; assert thumbnailSizeLabel != null : "fx:id=\"thumbnailSizeLabel\" was not injected: check your FXML file 'Toolbar.fxml'."; assert sizeSlider != null : "fx:id=\"sizeSlider\" was not injected: check your FXML file 'Toolbar.fxml'."; - + controller.viewState().addListener((observable, oldViewState, newViewState) -> { Platform.runLater(() -> syncGroupControlsEnabledState(newViewState)); }); syncGroupControlsEnabledState(controller.viewState().get()); - + dataSourceComboBox.setCellFactory(param -> new DataSourceCell()); dataSourceComboBox.setButtonCell(new DataSourceCell()); dataSourceComboBox.setConverter(new StringConverter>() { @@ -161,14 +161,14 @@ public class Toolbar extends ToolBar { public String toString(Optional object) { return object.map(DataSource::getName).orElse("All"); } - + @Override public Optional fromString(String string) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } }); dataSourceComboBox.setItems(dataSources); - + Case.addEventTypeSubscriber(ImmutableSet.of(DATA_SOURCE_ADDED, DATA_SOURCE_DELETED), evt -> { Platform.runLater(() -> { @@ -180,10 +180,10 @@ public class Toolbar extends ToolBar { syncDataSources(); /* TODO: 1010/7 push data source selected in dialog into UI */ - dataSourceComboBox.getSelectionModel().selectFirst(); - + dataSourceComboBox.getSelectionModel().select(Optional.ofNullable(controller.getGroupManager().getDataSource())); + initTagMenuButton(); - + CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(DhsImageCategory.FIVE, controller); catGroupMenuButton.setOnAction(cat5GroupAction); catGroupMenuButton.setText(cat5GroupAction.getText()); @@ -195,33 +195,33 @@ public class Toolbar extends ToolBar { catGroupMenuButton.getItems().setAll(categoryMenues); } }); - + groupByLabel.setText(Bundle.Toolbar_groupByLabel()); tagImageViewLabel.setText(Bundle.Toolbar_tagImageViewLabel()); categoryImageViewLabel.setText(Bundle.Toolbar_categoryImageViewLabel()); thumbnailSizeLabel.setText(Bundle.Toolbar_thumbnailSizeLabel()); - + groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs())); groupByBox.getSelectionModel().select(DrawableAttribute.PATH); - + groupByBox.disableProperty().bind(ImageGalleryController.getDefault().regroupDisabled()); groupByBox.setCellFactory(listView -> new AttributeListCell()); groupByBox.setButtonCell(new AttributeListCell()); - + sortChooser = new SortChooser<>(GroupSortBy.getValues()); sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> { final boolean orderDisabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY; sortChooser.setSortOrderDisabled(orderDisabled); - + final SortChooser.ValueType valueType = newComparator == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC; sortChooser.setValueType(valueType); queryInvalidationListener.invalidated(observable); }); - + sortChooser.setComparator(controller.getGroupManager().getSortBy()); getItems().add(2, sortChooser); sortHelpImageView.setCursor(Cursor.HAND); - + sortHelpImageView.setOnMouseClicked(clicked -> { Text text = new Text(Bundle.Toolbar_sortHelp()); text.setWrappingWidth(480); //This is a hack to fix the layout. @@ -229,12 +229,12 @@ public class Toolbar extends ToolBar { Bundle.Toolbar_sortHelpTitle(), sortHelpImageView.getImage(), text); }); - + dataSourceComboBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener); groupByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener); sortChooser.sortOrderProperty().addListener(queryInvalidationListener); } - + private void initTagMenuButton() { ListenableFuture future = exec.submit(() -> new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller)); Futures.addCallback(future, new FutureCallback() { @@ -244,7 +244,7 @@ public class Toolbar extends ToolBar { tagGroupMenuButton.setText(followUpGroupAction.getText()); tagGroupMenuButton.setGraphic(followUpGroupAction.getGraphic()); } - + @Override public void onFailure(Throwable t) { /* @@ -263,7 +263,7 @@ public class Toolbar extends ToolBar { } } }, Platform::runLater); - + tagGroupMenuButton.showingProperty().addListener(showing -> { if (tagGroupMenuButton.isShowing()) { List selTagMenues = Lists.transform(controller.getTagsManager().getNonCategoryTagNames(), @@ -272,7 +272,7 @@ public class Toolbar extends ToolBar { } }); } - + @ThreadConfined(type = ThreadConfined.ThreadType.ANY) private ListenableFuture> syncDataSources() { ListenableFuture> future = exec.submit(controller.getSleuthKitCase()::getDataSources); @@ -282,7 +282,7 @@ public class Toolbar extends ToolBar { dataSources.setAll(Collections.singleton(Optional.empty())); result.forEach(dataSource -> dataSources.add(Optional.of(dataSource))); } - + @Override public void onFailure(Throwable t) { LOGGER.log(Level.SEVERE, "Unable to get datasources for current case.", t); //NON-NLS @@ -292,10 +292,10 @@ public class Toolbar extends ToolBar { .showError(); } }, Platform::runLater); - + return future; } - + public DoubleProperty thumbnailSizeProperty() { return sizeSlider.valueProperty(); } @@ -319,11 +319,11 @@ public class Toolbar extends ToolBar { new Label(headerText)); borderPane.setPadding(new Insets(10)); borderPane.setPrefWidth(500); - + PopOver popOver = new PopOver(borderPane); popOver.setDetachable(false); popOver.setArrowLocation(PopOver.ArrowLocation.TOP_CENTER); - + popOver.show(owner); } @@ -342,14 +342,14 @@ public class Toolbar extends ToolBar { catGroupMenuButton.setDisable(noGroupSelected); }); } - + public void reset() { Platform.runLater(() -> { groupByBox.getSelectionModel().select(DrawableAttribute.PATH); sizeSlider.setValue(SIZE_SLIDER_DEFAULT); }); } - + public Toolbar(ImageGalleryController controller) { this.controller = controller; FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS @@ -359,7 +359,7 @@ public class Toolbar extends ToolBar { * Cell used to represent a DataSource in the dataSourceComboBoc */ static private class DataSourceCell extends ListCell> { - + @Override protected void updateItem(Optional item, boolean empty) { super.updateItem(item, empty); From fd6c014c9d84e3a734557bfb75897ce78d109950 Mon Sep 17 00:00:00 2001 From: Raman Date: Thu, 23 Aug 2018 13:28:32 -0400 Subject: [PATCH 26/84] 1010: Filter by datasource, for path based groups only. --- .../imagegallery/ImageGalleryController.java | 22 +++ .../imagegallery/datamodel/DrawableDB.java | 134 +++++++++++++++--- .../datamodel/grouping/GroupKey.java | 18 ++- .../datamodel/grouping/GroupManager.java | 2 +- 4 files changed, 150 insertions(+), 26 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 0781a358dd..f7e0d1f158 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -39,6 +39,7 @@ import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.ReadOnlyIntegerWrapper; +import javafx.beans.property.ReadOnlyLongWrapper; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleBooleanProperty; @@ -124,6 +125,9 @@ public final class ImageGalleryController { private final HashSetManager hashSetManager = new HashSetManager(); private final CategoryManager categoryManager = new CategoryManager(this); private final DrawableTagsManager tagsManager = new DrawableTagsManager(null); + + // RAMAN TBD: initialize this to 0 + private final ReadOnlyLongWrapper filterByDataSourceId = new ReadOnlyLongWrapper(1); private Runnable showTree; private Toolbar toolbar; @@ -213,6 +217,21 @@ public final class ImageGalleryController { return stale.get(); } + @ThreadConfined(type = ThreadConfined.ThreadType.ANY) + void setFilteringDataSourceId(long dataSourceObjId) { + Platform.runLater(() -> { + filterByDataSourceId.set(dataSourceObjId); + }); + } + + public long getFilteringDataSourceId() { + return filterByDataSourceId.get(); + } + + public boolean isFilteringByDataSource() { + return (filterByDataSourceId.get() != 0); + } + private ImageGalleryController() { // listener for the boolean property about when IG is listening / enabled @@ -549,6 +568,9 @@ public final class ImageGalleryController { } this.toolbar = toolbar; thumbnailSize.bind(toolbar.thumbnailSizeProperty()); + + // RAMAN TBD: bind filterByDataSourceId to the data source dropdown in the toolbar. + } public ReadOnlyDoubleProperty regroupProgress() { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 99b5e2bd63..1d125c0f40 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -122,12 +122,15 @@ public final class DrawableDB { private final PreparedStatement analyzedGroupStmt; private final PreparedStatement hashSetGroupStmt; + + private final PreparedStatement pathGroupFilterByDataSrcStmt; /** - * map from {@link DrawableAttribute} to the {@link PreparedStatement} thet + * map from {@link DrawableAttribute} to the {@link PreparedStatement} that * is used to select groups for that attribute */ private final Map, PreparedStatement> groupStatementMap = new HashMap<>(); + private final Map, PreparedStatement> groupStatementFilterByDataSrcMap = new HashMap<>(); private final GroupManager groupManager; @@ -213,11 +216,11 @@ public final class DrawableDB { Files.createDirectories(dbPath.getParent()); if (initializeDBSchema()) { updateFileStmt = prepareStatement( - "INSERT OR REPLACE INTO drawable_files (obj_id , path, name, created_time, modified_time, make, model, analyzed) " //NON-NLS - + "VALUES (?,?,?,?,?,?,?,?)"); //NON-NLS + "INSERT OR REPLACE INTO drawable_files (obj_id, data_source_obj_id, path, name, created_time, modified_time, make, model, analyzed) " //NON-NLS + + "VALUES (?,?,?,?,?,?,?,?,?)"); //NON-NLS insertFileStmt = prepareStatement( - "INSERT OR IGNORE INTO drawable_files (obj_id , path, name, created_time, modified_time, make, model, analyzed) " //NON-NLS - + "VALUES (?,?,?,?,?,?,?,?)"); //NON-NLS + "INSERT OR IGNORE INTO drawable_files (obj_id, data_source_obj_id, path, name, created_time, modified_time, make, model, analyzed) " //NON-NLS + + "VALUES (?,?,?,?,?,?,?,?,?)"); //NON-NLS updateDataSourceStmt = prepareStatement( "INSERT OR REPLACE INTO datasources (ds_obj_id, drawable_db_build_status) " //NON-NLS @@ -234,6 +237,9 @@ public final class DrawableDB { analyzedGroupStmt = prepareStatement("SELECT obj_id , analyzed FROM drawable_files WHERE analyzed = ?", DrawableAttribute.ANALYZED); //NON-NLS hashSetGroupStmt = prepareStatement("SELECT drawable_files.obj_id AS obj_id, analyzed FROM drawable_files , hash_sets , hash_set_hits WHERE drawable_files.obj_id = hash_set_hits.obj_id AND hash_sets.hash_set_id = hash_set_hits.hash_set_id AND hash_sets.hash_set_name = ?", DrawableAttribute.HASHSET); //NON-NLS + //add other xyzFilterByDataSrc prepared statments as we add support for filtering by DS to other groups + pathGroupFilterByDataSrcStmt = prepareFilterByDataSrcStatement("SELECT obj_id , analyzed FROM drawable_files WHERE path = ? AND data_source_obj_id = ?", DrawableAttribute.PATH); + selectHashSetNamesStmt = prepareStatement("SELECT DISTINCT hash_set_name FROM hash_sets"); //NON-NLS insertHashSetStmt = prepareStatement("INSERT OR IGNORE INTO hash_sets (hash_set_name) VALUES (?)"); //NON-NLS selectHashSetStmt = prepareStatement("SELECT hash_set_id FROM hash_sets WHERE hash_set_name = ?"); //NON-NLS @@ -305,6 +311,36 @@ public final class DrawableDB { return prepareStatement; } + /** + * calls {@link DrawableDB#prepareStatement(java.lang.String) , + * and then add the statement to the groupStatementFilterByDataSrcMap map used to lookup + * statements by the attribute/column they group on + * + * @param stmtString the string representation of the sqlite statement to + * prepare + * @param attr the {@link DrawableAttribute} this query groups by + * * + * @return the prepared statement + * + * @throws SQLExceptionif unable to prepare the statement + */ + private PreparedStatement prepareFilterByDataSrcStatement(String stmtString, DrawableAttribute attr) throws SQLException { + PreparedStatement prepareStatement = prepareStatement(stmtString); + if (attr != null) { + groupStatementFilterByDataSrcMap.put(attr, prepareStatement); + } + + return prepareStatement; + } + + private void setQueryParams(PreparedStatement statement, GroupKey groupKey) throws SQLException { + + statement.setObject(1, groupKey.getValue()); + + if (controller.isFilteringByDataSource() && (groupKey.getAttribute() == DrawableAttribute.PATH)) { + statement.setObject(2, groupKey.getDataSourceObjId()); + } + } /** * public factory method. Creates and opens a connection to a new database * * at the given path. @@ -394,6 +430,7 @@ public final class DrawableDB { try (Statement stmt = con.createStatement()) { String sql = "CREATE TABLE if not exists drawable_files " //NON-NLS + "( obj_id INTEGER PRIMARY KEY, " //NON-NLS + + " data_source_obj_id integer not null, " + " path VARCHAR(255), " //NON-NLS + " name VARCHAR(255), " //NON-NLS + " created_time integer, " //NON-NLS @@ -412,10 +449,11 @@ public final class DrawableDB { String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "SERIAL" : "INTEGER" ; String tableSchema = "( group_id " + autogenKeyType + " PRIMARY KEY, " //NON-NLS + + " data_source_obj_id integer DEFAULT 0, " + " value VARCHAR(255) not null, " //NON-NLS + " attribute VARCHAR(255) not null, " //NON-NLS + " seen integer DEFAULT 0, " //NON-NLS - + " UNIQUE(value, attribute) )"; //NON-NLS + + " UNIQUE(data_source_obj_id, value, attribute) )"; //NON-NLS tskCase.getCaseDbAccessManager().createTable(GROUPS_TABLENAME, tableSchema); } @@ -594,7 +632,15 @@ public final class DrawableDB { } try { - String groupSeenQueryStmt = String.format("seen FROM " + GROUPS_TABLENAME + " WHERE value = \'%s\' AND attribute = \'%s\'", groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString() ); + String groupSeenQueryStmt; + + if (groupKey.getDataSourceObjId() != 0) { + groupSeenQueryStmt = String.format("seen FROM " + GROUPS_TABLENAME + " WHERE value = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = %d", groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString(), groupKey.getDataSourceObjId() ); + } + else { + groupSeenQueryStmt = String.format("seen FROM " + GROUPS_TABLENAME + " WHERE value = \'%s\' AND attribute = \'%s\'", groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString() ); + } + GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor(); tskCase.getCaseDbAccessManager().select(groupSeenQueryStmt, queryResultProcessor); @@ -610,8 +656,15 @@ public final class DrawableDB { public void markGroupSeen(GroupKey gk, boolean seen) { try { - String updateSQL = String.format("set seen = %d where value = \'%s\' and attribute = \'%s\'", seen ? 1 : 0, + String updateSQL; + if (gk.getDataSourceObjId() != 0) { + updateSQL = String.format("set seen = %d where value = \'%s\' and attribute = \'%s\' and data_source_obj_id = %d", seen ? 1 : 0, + gk.getValueDisplayName(), gk.getAttribute().attrName.toString(), gk.getDataSourceObjId() ); + } + else { + updateSQL = String.format("set seen = %d where value = \'%s\' and attribute = \'%s\'", seen ? 1 : 0, gk.getValueDisplayName(), gk.getAttribute().attrName.toString() ); + } tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS @@ -708,13 +761,14 @@ public final class DrawableDB { try { // "INSERT OR IGNORE/ INTO drawable_files (path, name, created_time, modified_time, make, model, analyzed)" stmt.setLong(1, f.getId()); - stmt.setString(2, f.getDrawablePath()); - stmt.setString(3, f.getName()); - stmt.setLong(4, f.getCrtime()); - stmt.setLong(5, f.getMtime()); - stmt.setString(6, f.getMake()); - stmt.setString(7, f.getModel()); - stmt.setBoolean(8, f.isAnalyzed()); + stmt.setLong(2, f.getAbstractFile().getDataSource().getId()); + stmt.setString(3, f.getDrawablePath()); + stmt.setString(4, f.getName()); + stmt.setLong(5, f.getCrtime()); + stmt.setLong(6, f.getMtime()); + stmt.setString(7, f.getMake()); + stmt.setString(8, f.getModel()); + stmt.setBoolean(9, f.isAnalyzed()); stmt.executeUpdate(); // Update the list of file IDs in memory addImageFileToList(f.getId()); @@ -750,14 +804,19 @@ public final class DrawableDB { for (Comparable val : vals) { //use empty string for null values (mime_type), this shouldn't happen! if (null != val) { - insertGroup(val.toString(), attr, caseDbTransaction); + if (attr == DrawableAttribute.PATH) { + insertGroup(f.getAbstractFile().getDataSource().getId(), val.toString(), attr, caseDbTransaction); + } + else { + insertGroup(val.toString(), attr, caseDbTransaction); + } } } } tr.addUpdatedFile(f.getId()); - } catch (SQLException | NullPointerException ex) { + } catch (SQLException | NullPointerException | TskCoreException ex) { /* * This is one of the places where we get an error if the case is * closed during processing, which doesn't need to be reported here. @@ -1028,8 +1087,15 @@ public final class DrawableDB { default: dbReadLock(); //TODO: convert this to prepared statement - StringBuilder query = new StringBuilder("SELECT " + groupBy.attrName.toString() + ", COUNT(*) FROM drawable_files GROUP BY " + groupBy.attrName.toString()); //NON-NLS + + StringBuilder query = new StringBuilder("SELECT " + groupBy.attrName.toString() + ", COUNT(*) FROM drawable_files "); //NON-NLS + if (controller.isFilteringByDataSource()) { + query.append(" WHERE data_source_obj_id = ").append(controller.getFilteringDataSourceId()); + } + + query.append(" GROUP BY ").append(groupBy.attrName.toString()); + String orderByClause = ""; if (sortBy == GROUP_BY_VALUE) { @@ -1086,12 +1152,23 @@ public final class DrawableDB { * @param caseDbTransaction transaction to use for CaseDB insert/updates */ private void insertGroup(final String value, DrawableAttribute groupBy, CaseDbTransaction caseDbTransaction) { + insertGroup(0, value, groupBy, caseDbTransaction); + } + + /** + * Insert new group into DB + * @param ds_obj_id data source object id + * @param value Value of the group (unique to the type) + * @param groupBy Type of the grouping (CATEGORY, MAKE, etc.) + * @param caseDbTransaction transaction to use for CaseDB insert/updates + */ + private void insertGroup(long ds_obj_id, final String value, DrawableAttribute groupBy, CaseDbTransaction caseDbTransaction) { String insertSQL = ""; try { - insertSQL = String.format(" (value, attribute) VALUES (\'%s\', \'%s\')", value, groupBy.attrName.toString());; + insertSQL = String.format(" (data_source_obj_id, value, attribute) VALUES (\'%d\', \'%s\', \'%s\')", ds_obj_id, value, groupBy.attrName.toString()); if (DbType.POSTGRESQL == tskCase.getDatabaseType()) { - insertSQL += String.format(" ON CONFLICT (value, attribute) DO UPDATE SET value = \'%s\', attribute=\'%s\'", value, groupBy.attrName.toString()); + insertSQL += String.format(" ON CONFLICT (data_source_obj_id, value, attribute) DO UPDATE SET value = \'%s\', attribute=\'%s\'", value, groupBy.attrName.toString()); } tskCase.getCaseDbAccessManager().insertOrUpdate(GROUPS_TABLENAME, insertSQL, caseDbTransaction); @@ -1103,7 +1180,7 @@ public final class DrawableDB { } } } - + /** * @param id the obj_id of the file to return * @@ -1139,7 +1216,7 @@ public final class DrawableDB { dbReadLock(); try { PreparedStatement statement = getGroupStatment(groupKey.getAttribute()); - statement.setObject(1, groupKey.getValue()); + setQueryParams(statement, groupKey); try (ResultSet valsResults = statement.executeQuery()) { while (valsResults.next()) { @@ -1162,14 +1239,25 @@ public final class DrawableDB { } private PreparedStatement getGroupStatment(DrawableAttribute groupBy) { + + // + if ((groupBy == DrawableAttribute.PATH) && (controller.isFilteringByDataSource()) ) { + return this.groupStatementFilterByDataSrcMap.get(groupBy); + } return groupStatementMap.get(groupBy); } public int countAllFiles() { int result = -1; + + StringBuilder query = new StringBuilder("SELECT COUNT(*) AS COUNT FROM drawable_files"); //NON-NLS + if (controller.isFilteringByDataSource()) { + query.append(" WHERE data_source_obj_id = ").append(controller.getFilteringDataSourceId()); + } + dbReadLock(); - try (ResultSet rs = con.createStatement().executeQuery("SELECT COUNT(*) AS COUNT FROM drawable_files")) { //NON-NLS + try (ResultSet rs = con.createStatement().executeQuery(query.toString())) { //NON-NLS while (rs.next()) { result = rs.getInt("COUNT"); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java index 9945a72313..755e005417 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2013-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,10 +35,17 @@ public class GroupKey> implements Comparable private final T val; private final DrawableAttribute attr; + + private final long dataSourceObjectId; public GroupKey(DrawableAttribute attr, T val) { + this(attr, val, 0); + } + + public GroupKey(DrawableAttribute attr, T val, long dataSourceObjId) { this.attr = attr; this.val = val; + this.dataSourceObjectId = dataSourceObjId; } public T getValue() { @@ -49,6 +56,10 @@ public class GroupKey> implements Comparable return attr; } + public long getDataSourceObjId() { + return dataSourceObjectId; + } + public String getValueDisplayName() { return Objects.equals(attr, DrawableAttribute.TAGS) ? ((TagName) getValue()).getDisplayName() @@ -65,6 +76,7 @@ public class GroupKey> implements Comparable int hash = 5; hash = 29 * hash + Objects.hashCode(this.val); hash = 29 * hash + Objects.hashCode(this.attr); + hash = 29 * hash + Objects.hashCode(this.dataSourceObjectId); return hash; } @@ -80,7 +92,9 @@ public class GroupKey> implements Comparable if (this.attr != other.attr) { return false; } - + if (this.dataSourceObjectId != other.dataSourceObjectId) { + return false; + } return Objects.equals(this.val, other.val); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index fb381160f7..892e59efb1 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -800,7 +800,7 @@ public class GroupManager { updateMessage(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val)); updateProgress(p, vals.size()); groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); - popuplateIfAnalyzed(new GroupKey<>(groupBy, val), this); + popuplateIfAnalyzed(new GroupKey<>(groupBy, val, controller.getFilteringDataSourceId()), this); } Platform.runLater(() -> FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy))); From 3055576f0609523903a796f1749c150b4a3a531b Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 24 Aug 2018 10:52:27 +0200 Subject: [PATCH 27/84] move dialog display. --- .../ImageGalleryTopComponent.java | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 4e4dd2fbd4..bff37548ae 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -131,6 +131,24 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl if (tc != null) { topComponentInitialized = true; if (tc.isOpened() == false) { + try { + List dataSources = ((ImageGalleryTopComponent) tc).controller.getSleuthKitCase().getDataSources(); + + Platform.runLater(() -> { + Dialog d = new ChoiceDialog<>(null, dataSources); + d.setTitle("Image Gallery"); + d.setHeaderText("Choose a data source to view."); + d.setContentText("Data source:"); + d.initOwner(((ImageGalleryTopComponent) tc).jfxPanel.getScene().getWindow()); + d.initModality(Modality.WINDOW_MODAL); + GuiUtils.setDialogIcons(d); + + Optional dataSource = d.showAndWait(); + dataSource.ifPresent(ds -> ((ImageGalleryTopComponent) tc).controller.getGroupManager().setDataSource(ds)); + }); + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException); + }; tc.open(); } tc.toFront(); @@ -161,22 +179,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl fullUIStack = new StackPane(); //this is passed into controller myScene = new Scene(fullUIStack); jfxPanel.setScene(myScene); - try { - List dataSources = controller.getSleuthKitCase().getDataSources(); - Dialog d = new ChoiceDialog<>(null, dataSources); - d.setTitle("Image Gallery"); - d.setHeaderText("Choose a data source to view."); - d.setContentText("Data source:"); - d.initOwner(jfxPanel.getScene().getWindow()); - d.initModality(Modality.WINDOW_MODAL); - GuiUtils.setDialogIcons(d); - - Optional dataSource = d.showAndWait(); - dataSource.ifPresent(ds -> controller.getGroupManager().setDataSource(ds)); - } catch (TskCoreException tskCoreException) { - logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException); - } groupPane = new GroupPane(controller); centralStack = new StackPane(groupPane); //this is passed into controller fullUIStack.getChildren().add(borderPane); From 0fc0b1347c30fb5aaf3aff09ec6b465d1070a81f Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 24 Aug 2018 12:24:11 +0200 Subject: [PATCH 28/84] improve initial dialog --- .../imagegallery/ImageGalleryTopComponent.java | 12 +++++++----- .../sleuthkit/autopsy/imagegallery/gui/Toolbar.java | 7 +++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index bff37548ae..56bae76001 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -18,7 +18,9 @@ */ package org.sleuthkit.autopsy.imagegallery; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.logging.Level; import java.util.stream.Collectors; @@ -26,7 +28,6 @@ import javafx.application.Platform; import javafx.embed.swing.JFXPanel; import javafx.scene.Scene; import javafx.scene.control.ChoiceDialog; -import javafx.scene.control.Dialog; import javafx.scene.control.SplitPane; import javafx.scene.control.TabPane; import javafx.scene.layout.BorderPane; @@ -133,9 +134,10 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl if (tc.isOpened() == false) { try { List dataSources = ((ImageGalleryTopComponent) tc).controller.getSleuthKitCase().getDataSources(); - + Map dataSourceNames = new HashMap<>(); + dataSources.forEach(dataSource -> dataSourceNames.put(dataSource.getName(), dataSource)); Platform.runLater(() -> { - Dialog d = new ChoiceDialog<>(null, dataSources); + ChoiceDialog d = new ChoiceDialog<>(null, dataSourceNames.keySet()); d.setTitle("Image Gallery"); d.setHeaderText("Choose a data source to view."); d.setContentText("Data source:"); @@ -143,8 +145,8 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl d.initModality(Modality.WINDOW_MODAL); GuiUtils.setDialogIcons(d); - Optional dataSource = d.showAndWait(); - dataSource.ifPresent(ds -> ((ImageGalleryTopComponent) tc).controller.getGroupManager().setDataSource(ds)); + Optional dataSourceName = d.showAndWait(); + dataSourceName.map(dataSourceNames::get).ifPresent(ds -> ((ImageGalleryTopComponent) tc).controller.getGroupManager().setDataSource(ds)); }); } catch (TskCoreException tskCoreException) { logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index f59bdaa41c..82d879d137 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -55,6 +55,7 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.scene.text.Text; import javafx.util.StringConverter; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; import org.controlsfx.control.Notifications; import org.controlsfx.control.PopOver; import org.openide.util.NbBundle; @@ -117,8 +118,10 @@ public class Toolbar extends ToolBar { private final InvalidationListener queryInvalidationListener = new InvalidationListener() { @Override public void invalidated(Observable invalidated) { - controller.getGroupManager().regroup( - dataSourceComboBox.getSelectionModel().getSelectedItem().orElse(null), + Optional selectedItem = dataSourceComboBox.getSelectionModel().getSelectedItem(); + selectedItem = defaultIfNull(selectedItem, Optional.empty()); + + controller.getGroupManager().regroup(selectedItem.orElse(null), groupByBox.getSelectionModel().getSelectedItem(), sortChooser.getComparator(), sortChooser.getSortOrder(), From 659a86bb63a0fd9f59b5036559da3964c9201a95 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 24 Aug 2018 12:34:13 +0200 Subject: [PATCH 29/84] don't show datasource chooser if there is only ony datasource --- .../ImageGalleryTopComponent.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 56bae76001..e5047021a6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -134,20 +134,22 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl if (tc.isOpened() == false) { try { List dataSources = ((ImageGalleryTopComponent) tc).controller.getSleuthKitCase().getDataSources(); - Map dataSourceNames = new HashMap<>(); - dataSources.forEach(dataSource -> dataSourceNames.put(dataSource.getName(), dataSource)); - Platform.runLater(() -> { - ChoiceDialog d = new ChoiceDialog<>(null, dataSourceNames.keySet()); - d.setTitle("Image Gallery"); - d.setHeaderText("Choose a data source to view."); - d.setContentText("Data source:"); - d.initOwner(((ImageGalleryTopComponent) tc).jfxPanel.getScene().getWindow()); - d.initModality(Modality.WINDOW_MODAL); - GuiUtils.setDialogIcons(d); + if (dataSources.size() > 1) { + Map dataSourceNames = new HashMap<>(); + dataSourceNames.put("All", null); + dataSources.forEach(dataSource -> dataSourceNames.put(dataSource.getName(), dataSource)); + Platform.runLater(() -> { + ChoiceDialog d = new ChoiceDialog<>(null, dataSourceNames.keySet()); + d.setTitle("Image Gallery"); + d.setHeaderText("Choose a data source to view."); + d.setContentText("Data source:"); + d.initModality(Modality.WINDOW_MODAL); + GuiUtils.setDialogIcons(d); - Optional dataSourceName = d.showAndWait(); - dataSourceName.map(dataSourceNames::get).ifPresent(ds -> ((ImageGalleryTopComponent) tc).controller.getGroupManager().setDataSource(ds)); - }); + Optional dataSourceName = d.showAndWait(); + dataSourceName.map(dataSourceNames::get).ifPresent(ds -> ((ImageGalleryTopComponent) tc).controller.getGroupManager().setDataSource(ds)); + }); + } } catch (TskCoreException tskCoreException) { logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException); }; @@ -176,7 +178,6 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl initComponents(); Platform.runLater(() -> { - //initialize jfx ui fullUIStack = new StackPane(); //this is passed into controller myScene = new Scene(fullUIStack); From 28e27fa564ca23c99998b9e4e811e0409e3c34c6 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 24 Aug 2018 12:42:16 +0200 Subject: [PATCH 30/84] keep toolbar datasource in sync with groupmanager --- .../src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 82d879d137..fea27d64d7 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -36,6 +36,8 @@ import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.DoubleProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -182,7 +184,9 @@ public class Toolbar extends ToolBar { }); syncDataSources(); - /* TODO: 1010/7 push data source selected in dialog into UI */ + controller.getGroupManager().getDataSourceProperty().addListener((observable, oldDataSource, newDataSource) -> { + dataSourceComboBox.getSelectionModel().select(Optional.ofNullable(newDataSource)); + }); dataSourceComboBox.getSelectionModel().select(Optional.ofNullable(controller.getGroupManager().getDataSource())); initTagMenuButton(); From aeff9645793e401e2aa900a3c7acd1252a0b0c30 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 24 Aug 2018 12:42:27 +0200 Subject: [PATCH 31/84] cleanup SQL --- .../sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 0e845e3a17..ec55ef18db 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -419,7 +419,7 @@ public final class DrawableDB { } try (Statement stmt = con.createStatement()) { - String sql = "CREATE TABLE if not exists datasources " //NON-NLS + String sql = "CREATE TABLE IF NOT EXISTS datasources " //NON-NLS + "( id INTEGER PRIMARY KEY, " //NON-NLS + " ds_obj_id integer UNIQUE NOT NULL, " + " drawable_db_build_status VARCHAR(128) )"; //NON-NLS @@ -432,7 +432,7 @@ public final class DrawableDB { try (Statement stmt = con.createStatement()) { String sql = "CREATE TABLE if not exists drawable_files " //NON-NLS + "( obj_id INTEGER PRIMARY KEY, " //NON-NLS - + " data_source_obj_id integer not null, " + + " data_source_obj_id INTEGER NOT NULL REFERENCES datasources.ds_obj_id, " + " path VARCHAR(255), " //NON-NLS + " name VARCHAR(255), " //NON-NLS + " created_time integer, " //NON-NLS From 3fd4419a4b090067a5035676aa4b3d68ff2394bc Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 24 Aug 2018 08:39:54 -0400 Subject: [PATCH 32/84] Updated comment. --- .../sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 1d125c0f40..2271a65a61 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -759,7 +759,7 @@ public final class DrawableDB { dbWriteLock(); try { - // "INSERT OR IGNORE/ INTO drawable_files (path, name, created_time, modified_time, make, model, analyzed)" + // "INSERT OR IGNORE/ INTO drawable_files (obj_id, data_source_obj_id, path, name, created_time, modified_time, make, model, analyzed)" stmt.setLong(1, f.getId()); stmt.setLong(2, f.getAbstractFile().getDataSource().getId()); stmt.setString(3, f.getDrawablePath()); From 6b2a120c6ab366eb56a826d0e605ad411eef7eb6 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 24 Aug 2018 15:14:55 +0200 Subject: [PATCH 33/84] minor refactoring --- .../ImageGalleryTopComponent.java | 58 ++++++++++++------- .../imagegallery/datamodel/DrawableDB.java | 2 +- .../datamodel/grouping/GroupManager.java | 33 ++++++----- 3 files changed, 58 insertions(+), 35 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index e5047021a6..46b8c0c2fe 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.imagegallery; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -35,6 +36,7 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.stage.Modality; +import javax.swing.SwingUtilities; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; import org.openide.util.Lookup; @@ -131,32 +133,46 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); if (tc != null) { topComponentInitialized = true; - if (tc.isOpened() == false) { + if (tc.isOpened()) { + tc.toFront(); + tc.requestActive(); + } else { + List dataSources = Collections.emptyList(); try { - List dataSources = ((ImageGalleryTopComponent) tc).controller.getSleuthKitCase().getDataSources(); - if (dataSources.size() > 1) { - Map dataSourceNames = new HashMap<>(); - dataSourceNames.put("All", null); - dataSources.forEach(dataSource -> dataSourceNames.put(dataSource.getName(), dataSource)); - Platform.runLater(() -> { - ChoiceDialog d = new ChoiceDialog<>(null, dataSourceNames.keySet()); - d.setTitle("Image Gallery"); - d.setHeaderText("Choose a data source to view."); - d.setContentText("Data source:"); - d.initModality(Modality.WINDOW_MODAL); - GuiUtils.setDialogIcons(d); - - Optional dataSourceName = d.showAndWait(); - dataSourceName.map(dataSourceNames::get).ifPresent(ds -> ((ImageGalleryTopComponent) tc).controller.getGroupManager().setDataSource(ds)); - }); - } + dataSources = ((ImageGalleryTopComponent) tc).controller.getSleuthKitCase().getDataSources(); } catch (TskCoreException tskCoreException) { logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException); }; - tc.open(); + if (dataSources.size() > 1) { + Map dataSourceNames = new HashMap<>(); + dataSourceNames.put("All", null); + dataSources.forEach(dataSource -> dataSourceNames.put(dataSource.getName(), dataSource)); + + Platform.runLater(() -> { + ChoiceDialog d = new ChoiceDialog<>(null, dataSourceNames.keySet()); + d.setTitle("Image Gallery"); + d.setHeaderText("Choose a data source to view."); + d.setContentText("Data source:"); + d.initModality(Modality.APPLICATION_MODAL); + GuiUtils.setDialogIcons(d); + + Optional dataSourceName = d.showAndWait(); + dataSourceName.map(dataSourceNames::get).ifPresent(ds -> ((ImageGalleryTopComponent) tc).controller.getGroupManager().setDataSource(ds)); + + SwingUtilities.invokeLater(() -> { + tc.open(); + tc.toFront(); + tc.requestActive(); + }); + }); + } else { + SwingUtilities.invokeLater(() -> { + tc.open(); + tc.toFront(); + tc.requestActive(); + }); + } } - tc.toFront(); - tc.requestActive(); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index ec55ef18db..cd26fa2753 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -432,7 +432,7 @@ public final class DrawableDB { try (Statement stmt = con.createStatement()) { String sql = "CREATE TABLE if not exists drawable_files " //NON-NLS + "( obj_id INTEGER PRIMARY KEY, " //NON-NLS - + " data_source_obj_id INTEGER NOT NULL REFERENCES datasources.ds_obj_id, " + + " data_source_obj_id INTEGER NOT NULL, " + " path VARCHAR(255), " //NON-NLS + " name VARCHAR(255), " //NON-NLS + " created_time integer, " //NON-NLS diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index d675072347..cae74f4321 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2013-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,8 +43,6 @@ import java.util.stream.Stream; import javafx.application.Platform; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleWrapper; -import javafx.beans.property.ReadOnlyLongProperty; -import javafx.beans.property.ReadOnlyLongWrapper; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; @@ -96,8 +94,6 @@ public class GroupManager { private static final Logger LOGGER = Logger.getLogger(GroupManager.class.getName()); - private DrawableDB db; - private final ImageGalleryController controller; /** @@ -139,10 +135,13 @@ public class GroupManager { private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper(); public void setDB(DrawableDB db) { - this.db = db; regroup(dataSource, groupBy, sortBy, sortOrder, true); } + DrawableDB getDB() { + return controller.getDatabase(); + } + @SuppressWarnings("ReturnOfCollectionOrArrayField") public ObservableList getAnalyzedGroups() { return unmodifiableAnalyzedGroups; @@ -157,7 +156,6 @@ public class GroupManager { /** * construct a group manager hooked up to the given db and controller * - * @param db * @param controller */ public GroupManager(ImageGalleryController controller) { @@ -190,14 +188,17 @@ public class GroupManager { } /** - * using the current groupBy set for this manager, find groupkeys for all + * Using the current groupBy set for this manager, find groupkeys for all * the groups the given file is a part of * + * @param fileID The Id of the file to get group keys for. + * * @return a a set of {@link GroupKey}s representing the group(s) the given * file is a part of */ synchronized public Set> getGroupKeysForFileID(Long fileID) { try { + DrawableDB db = getDB(); if (nonNull(db)) { DrawableFile file = db.getFileFromID(fileID); return getGroupKeysForFile(file); @@ -242,7 +243,6 @@ public class GroupManager { groupMap.values().forEach(controller.getCategoryManager()::unregisterListener); groupMap.clear(); } - db = null; } public boolean isRegrouping() { @@ -268,10 +268,12 @@ public class GroupManager { * 'mark' the given group as seen. This removes it from the queue of groups * to review, and is persisted in the drawable db. * - * @param group the {@link DrawableGroup} to mark as seen + * @param group The DrawableGroup to mark as seen. + * @param seen The seen stastus to set. */ @ThreadConfined(type = ThreadType.JFX) public void markGroupSeen(DrawableGroup group, boolean seen) { + DrawableDB db = getDB(); if (nonNull(db)) { db.markGroupSeen(group.getGroupKey(), seen); group.setSeen(seen); @@ -291,6 +293,7 @@ public class GroupManager { * * @param groupKey the value of groupKey * @param fileID the value of file + * */ public synchronized DrawableGroup removeFromGroup(GroupKey groupKey, final Long fileID) { //get grouping this file would be in @@ -339,6 +342,7 @@ public class GroupManager { // case HASHSET: //comment out this case to use db functionality for hashsets // return getFileIDsWithHashSetName((String) groupKey.getValue()); default: + DrawableDB db = getDB(); //straight db query if (nonNull(db)) { fileIDsToReturn = db.getFileIDsInGroup(groupKey); @@ -351,6 +355,7 @@ public class GroupManager { // Unless the list of file IDs is necessary, use countFilesWithCategory() to get the counts. public Set getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException { Set fileIDsToReturn = Collections.emptySet(); + DrawableDB db = getDB(); if (nonNull(db)) { try { final DrawableTagsManager tagsManager = controller.getTagsManager(); @@ -393,6 +398,7 @@ public class GroupManager { try { Set files = new HashSet<>(); List contentTags = controller.getTagsManager().getContentTagsByTagName(tagName); + DrawableDB db = getDB(); for (ContentTag ct : contentTags) { if (ct.getContent() instanceof AbstractFile && nonNull(db) && db.isInDB(ct.getContent().getId())) { files.add(ct.getContent().getId()); @@ -613,8 +619,8 @@ public class GroupManager { * task was still running) */ - } else // no task or un-cancelled task - { + } else { // no task or un-cancelled task + DrawableDB db = getDB(); if (nonNull(db) && ((groupKey.getAttribute() != DrawableAttribute.PATH) || db.isGroupAnalyzed(groupKey))) { /* * for attributes other than path we can't be sure a group is @@ -667,7 +673,7 @@ public class GroupManager { String query = (null == mimeType) ? "SELECT obj_id FROM tsk_files WHERE mime_type IS NULL" //NON-NLS : "SELECT obj_id FROM tsk_files WHERE mime_type = '" + mimeType + "'"; //NON-NLS - + DrawableDB db = getDB(); try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); ResultSet resultSet = executeQuery.getResultSet();) { while (resultSet.next()) { @@ -775,6 +781,7 @@ public class GroupManager { @SuppressWarnings({"unchecked"}) public List findValuesForAttribute() { List values = Collections.emptyList(); + DrawableDB db = getDB(); try { switch (groupBy.attrName) { //these cases get special treatment From 436ad753d58ea9b51dfa3fb2190d5ca3381d97a4 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 24 Aug 2018 15:29:40 +0200 Subject: [PATCH 34/84] fix datasource filtering weh All is seleceted --- .../autopsy/imagegallery/datamodel/DrawableDB.java | 12 ++++++------ .../datamodel/grouping/GroupManager.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index cd26fa2753..c4450c6f5a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -337,7 +337,7 @@ public final class DrawableDB { statement.setObject(1, groupKey.getValue()); - if (groupKey.getDataSource() != null + if (groupKey.getDataSource().isPresent() && (groupKey.getAttribute() == DrawableAttribute.PATH)) { statement.setObject(2, groupKey.getDataSourceObjId()); } @@ -636,7 +636,7 @@ public final class DrawableDB { try { String groupSeenQueryStmt; - if (groupKey.getDataSourceObjId() != 0) { + if (groupKey.getDataSource().isPresent()) { groupSeenQueryStmt = String.format("seen FROM " + GROUPS_TABLENAME + " WHERE value = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = %d", groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString(), groupKey.getDataSourceObjId()); } else { groupSeenQueryStmt = String.format("seen FROM " + GROUPS_TABLENAME + " WHERE value = \'%s\' AND attribute = \'%s\'", groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString()); @@ -657,11 +657,11 @@ public final class DrawableDB { public void markGroupSeen(GroupKey gk, boolean seen) { try { String updateSQL; - if (gk.getDataSourceObjId() != 0) { - updateSQL = String.format("set seen = %d where value = \'%s\' and attribute = \'%s\' and data_source_obj_id = %d", seen ? 1 : 0, + if (gk.getDataSource().isPresent()) { + updateSQL = String.format("SET seen = %d WHERE VALUE = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = %d", seen ? 1 : 0, gk.getValueDisplayName(), gk.getAttribute().attrName.toString(), gk.getDataSourceObjId()); } else { - updateSQL = String.format("set seen = %d where value = \'%s\' and attribute = \'%s\'", seen ? 1 : 0, + updateSQL = String.format("SET seen = %d WHERE VALUE = \'%s\' AND attribute = \'%s\'", seen ? 1 : 0, gk.getValueDisplayName(), gk.getAttribute().attrName.toString()); } tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL); @@ -1253,7 +1253,7 @@ public final class DrawableDB { if (null != dataSource) { return countFilesWhere(" data_source_obj_id = "); } else { - return countFilesWhere(" true "); + return countFilesWhere(" 1 "); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index cae74f4321..37617acbc8 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -625,7 +625,7 @@ public class GroupManager { /* * for attributes other than path we can't be sure a group is * fully analyzed because we don't know all the files that will - * be a part of that group,. just show them no matter what. + * be a part of that group. just show them no matter what. */ try { From c1f996800f6ebca608f1fc8d06704a8df2846642 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 27 Aug 2018 11:32:06 +0200 Subject: [PATCH 35/84] move button and refactor the Toolbar controller slightly --- .../autopsy/imagegallery/gui/Toolbar.fxml | 28 ++--- .../autopsy/imagegallery/gui/Toolbar.java | 119 ++++++++++-------- 2 files changed, 79 insertions(+), 68 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml index 45028da397..77f73b2cb9 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.fxml @@ -13,7 +13,20 @@ - + + + + + + + + + + + + - - - - - - - - - - - diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index fea27d64d7..2599f2cc7b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -56,6 +56,7 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.scene.text.Text; +import javafx.util.Duration; import javafx.util.StringConverter; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; import org.controlsfx.control.Notifications; @@ -154,11 +155,71 @@ public class Toolbar extends ToolBar { assert thumbnailSizeLabel != null : "fx:id=\"thumbnailSizeLabel\" was not injected: check your FXML file 'Toolbar.fxml'."; assert sizeSlider != null : "fx:id=\"sizeSlider\" was not injected: check your FXML file 'Toolbar.fxml'."; - controller.viewState().addListener((observable, oldViewState, newViewState) -> { - Platform.runLater(() -> syncGroupControlsEnabledState(newViewState)); - }); + //set internationalized label text + groupByLabel.setText(Bundle.Toolbar_groupByLabel()); + tagImageViewLabel.setText(Bundle.Toolbar_tagImageViewLabel()); + categoryImageViewLabel.setText(Bundle.Toolbar_categoryImageViewLabel()); + thumbnailSizeLabel.setText(Bundle.Toolbar_thumbnailSizeLabel()); + + controller.viewState().addListener((observable, oldViewState, newViewState) + -> Platform.runLater(() -> syncGroupControlsEnabledState(newViewState)) + ); syncGroupControlsEnabledState(controller.viewState().get()); + initDataSourceComboBox(); + groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs())); + groupByBox.getSelectionModel().select(DrawableAttribute.PATH); + groupByBox.disableProperty().bind(ImageGalleryController.getDefault().regroupDisabled()); + groupByBox.setCellFactory(listView -> new AttributeListCell()); + groupByBox.setButtonCell(new AttributeListCell()); + groupByBox.getSelectionModel().selectedItemProperty().addListener(observable -> { + if (groupByBox.getSelectionModel().getSelectedItem() != DrawableAttribute.PATH) { + Notifications.create().owner(getScene().getWindow()) + .text("Grouping by attributes other than path does not support the data source filter.\nFiles and groups from all data sources will be shown.") + .hideAfter(Duration.seconds(30)) + .showInformation(); + } + queryInvalidationListener.invalidated(observable); + }); + + sortChooser = new SortChooser<>(GroupSortBy.getValues()); + sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> { + final boolean orderDisabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY; + sortChooser.setSortOrderDisabled(orderDisabled); + + final SortChooser.ValueType valueType = newComparator == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC; + sortChooser.setValueType(valueType); + queryInvalidationListener.invalidated(observable); + }); + sortChooser.setComparator(controller.getGroupManager().getSortBy()); + sortChooser.sortOrderProperty().addListener(queryInvalidationListener); + getItems().add(2, sortChooser); + + sortHelpImageView.setCursor(Cursor.HAND); + sortHelpImageView.setOnMouseClicked(clicked -> { + Text text = new Text(Bundle.Toolbar_sortHelp()); + text.setWrappingWidth(480); //This is a hack to fix the layout. + showPopoverHelp(sortHelpImageView, + Bundle.Toolbar_sortHelpTitle(), + sortHelpImageView.getImage(), text); + }); + initTagMenuButton(); + + CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(DhsImageCategory.FIVE, controller); + catGroupMenuButton.setOnAction(cat5GroupAction); + catGroupMenuButton.setText(cat5GroupAction.getText()); + catGroupMenuButton.setGraphic(cat5GroupAction.getGraphic()); + catGroupMenuButton.showingProperty().addListener(showing -> { + if (catGroupMenuButton.isShowing()) { + List categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()), + cat -> GuiUtils.createAutoAssigningMenuItem(catGroupMenuButton, new CategorizeGroupAction(cat, controller))); + catGroupMenuButton.getItems().setAll(categoryMenues); + } + }); + + } + + private void initDataSourceComboBox() { dataSourceComboBox.setCellFactory(param -> new DataSourceCell()); dataSourceComboBox.setButtonCell(new DataSourceCell()); dataSourceComboBox.setConverter(new StringConverter>() { @@ -188,58 +249,8 @@ public class Toolbar extends ToolBar { dataSourceComboBox.getSelectionModel().select(Optional.ofNullable(newDataSource)); }); dataSourceComboBox.getSelectionModel().select(Optional.ofNullable(controller.getGroupManager().getDataSource())); - - initTagMenuButton(); - - CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(DhsImageCategory.FIVE, controller); - catGroupMenuButton.setOnAction(cat5GroupAction); - catGroupMenuButton.setText(cat5GroupAction.getText()); - catGroupMenuButton.setGraphic(cat5GroupAction.getGraphic()); - catGroupMenuButton.showingProperty().addListener(showing -> { - if (catGroupMenuButton.isShowing()) { - List categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()), - cat -> GuiUtils.createAutoAssigningMenuItem(catGroupMenuButton, new CategorizeGroupAction(cat, controller))); - catGroupMenuButton.getItems().setAll(categoryMenues); - } - }); - - groupByLabel.setText(Bundle.Toolbar_groupByLabel()); - tagImageViewLabel.setText(Bundle.Toolbar_tagImageViewLabel()); - categoryImageViewLabel.setText(Bundle.Toolbar_categoryImageViewLabel()); - thumbnailSizeLabel.setText(Bundle.Toolbar_thumbnailSizeLabel()); - - groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs())); - groupByBox.getSelectionModel().select(DrawableAttribute.PATH); - - groupByBox.disableProperty().bind(ImageGalleryController.getDefault().regroupDisabled()); - groupByBox.setCellFactory(listView -> new AttributeListCell()); - groupByBox.setButtonCell(new AttributeListCell()); - - sortChooser = new SortChooser<>(GroupSortBy.getValues()); - sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> { - final boolean orderDisabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY; - sortChooser.setSortOrderDisabled(orderDisabled); - - final SortChooser.ValueType valueType = newComparator == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC; - sortChooser.setValueType(valueType); - queryInvalidationListener.invalidated(observable); - }); - - sortChooser.setComparator(controller.getGroupManager().getSortBy()); - getItems().add(2, sortChooser); - sortHelpImageView.setCursor(Cursor.HAND); - - sortHelpImageView.setOnMouseClicked(clicked -> { - Text text = new Text(Bundle.Toolbar_sortHelp()); - text.setWrappingWidth(480); //This is a hack to fix the layout. - showPopoverHelp(sortHelpImageView, - Bundle.Toolbar_sortHelpTitle(), - sortHelpImageView.getImage(), text); - }); - + dataSourceComboBox.disableProperty().bind(groupByBox.getSelectionModel().selectedItemProperty().isNotEqualTo(DrawableAttribute.PATH)); dataSourceComboBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener); - groupByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener); - sortChooser.sortOrderProperty().addListener(queryInvalidationListener); } private void initTagMenuButton() { From 437f2446e853795bf951dab86d162ec482362dee Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 27 Aug 2018 14:39:05 +0200 Subject: [PATCH 36/84] internationalize non-path grouping text --- .../ImageGalleryTopComponent.java | 22 ++++++--- .../autopsy/imagegallery/gui/Toolbar.java | 47 +++++++++---------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 46b8c0c2fe..774f0854de 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -46,6 +46,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.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils; import org.sleuthkit.autopsy.imagegallery.gui.StatusBar; import org.sleuthkit.autopsy.imagegallery.gui.SummaryTablePane; @@ -121,6 +122,11 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl return WindowManager.getDefault().findTopComponent(PREFERRED_ID); } + @Messages({ + "ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.headerText=Choose a data source to view.", + "ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.contentText=Data source:", + "ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.all=All", + "ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.titleText=Image Gallery",}) public static void openTopComponent() { //TODO:eventually move to this model, throwing away everything and rebuilding controller groupmanager etc for each case. // synchronized (OpenTimelineAction.class) { @@ -138,26 +144,28 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl tc.requestActive(); } else { List dataSources = Collections.emptyList(); + ImageGalleryController controller = ((ImageGalleryTopComponent) tc).controller; try { - dataSources = ((ImageGalleryTopComponent) tc).controller.getSleuthKitCase().getDataSources(); + dataSources = controller.getSleuthKitCase().getDataSources(); } catch (TskCoreException tskCoreException) { logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException); - }; - if (dataSources.size() > 1) { + } + if (dataSources.size() > 1 + && controller.getGroupManager().getGroupBy() == DrawableAttribute.PATH) { Map dataSourceNames = new HashMap<>(); dataSourceNames.put("All", null); dataSources.forEach(dataSource -> dataSourceNames.put(dataSource.getName(), dataSource)); Platform.runLater(() -> { ChoiceDialog d = new ChoiceDialog<>(null, dataSourceNames.keySet()); - d.setTitle("Image Gallery"); - d.setHeaderText("Choose a data source to view."); - d.setContentText("Data source:"); + d.setTitle(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_titleText()); + d.setHeaderText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_headerText()); + d.setContentText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_contentText()); d.initModality(Modality.APPLICATION_MODAL); GuiUtils.setDialogIcons(d); Optional dataSourceName = d.showAndWait(); - dataSourceName.map(dataSourceNames::get).ifPresent(ds -> ((ImageGalleryTopComponent) tc).controller.getGroupManager().setDataSource(ds)); + dataSourceName.map(dataSourceNames::get).ifPresent(ds -> controller.getGroupManager().setDataSource(ds)); SwingUtilities.invokeLater(() -> { tc.open(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 2599f2cc7b..0ab052f45f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -36,8 +36,6 @@ import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.DoubleProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -84,10 +82,6 @@ import org.sleuthkit.datamodel.DataSource; public class Toolbar extends ToolBar { private static final Logger LOGGER = Logger.getLogger(Toolbar.class.getName()); - ListeningExecutorService exec - = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( - new ThreadFactoryBuilder().setNameFormat("Image Gallery Toolbar BG Thread").build())); - private static final int SIZE_SLIDER_DEFAULT = 100; @FXML @@ -110,9 +104,12 @@ public class Toolbar extends ToolBar { private Label categoryImageViewLabel; @FXML private Label thumbnailSizeLabel; - private SortChooser sortChooser; + private final ListeningExecutorService exec + = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder().setNameFormat("Image Gallery Toolbar BG Thread").build())); + private final ImageGalleryController controller; @ThreadConfined(type = ThreadConfined.ThreadType.JFX) @@ -143,7 +140,8 @@ public class Toolbar extends ToolBar { "Toolbar.thumbnailSizeLabel=Thumbnail Size (px):", "Toolbar.sortHelp=The sort direction (ascending/descending) affects the queue of unseen groups that Image Gallery maintains, but changes to this queue aren't apparent until the \"Next Unseen Group\" button is pressed.", "Toolbar.sortHelpTitle=Group Sorting", - "Toolbar.getDataSources.errMessage=Unable to get datasources for current case."}) + "Toolbar.getDataSources.errMessage=Unable to get datasources for current case.", + "Toolbar.nonPathGroupingWarning.message=Grouping by attributes other than path does not support the data source filter.\nFiles and groups from all data sources will be shown."}) void initialize() { assert groupByBox != null : "fx:id=\"groupByBox\" was not injected: check your FXML file 'Toolbar.fxml'."; assert dataSourceComboBox != null : "fx:id=\"dataSourceComboBox\" was not injected: check your FXML file 'Toolbar.fxml'."; @@ -172,24 +170,25 @@ public class Toolbar extends ToolBar { groupByBox.disableProperty().bind(ImageGalleryController.getDefault().regroupDisabled()); groupByBox.setCellFactory(listView -> new AttributeListCell()); groupByBox.setButtonCell(new AttributeListCell()); - groupByBox.getSelectionModel().selectedItemProperty().addListener(observable -> { - if (groupByBox.getSelectionModel().getSelectedItem() != DrawableAttribute.PATH) { - Notifications.create().owner(getScene().getWindow()) - .text("Grouping by attributes other than path does not support the data source filter.\nFiles and groups from all data sources will be shown.") - .hideAfter(Duration.seconds(30)) - .showInformation(); + groupByBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (oldValue == DrawableAttribute.PATH + && newValue != DrawableAttribute.PATH) { + Notifications.create().owner(getScene().getRoot()) + .text(Bundle.Toolbar_nonPathGroupingWarning_message()) + .hideAfter(Duration.seconds(20)) + .showWarning(); } queryInvalidationListener.invalidated(observable); }); sortChooser = new SortChooser<>(GroupSortBy.getValues()); sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> { - final boolean orderDisabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY; - sortChooser.setSortOrderDisabled(orderDisabled); + final boolean orderDisabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY; + sortChooser.setSortOrderDisabled(orderDisabled); - final SortChooser.ValueType valueType = newComparator == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC; - sortChooser.setValueType(valueType); - queryInvalidationListener.invalidated(observable); + final SortChooser.ValueType valueType = newComparator == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC; + sortChooser.setValueType(valueType); + queryInvalidationListener.invalidated(observable); }); sortChooser.setComparator(controller.getGroupManager().getSortBy()); sortChooser.sortOrderProperty().addListener(queryInvalidationListener); @@ -210,11 +209,11 @@ public class Toolbar extends ToolBar { catGroupMenuButton.setText(cat5GroupAction.getText()); catGroupMenuButton.setGraphic(cat5GroupAction.getGraphic()); catGroupMenuButton.showingProperty().addListener(showing -> { - if (catGroupMenuButton.isShowing()) { - List categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()), - cat -> GuiUtils.createAutoAssigningMenuItem(catGroupMenuButton, new CategorizeGroupAction(cat, controller))); - catGroupMenuButton.getItems().setAll(categoryMenues); - } + if (catGroupMenuButton.isShowing()) { + List categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()), + cat -> GuiUtils.createAutoAssigningMenuItem(catGroupMenuButton, new CategorizeGroupAction(cat, controller))); + catGroupMenuButton.getItems().setAll(categoryMenues); + } }); } From d73f861d07b1eefcb15e7595d19664df4eddd23a Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 27 Aug 2018 17:25:22 +0200 Subject: [PATCH 37/84] WIP --- .../imagegallery/actions/NextUnseenGroup.java | 2 +- .../imagegallery/datamodel/DrawableDB.java | 132 +++++++++--------- .../datamodel/grouping/DrawableGroup.java | 1 + .../datamodel/grouping/GroupManager.java | 130 ++++++++++------- 4 files changed, 142 insertions(+), 123 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index fe12dfe010..fc7e5020ec 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -69,7 +69,7 @@ public class NextUnseenGroup extends Action { Optional.ofNullable(controller.viewState()) .map(ObjectExpression::getValue) .map(GroupViewState::getGroup) - .ifPresent(group -> groupManager.markGroupSeen(group, true)); + .ifPresent(group -> groupManager.saveGroupSeen(group, true)); if (unSeenGroups.isEmpty() == false) { controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index e9fe337d4b..7e33766ea5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -80,7 +80,7 @@ import org.sqlite.SQLiteJDBCLoader; */ public final class DrawableDB { - private static final org.sleuthkit.autopsy.coreutils.Logger LOGGER = Logger.getLogger(DrawableDB.class.getName()); + private static final org.sleuthkit.autopsy.coreutils.Logger logger = Logger.getLogger(DrawableDB.class.getName()); //column name constants////////////////////// private static final String ANALYZED = "analyzed"; //NON-NLS @@ -147,7 +147,7 @@ public final class DrawableDB { try { Class.forName("org.sqlite.JDBC"); } catch (ClassNotFoundException ex) { - LOGGER.log(Level.SEVERE, "Failed to load sqlite JDBC driver", ex); //NON-NLS + logger.log(Level.SEVERE, "Failed to load sqlite JDBC driver", ex); //NON-NLS } } private final SleuthkitCase tskCase; @@ -259,7 +259,7 @@ public final class DrawableDB { try { caseDbTransaction.rollback(); } catch (TskCoreException ex2) { - LOGGER.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); + logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); } } throw new ExceptionInInitializerError(ex); @@ -356,10 +356,10 @@ public final class DrawableDB { try { return new DrawableDB(dbPath.resolve("drawable.db"), controller); //NON-NLS } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "sql error creating database connection", ex); //NON-NLS + logger.log(Level.SEVERE, "sql error creating database connection", ex); //NON-NLS return null; } catch (ExceptionInInitializerError | IOException ex) { - LOGGER.log(Level.SEVERE, "error creating database connection", ex); //NON-NLS + logger.log(Level.SEVERE, "error creating database connection", ex); //NON-NLS return null; } } @@ -391,11 +391,11 @@ public final class DrawableDB { } try { - LOGGER.log(Level.INFO, String.format("sqlite-jdbc version %s loaded in %s mode", //NON-NLS + logger.log(Level.INFO, String.format("sqlite-jdbc version %s loaded in %s mode", //NON-NLS SQLiteJDBCLoader.getVersion(), SQLiteJDBCLoader.isNativeMode() ? "native" : "pure-java")); //NON-NLS } catch (Exception exception) { - LOGGER.log(Level.WARNING, "exception while checking sqlite-jdbc version and mode", exception); //NON-NLS + logger.log(Level.WARNING, "exception while checking sqlite-jdbc version and mode", exception); //NON-NLS } } @@ -414,7 +414,7 @@ public final class DrawableDB { setPragmas(); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "problem accessing database", ex); //NON-NLS + logger.log(Level.SEVERE, "problem accessing database", ex); //NON-NLS return false; } @@ -425,7 +425,7 @@ public final class DrawableDB { + " drawable_db_build_status VARCHAR(128) )"; //NON-NLS stmt.execute(sql); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "problem creating datasources table", ex); //NON-NLS + logger.log(Level.SEVERE, "problem creating datasources table", ex); //NON-NLS return false; } @@ -442,7 +442,7 @@ public final class DrawableDB { + " analyzed integer DEFAULT 0)"; //NON-NLS stmt.execute(sql); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "problem creating drawable_files table", ex); //NON-NLS + logger.log(Level.SEVERE, "problem creating drawable_files table", ex); //NON-NLS return false; } @@ -459,7 +459,7 @@ public final class DrawableDB { tskCase.getCaseDbAccessManager().createTable(GROUPS_TABLENAME, tableSchema); } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "problem creating groups table", ex); //NON-NLS + logger.log(Level.SEVERE, "problem creating groups table", ex); //NON-NLS return false; } @@ -469,7 +469,7 @@ public final class DrawableDB { + " hash_set_name VARCHAR(255) UNIQUE NOT NULL)"; //NON-NLS stmt.execute(sql); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "problem creating hash_sets table", ex); //NON-NLS + logger.log(Level.SEVERE, "problem creating hash_sets table", ex); //NON-NLS return false; } @@ -480,7 +480,7 @@ public final class DrawableDB { + " PRIMARY KEY (hash_set_id, obj_id))"; //NON-NLS stmt.execute(sql); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "problem creating hash_set_hits table", ex); //NON-NLS + logger.log(Level.SEVERE, "problem creating hash_set_hits table", ex); //NON-NLS return false; } @@ -488,35 +488,35 @@ public final class DrawableDB { String sql = "CREATE INDEX if not exists path_idx ON drawable_files(path)"; //NON-NLS stmt.execute(sql); } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "problem creating path_idx", ex); //NON-NLS + logger.log(Level.WARNING, "problem creating path_idx", ex); //NON-NLS } try (Statement stmt = con.createStatement()) { String sql = "CREATE INDEX if not exists name_idx ON drawable_files(name)"; //NON-NLS stmt.execute(sql); } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "problem creating name_idx", ex); //NON-NLS + logger.log(Level.WARNING, "problem creating name_idx", ex); //NON-NLS } try (Statement stmt = con.createStatement()) { String sql = "CREATE INDEX if not exists make_idx ON drawable_files(make)"; //NON-NLS stmt.execute(sql); } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "problem creating make_idx", ex); //NON-NLS + logger.log(Level.WARNING, "problem creating make_idx", ex); //NON-NLS } try (Statement stmt = con.createStatement()) { String sql = "CREATE INDEX if not exists model_idx ON drawable_files(model)"; //NON-NLS stmt.execute(sql); } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "problem creating model_idx", ex); //NON-NLS + logger.log(Level.WARNING, "problem creating model_idx", ex); //NON-NLS } try (Statement stmt = con.createStatement()) { String sql = "CREATE INDEX if not exists analyzed_idx ON drawable_files(analyzed)"; //NON-NLS stmt.execute(sql); } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "problem creating analyzed_idx", ex); //NON-NLS + logger.log(Level.WARNING, "problem creating analyzed_idx", ex); //NON-NLS } return true; @@ -537,7 +537,7 @@ public final class DrawableDB { closeStatements(); con.close(); } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "Failed to close connection to drawable.db", ex); //NON-NLS + logger.log(Level.WARNING, "Failed to close connection to drawable.db", ex); //NON-NLS } } con = null; @@ -549,7 +549,7 @@ public final class DrawableDB { con = DriverManager.getConnection("jdbc:sqlite:" + dbPath.toString()); //NON-NLS } } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "Failed to open connection to drawable.db", ex); //NON-NLS + logger.log(Level.WARNING, "Failed to open connection to drawable.db", ex); //NON-NLS } } @@ -600,7 +600,7 @@ public final class DrawableDB { names.add(rs.getString(HASH_SET_NAME)); } } catch (SQLException sQLException) { - LOGGER.log(Level.WARNING, "failed to get hash set names", sQLException); //NON-NLS + logger.log(Level.WARNING, "failed to get hash set names", sQLException); //NON-NLS } finally { dbReadUnlock(); } @@ -628,7 +628,7 @@ public final class DrawableDB { } } } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "failed to get group seen", ex); //NON-NLS + logger.log(Level.SEVERE, "failed to get group seen", ex); //NON-NLS } } } @@ -648,26 +648,22 @@ public final class DrawableDB { return queryResultProcessor.getGroupSeen(); } catch (TskCoreException ex) { String msg = String.format("Failed to get is group seen for group key %s", groupKey.getValueDisplayName()); //NON-NLS - LOGGER.log(Level.WARNING, msg, ex); + logger.log(Level.WARNING, msg, ex); } return false; } - public void markGroupSeen(GroupKey gk, boolean seen) { - try { - String updateSQL; - if (gk.getDataSource().isPresent()) { - updateSQL = String.format("SET seen = %d WHERE VALUE = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = %d", seen ? 1 : 0, - gk.getValueDisplayName(), gk.getAttribute().attrName.toString(), gk.getDataSourceObjId()); - } else { - updateSQL = String.format("SET seen = %d WHERE VALUE = \'%s\' AND attribute = \'%s\'", seen ? 1 : 0, - gk.getValueDisplayName(), gk.getAttribute().attrName.toString()); - } - tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS + public void markGroupSeen(GroupKey gk, boolean seen) throws TskCoreException { + String updateSQL; + if (gk.getDataSource().isPresent()) { + updateSQL = String.format("SET seen = %d WHERE VALUE = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = %d", seen ? 1 : 0, + gk.getValueDisplayName(), gk.getAttribute().attrName.toString(), gk.getDataSourceObjId()); + } else { + updateSQL = String.format("SET seen = %d WHERE VALUE = \'%s\' AND attribute = \'%s\'", seen ? 1 : 0, + gk.getValueDisplayName(), gk.getAttribute().attrName.toString()); } + tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL); } public boolean removeFile(long id) { @@ -695,10 +691,10 @@ public final class DrawableDB { try { caseDbTransaction.rollback(); } catch (TskCoreException ex2) { - LOGGER.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS + logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS } } - LOGGER.log(Level.SEVERE, "Error updating file", ex); //NON-NLS + logger.log(Level.SEVERE, "Error updating file", ex); //NON-NLS } } @@ -720,10 +716,10 @@ public final class DrawableDB { try { caseDbTransaction.rollback(); } catch (TskCoreException ex2) { - LOGGER.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS + logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS } } - LOGGER.log(Level.SEVERE, "Error inserting file", ex); //NON-NLS + logger.log(Level.SEVERE, "Error inserting file", ex); //NON-NLS } } @@ -792,7 +788,7 @@ public final class DrawableDB { } } } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "failed to insert/update hash hits for file" + f.getContentPathSafe(), ex); //NON-NLS + logger.log(Level.SEVERE, "failed to insert/update hash hits for file" + f.getContentPathSafe(), ex); //NON-NLS } //and update all groups this file is in @@ -818,7 +814,7 @@ public final class DrawableDB { * closed during processing, which doesn't need to be reported here. */ if (Case.isCaseOpen()) { - LOGGER.log(Level.SEVERE, "failed to insert/update file" + f.getContentPathSafe(), ex); //NON-NLS + logger.log(Level.SEVERE, "failed to insert/update file" + f.getContentPathSafe(), ex); //NON-NLS } } finally { @@ -850,14 +846,14 @@ public final class DrawableDB { try { rs.close(); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error closing resultset", ex); //NON-NLS + 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 + logger.log(Level.SEVERE, "Error closing statement ", ex); //NON-NLS } } dbReadUnlock(); @@ -882,7 +878,7 @@ public final class DrawableDB { updateDataSourceStmt.executeUpdate(); } catch (SQLException | NullPointerException ex) { - LOGGER.log(Level.SEVERE, "failed to insert/update datasources table", ex); //NON-NLS + logger.log(Level.SEVERE, "failed to insert/update datasources table", ex); //NON-NLS } finally { dbWriteUnlock(); } @@ -919,7 +915,7 @@ public final class DrawableDB { } } catch (SQLException ex) { String msg = String.format("Failed to determine if file %s is finalized", String.valueOf(fileId)); //NON-NLS - LOGGER.log(Level.WARNING, msg, ex); + logger.log(Level.WARNING, msg, ex); } finally { dbReadUnlock(); } @@ -937,7 +933,7 @@ public final class DrawableDB { return analyzedQuery.getInt(ANALYZED) == fileIds.size(); } } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "problem counting analyzed files: ", ex); //NON-NLS + logger.log(Level.WARNING, "problem counting analyzed files: ", ex); //NON-NLS } finally { dbReadUnlock(); } @@ -964,10 +960,10 @@ public final class DrawableDB { } } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "problem counting analyzed files: ", ex); //NON-NLS + logger.log(Level.WARNING, "problem counting analyzed files: ", ex); //NON-NLS } } catch (TskCoreException tskCoreException) { - LOGGER.log(Level.WARNING, "problem counting analyzed files: ", tskCoreException); //NON-NLS + logger.log(Level.WARNING, "problem counting analyzed files: ", tskCoreException); //NON-NLS } finally { dbReadUnlock(); } @@ -1004,14 +1000,14 @@ public final class DrawableDB { try { rs.close(); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error closing result set after executing findAllFileIdsWhere", ex); //NON-NLS + logger.log(Level.SEVERE, "Error closing result set after executing findAllFileIdsWhere", ex); //NON-NLS } } if (statement != null) { try { statement.close(); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error closing statement after executing findAllFileIdsWhere", ex); //NON-NLS + logger.log(Level.SEVERE, "Error closing statement after executing findAllFileIdsWhere", ex); //NON-NLS } } dbReadUnlock(); @@ -1045,14 +1041,14 @@ public final class DrawableDB { try { rs.close(); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error closing result set after executing countFilesWhere", ex); //NON-NLS + logger.log(Level.SEVERE, "Error closing result set after executing countFilesWhere", ex); //NON-NLS } } if (statement != null) { try { statement.close(); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "Error closing statement after executing countFilesWhere", ex); //NON-NLS + logger.log(Level.SEVERE, "Error closing statement after executing countFilesWhere", ex); //NON-NLS } } dbReadUnlock(); @@ -1132,7 +1128,7 @@ public final class DrawableDB { vals.add(value); } } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "Unable to get values for attribute", ex); //NON-NLS + logger.log(Level.WARNING, "Unable to get values for attribute", ex); //NON-NLS } finally { dbReadUnlock(); } @@ -1173,7 +1169,7 @@ public final class DrawableDB { } catch (TskCoreException ex) { // Don't need to report it if the case was closed if (Case.isCaseOpen()) { - LOGGER.log(Level.SEVERE, "Unable to insert group", ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to insert group", ex); //NON-NLS } } @@ -1193,7 +1189,7 @@ public final class DrawableDB { return DrawableFile.create(f, areFilesAnalyzed(Collections.singleton(id)), isVideoFile(f)); } catch (IllegalStateException ex) { - LOGGER.log(Level.SEVERE, "there is no case open; failed to load file with id: {0}", id); //NON-NLS + logger.log(Level.SEVERE, "there is no case open; failed to load file with id: {0}", id); //NON-NLS return null; } } @@ -1222,7 +1218,7 @@ public final class DrawableDB { } } } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "failed to get file for group:" + groupKey.getAttribute() + " == " + groupKey.getValue(), ex); //NON-NLS + logger.log(Level.WARNING, "failed to get file for group:" + groupKey.getAttribute() + " == " + groupKey.getValue(), ex); //NON-NLS } finally { dbReadUnlock(); } @@ -1282,7 +1278,7 @@ public final class DrawableDB { //TODO: delete from hash_set_hits table also... } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "failed to delete row for obj_id = " + id, ex); //NON-NLS + logger.log(Level.WARNING, "failed to delete row for obj_id = " + id, ex); //NON-NLS } finally { dbWriteUnlock(); } @@ -1343,7 +1339,7 @@ public final class DrawableDB { addImageFileToList(analyzedQuery.getLong(OBJ_ID)); } } catch (SQLException ex) { - LOGGER.log(Level.WARNING, "problem loading file IDs: ", ex); //NON-NLS + logger.log(Level.WARNING, "problem loading file IDs: ", ex); //NON-NLS } finally { dbReadUnlock(); } @@ -1394,9 +1390,9 @@ public final class DrawableDB { .count(); } } catch (IllegalStateException ex) { - LOGGER.log(Level.WARNING, "Case closed while getting files"); //NON-NLS + logger.log(Level.WARNING, "Case closed while getting files"); //NON-NLS } catch (TskCoreException ex1) { - LOGGER.log(Level.SEVERE, "Failed to get content tags by tag name.", ex1); //NON-NLS + logger.log(Level.SEVERE, "Failed to get content tags by tag name.", ex1); //NON-NLS } return -1; @@ -1445,7 +1441,7 @@ public final class DrawableDB { return resultSet.getLong("obj_count"); //NON-NLS } } catch (SQLException | TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error getting category count.", ex); //NON-NLS + logger.log(Level.SEVERE, "Error getting category count.", ex); //NON-NLS } return -1; } @@ -1479,7 +1475,7 @@ public final class DrawableDB { con.setAutoCommit(false); } catch (SQLException ex) { - LOGGER.log(Level.SEVERE, "failed to set auto-commit to to false", ex); //NON-NLS + logger.log(Level.SEVERE, "failed to set auto-commit to to false", ex); //NON-NLS } } @@ -1490,7 +1486,7 @@ public final class DrawableDB { con.rollback(); updatedFiles.clear(); } catch (SQLException ex1) { - LOGGER.log(Level.SEVERE, "Exception while attempting to rollback!!", ex1); //NON-NLS + logger.log(Level.SEVERE, "Exception while attempting to rollback!!", ex1); //NON-NLS } finally { close(); } @@ -1512,9 +1508,9 @@ public final class DrawableDB { } } catch (SQLException ex) { if (Case.isCaseOpen()) { - LOGGER.log(Level.SEVERE, "Error commiting drawable.db.", ex); //NON-NLS + logger.log(Level.SEVERE, "Error commiting drawable.db.", ex); //NON-NLS } else { - LOGGER.log(Level.WARNING, "Error commiting drawable.db - case is closed."); //NON-NLS + logger.log(Level.WARNING, "Error commiting drawable.db - case is closed."); //NON-NLS } rollback(); } @@ -1527,9 +1523,9 @@ public final class DrawableDB { con.setAutoCommit(true); } catch (SQLException ex) { if (Case.isCaseOpen()) { - LOGGER.log(Level.SEVERE, "Error setting auto-commit to true.", ex); //NON-NLS + logger.log(Level.SEVERE, "Error setting auto-commit to true.", ex); //NON-NLS } else { - LOGGER.log(Level.SEVERE, "Error setting auto-commit to true - case is closed"); //NON-NLS + logger.log(Level.SEVERE, "Error setting auto-commit to true - case is closed"); //NON-NLS } } finally { closed = true; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java index 3ef949c9c9..0b4940255f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java @@ -174,6 +174,7 @@ public class DrawableGroup implements Comparable { } } + synchronized void addFile(Long f) { if (fileIDs.contains(f) == false) { fileIDs.add(f); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 37617acbc8..146f613ccf 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -19,6 +19,11 @@ package org.sleuthkit.autopsy.imagegallery.datamodel.grouping; import com.google.common.eventbus.Subscribe; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; @@ -31,9 +36,12 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; @@ -86,13 +94,17 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData.DbType; /** - * Provides an abstraction layer on top of {@link DrawableDB} ( and to some - * extent {@link SleuthkitCase} ) to facilitate creation, retrieval, updating, - * and sorting of {@link DrawableGroup}s. + * Provides an abstraction layer on top of DrawableDB ( and to some extent + * SleuthkitCase ) to facilitate creation, retrieval, updating, and sorting of + * DrawableGroups. */ public class GroupManager { - private static final Logger LOGGER = Logger.getLogger(GroupManager.class.getName()); + private static final Logger logger = Logger.getLogger(GroupManager.class.getName()); + + /** An executor to submit async UI related background tasks to. */ + private final ListeningExecutorService exec = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( + new BasicThreadFactory.Builder().namingPattern("GUI Task -%d").build())); //NON-NLS private final ImageGalleryController controller; @@ -265,25 +277,40 @@ public class GroupManager { } /** - * 'mark' the given group as seen. This removes it from the queue of groups - * to review, and is persisted in the drawable db. + * 'Save' the given group as seen in the drawable db. * * @param group The DrawableGroup to mark as seen. - * @param seen The seen stastus to set. + * @param seen The seen state to set for the given group. + * + * @return A ListenableFuture that encapsulates saving the seen state to the + * DB. */ - @ThreadConfined(type = ThreadType.JFX) - public void markGroupSeen(DrawableGroup group, boolean seen) { - DrawableDB db = getDB(); - if (nonNull(db)) { - db.markGroupSeen(group.getGroupKey(), seen); - group.setSeen(seen); - if (seen) { - unSeenGroups.removeAll(group); - } else if (unSeenGroups.contains(group) == false) { - unSeenGroups.add(group); + public ListenableFuture saveGroupSeen(DrawableGroup group, boolean seen) { + synchronized (controller) { + DrawableDB db = getDB(); + if (nonNull(db)) { + return exec.submit(() -> { + try { + db.markGroupSeen(group.getGroupKey(), seen); + group.setSeen(seen); + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS + } + }); } - FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy)); } + return Futures.immediateFuture(null); + } + + @ThreadConfined(type = ThreadType.JFX) + private void updateUnSeenGroups(DrawableGroup group, boolean seen) { + if (seen) { + unSeenGroups.removeAll(group); + } else if (unSeenGroups.contains(group) == false) { + unSeenGroups.add(group); + } + FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy)); } /** @@ -387,7 +414,7 @@ public class GroupManager { .collect(Collectors.toSet()); } } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS + logger.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS throw ex; } } @@ -406,7 +433,7 @@ public class GroupManager { } return files; } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "TSK error getting files with Tag:" + tagName.getDisplayName(), ex); //NON-NLS + logger.log(Level.WARNING, "TSK error getting files with Tag:" + tagName.getDisplayName(), ex); //NON-NLS throw ex; } } @@ -495,7 +522,7 @@ public class GroupManager { groupByTask = new ReGroupTask<>(dataSource, groupBy, sortBy, sortOrder); Platform.runLater(() -> regroupProgress.bind(groupByTask.progressProperty())); - regroupExecutor.submit(groupByTask); + exec.submit(groupByTask); } else { // resort the list of groups setSortBy(sortBy); @@ -507,11 +534,6 @@ public class GroupManager { } } - /** - * an executor to submit async ui related background tasks to. - */ - final ExecutorService regroupExecutor = Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder().namingPattern("ui task -%d").build()); //NON-NLS - public ReadOnlyDoubleProperty regroupProgress() { return regroupProgress.getReadOnlyProperty(); } @@ -579,10 +601,9 @@ public class GroupManager { } /** - * handle {@link FileUpdateEvent} sent from Db when files are - * inserted/updated + * Handle notificationsS sent from Db when files are inserted/updated * - * @param evt + * @param updatedFileIDs The ID of the inserted/updated files. */ @Subscribe synchronized public void handleFileUpdate(Collection updatedFileIDs) { @@ -608,26 +629,22 @@ public class GroupManager { } private DrawableGroup popuplateIfAnalyzed(GroupKey groupKey, ReGroupTask task) { - - if (Objects.nonNull(task) && (task.isCancelled())) { - /* - * if this method call is part of a ReGroupTask and that task is - * cancelled, no-op - * - * this allows us to stop if a regroup task has been cancelled (e.g. - * the user picked a different group by attribute, while the current - * task was still running) - */ - - } else { // no task or un-cancelled task + /* + * If this method call is part of a ReGroupTask and that task is + * cancelled, no-op. + * + * This allows us to stop if a regroup task has been cancelled (e.g. the + * user picked a different group by attribute, while the current task + * was still running) + */ + if (isNull(task) || task.isCancelled() == false) { DrawableDB db = getDB(); + /* + * For attributes other than path we can't be sure a group is fully + * analyzed because we don't know all the files that will be a part + * of that group. just show them no matter what. + */ if (nonNull(db) && ((groupKey.getAttribute() != DrawableAttribute.PATH) || db.isGroupAnalyzed(groupKey))) { - /* - * for attributes other than path we can't be sure a group is - * fully analyzed because we don't know all the files that will - * be a part of that group. just show them no matter what. - */ - try { Set fileIDs = getFileIDsInGroup(groupKey); if (Objects.nonNull(fileIDs)) { @@ -636,34 +653,38 @@ public class GroupManager { synchronized (groupMap) { if (groupMap.containsKey(groupKey)) { group = groupMap.get(groupKey); - group.setFiles(ObjectUtils.defaultIfNull(fileIDs, Collections.emptySet())); + group.setSeen(groupSeen); } else { group = new DrawableGroup(groupKey, fileIDs, groupSeen); controller.getCategoryManager().registerListener(group); group.seenProperty().addListener((o, oldSeen, newSeen) - -> Platform.runLater(() -> markGroupSeen(group, newSeen)) - ); + -> saveGroupSeen(group, newSeen) + .addListener(() -> updateUnSeenGroups(group, newSeen), + Platform::runLater)); + groupMap.put(groupKey, group); } } + Platform.runLater(() -> { if (analyzedGroups.contains(group) == false) { analyzedGroups.add(group); - if (Objects.isNull(task)) { + if (isNull(task)) { FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy)); } } - markGroupSeen(group, groupSeen); + updateUnSeenGroups(group, groupSeen); }); return group; } } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex); //NON-NLS + logger.log(Level.SEVERE, "failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex); //NON-NLS } } } + return null; } @@ -687,6 +708,7 @@ public class GroupManager { } catch (Exception ex) { Exceptions.printStackTrace(ex); throw new TskCoreException("Failed to get file ids with mime type " + mimeType, ex); + } } @@ -842,7 +864,7 @@ public class GroupManager { return values; } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS + logger.log(Level.WARNING, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS return Collections.emptyList(); } } From 2d661e66c8e954d5501bd843eb2c909062ceb083 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 28 Aug 2018 08:44:24 +0200 Subject: [PATCH 38/84] cleanup NextUnseenGroup.java --- .../imagegallery/actions/NextUnseenGroup.java | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index fc7e5020ec..336b91471c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.imagegallery.actions; import java.util.Optional; import javafx.beans.Observable; -import javafx.beans.binding.ObjectExpression; +import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.scene.image.Image; import javafx.scene.image.ImageView; @@ -40,47 +40,46 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; "NextUnseenGroup.nextUnseenGroup=Next Unseen group"}) public class NextUnseenGroup extends Action { - private static final Image END = - new Image(NextUnseenGroup.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/control-stop.png")); //NON-NLS - private static final Image ADVANCE = - new Image(NextUnseenGroup.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/control-double.png")); //NON-NLS + private static final String IMAGE_PATH = "/org/sleuthkit/autopsy/imagegallery/images/"; //NON-NLS + private static final Image END = new Image(NextUnseenGroup.class.getResourceAsStream( + IMAGE_PATH + "control-stop.png")); //NON-NLS + private static final Image ADVANCE = new Image(NextUnseenGroup.class.getResourceAsStream( + IMAGE_PATH + "control-double.png")); //NON-NLS private static final String MARK_GROUP_SEEN = Bundle.NextUnseenGroup_markGroupSeen(); private static final String NEXT_UNSEEN_GROUP = Bundle.NextUnseenGroup_nextUnseenGroup(); - private final GroupManager groupManager; - private final ObservableList unSeenGroups; - private final ObservableList analyzedGroups; - public NextUnseenGroup(ImageGalleryController controller) { super(NEXT_UNSEEN_GROUP); - groupManager = controller.getGroupManager(); - unSeenGroups = groupManager.getUnSeenGroups(); - analyzedGroups = groupManager.getAnalyzedGroups(); + GroupManager groupManager = controller.getGroupManager(); + + ObservableList unSeenGroups = groupManager.getUnSeenGroups(); setGraphic(new ImageView(ADVANCE)); - //TODO: do we need both these listeners? - analyzedGroups.addListener((Observable o) -> this.updateButton()); - unSeenGroups.addListener((Observable o) -> this.updateButton()); + unSeenGroups.addListener((Observable o) -> this.updateButton(unSeenGroups)); setEventHandler(event -> { //fx-thread //if there is a group assigned to the view, mark it as seen Optional.ofNullable(controller.viewState()) - .map(ObjectExpression::getValue) + .map(ObservableValue::getValue) .map(GroupViewState::getGroup) .ifPresent(group -> groupManager.saveGroupSeen(group, true)); if (unSeenGroups.isEmpty() == false) { controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true); - updateButton(); + updateButton(unSeenGroups); } }); - updateButton(); + updateButton(unSeenGroups); } + /** + * + * @param unSeenGroups the value of unSeenGroups + */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - private void updateButton() { + private void updateButton(ObservableList unSeenGroups) { setDisabled(unSeenGroups.isEmpty()); if (unSeenGroups.size() <= 1) { setText(MARK_GROUP_SEEN); From dba02a6ffabc53b564a5103b43a12fbd12f79be5 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 28 Aug 2018 08:45:22 +0200 Subject: [PATCH 39/84] make sure unseen groups always get updated when a group is marked as seen --- .../imagegallery/actions/NextUnseenGroup.java | 52 +++++++++++-------- .../datamodel/grouping/GroupManager.java | 30 +++++------ 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index 336b91471c..24eb31fcea 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-17 Basis Technology Corp. + * Copyright 2011-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.imagegallery.actions; import java.util.Optional; +import javafx.application.Platform; import javafx.beans.Observable; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; @@ -41,52 +42,57 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; public class NextUnseenGroup extends Action { private static final String IMAGE_PATH = "/org/sleuthkit/autopsy/imagegallery/images/"; //NON-NLS - private static final Image END = new Image(NextUnseenGroup.class.getResourceAsStream( + private static final Image END_IMAGE = new Image(NextUnseenGroup.class.getResourceAsStream( IMAGE_PATH + "control-stop.png")); //NON-NLS - private static final Image ADVANCE = new Image(NextUnseenGroup.class.getResourceAsStream( + private static final Image ADVANCE_IMAGE = new Image(NextUnseenGroup.class.getResourceAsStream( IMAGE_PATH + "control-double.png")); //NON-NLS private static final String MARK_GROUP_SEEN = Bundle.NextUnseenGroup_markGroupSeen(); private static final String NEXT_UNSEEN_GROUP = Bundle.NextUnseenGroup_nextUnseenGroup(); + private final ImageGalleryController controller; + private final ObservableList unSeenGroups; + public NextUnseenGroup(ImageGalleryController controller) { super(NEXT_UNSEEN_GROUP); + setGraphic(new ImageView(ADVANCE_IMAGE)); + + this.controller = controller; GroupManager groupManager = controller.getGroupManager(); + unSeenGroups = groupManager.getUnSeenGroups(); + unSeenGroups.addListener((Observable observable) -> this.updateButton()); - ObservableList unSeenGroups = groupManager.getUnSeenGroups(); - setGraphic(new ImageView(ADVANCE)); - - unSeenGroups.addListener((Observable o) -> this.updateButton(unSeenGroups)); - - setEventHandler(event -> { - //fx-thread + setEventHandler(event -> { //on fx-thread //if there is a group assigned to the view, mark it as seen Optional.ofNullable(controller.viewState()) .map(ObservableValue::getValue) .map(GroupViewState::getGroup) - .ifPresent(group -> groupManager.saveGroupSeen(group, true)); + .ifPresent(group -> { + groupManager.saveGroupSeen(group, true) + .addListener(this::advanceToNextUnseenGroup, Platform::runLater); + }); - if (unSeenGroups.isEmpty() == false) { - controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true); - updateButton(unSeenGroups); - } }); - updateButton(unSeenGroups); + updateButton(); } - /** - * - * @param unSeenGroups the value of unSeenGroups - */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - private void updateButton(ObservableList unSeenGroups) { + private void advanceToNextUnseenGroup() { + if (unSeenGroups.isEmpty() == false) { + controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true); + } + updateButton(); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + private void updateButton() { setDisabled(unSeenGroups.isEmpty()); if (unSeenGroups.size() <= 1) { setText(MARK_GROUP_SEEN); - setGraphic(new ImageView(END)); + setGraphic(new ImageView(END_IMAGE)); } else { setText(NEXT_UNSEEN_GROUP); - setGraphic(new ImageView(ADVANCE)); + setGraphic(new ImageView(ADVANCE_IMAGE)); } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 146f613ccf..cc65732917 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -286,20 +286,19 @@ public class GroupManager { * DB. */ public ListenableFuture saveGroupSeen(DrawableGroup group, boolean seen) { - synchronized (controller) { - DrawableDB db = getDB(); - if (nonNull(db)) { - return exec.submit(() -> { - try { - db.markGroupSeen(group.getGroupKey(), seen); - group.setSeen(seen); - - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS - } - }); - } + DrawableDB db = getDB(); + if (nonNull(db)) { + return exec.submit(() -> { + try { + db.markGroupSeen(group.getGroupKey(), seen); + group.setSeen(seen); + Platform.runLater(() -> updateUnSeenGroups(group, seen)); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS + } + }); } + return Futures.immediateFuture(null); } @@ -659,9 +658,7 @@ public class GroupManager { group = new DrawableGroup(groupKey, fileIDs, groupSeen); controller.getCategoryManager().registerListener(group); group.seenProperty().addListener((o, oldSeen, newSeen) - -> saveGroupSeen(group, newSeen) - .addListener(() -> updateUnSeenGroups(group, newSeen), - Platform::runLater)); + -> saveGroupSeen(group, newSeen)); groupMap.put(groupKey, group); } @@ -857,7 +854,6 @@ public class GroupManager { default: //otherwise do straight db query if (nonNull(db)) { - //TODO -1017: pass datasource in here as appropriate values = db.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource); } } From 02955014ba3549dc4b5b34ab35e2cc4c01e3c8fa Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 28 Aug 2018 13:19:31 +0200 Subject: [PATCH 40/84] WIP --- .../imagegallery/ImageGalleryController.java | 5 -- .../imagegallery/actions/NextUnseenGroup.java | 3 +- .../imagegallery/datamodel/DrawableDB.java | 77 +++++++------------ .../datamodel/grouping/GroupManager.java | 60 ++++++++------- 4 files changed, 61 insertions(+), 84 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 9b49bcc546..4918163d88 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -39,7 +39,6 @@ import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.ReadOnlyIntegerWrapper; -import javafx.beans.property.ReadOnlyLongWrapper; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleBooleanProperty; @@ -61,7 +60,6 @@ import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.netbeans.api.progress.ProgressHandle; import org.openide.util.Cancellable; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case.CaseType; @@ -127,9 +125,6 @@ public final class ImageGalleryController { private final CategoryManager categoryManager = new CategoryManager(this); private final DrawableTagsManager tagsManager = new DrawableTagsManager(null); - // RAMAN TBD: initialize this to 0 - private final ReadOnlyLongWrapper filterByDataSourceId = new ReadOnlyLongWrapper(1); - private Runnable showTree; private Toolbar toolbar; private StackPane fullUIStackPane; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index 24eb31fcea..d2b983b693 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -68,10 +68,9 @@ public class NextUnseenGroup extends Action { .map(ObservableValue::getValue) .map(GroupViewState::getGroup) .ifPresent(group -> { - groupManager.saveGroupSeen(group, true) + groupManager.setGroupSeen(group, true) .addListener(this::advanceToNextUnseenGroup, Platform::runLater); }); - }); updateButton(); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 7e33766ea5..7f84fbdfb7 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.imagegallery.datamodel; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import java.io.IOException; import java.nio.file.Files; @@ -68,6 +70,7 @@ import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData.DbType; +import org.sleuthkit.datamodel.TskDataException; import org.sqlite.SQLiteJDBCLoader; /** @@ -636,10 +639,12 @@ public final class DrawableDB { try { String groupSeenQueryStmt; - if (groupKey.getDataSource().isPresent()) { - groupSeenQueryStmt = String.format("seen FROM " + GROUPS_TABLENAME + " WHERE value = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = %d", groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString(), groupKey.getDataSourceObjId()); + if (groupKey.getAttribute() == DrawableAttribute.PATH) { + groupSeenQueryStmt = String.format("seen FROM " + GROUPS_TABLENAME + " WHERE value = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = %d", + groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString(), groupKey.getDataSourceObjId()); } else { - groupSeenQueryStmt = String.format("seen FROM " + GROUPS_TABLENAME + " WHERE value = \'%s\' AND attribute = \'%s\'", groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString()); + groupSeenQueryStmt = String.format("seen FROM " + GROUPS_TABLENAME + " WHERE value = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = 0", + groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString()); } GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor(); @@ -654,14 +659,14 @@ public final class DrawableDB { return false; } - public void markGroupSeen(GroupKey gk, boolean seen) throws TskCoreException { + public void setGroupSeen(GroupKey gk, boolean seen) throws TskCoreException { String updateSQL; - if (gk.getDataSource().isPresent()) { - updateSQL = String.format("SET seen = %d WHERE VALUE = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = %d", seen ? 1 : 0, - gk.getValueDisplayName(), gk.getAttribute().attrName.toString(), gk.getDataSourceObjId()); + if (gk.getAttribute() == DrawableAttribute.PATH) { + updateSQL = String.format("SET seen = %d WHERE VALUE = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = %d", + seen ? 1 : 0, gk.getValueDisplayName(), gk.getAttribute().attrName.toString(), gk.getDataSourceObjId()); } else { - updateSQL = String.format("SET seen = %d WHERE VALUE = \'%s\' AND attribute = \'%s\'", seen ? 1 : 0, - gk.getValueDisplayName(), gk.getAttribute().attrName.toString()); + updateSQL = String.format("SET seen = %d WHERE VALUE = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = 0", + seen ? 1 : 0, gk.getValueDisplayName(), gk.getAttribute().attrName.toString()); } tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL); } @@ -699,30 +704,6 @@ public final class DrawableDB { } - public void insertFile(DrawableFile f) { - DrawableTransaction trans = null; - CaseDbTransaction caseDbTransaction = null; - try { - trans = beginTransaction(); - caseDbTransaction = tskCase.beginTransaction(); - insertFile(f, trans, caseDbTransaction); - commitTransaction(trans, true); - caseDbTransaction.commit(); - } catch (TskCoreException ex) { - if (null != trans) { - rollbackTransaction(trans); - } - if (null != caseDbTransaction) { - try { - caseDbTransaction.rollback(); - } catch (TskCoreException ex2) { - logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS - } - } - logger.log(Level.SEVERE, "Error inserting file", ex); //NON-NLS - } - } - public void insertFile(DrawableFile f, DrawableTransaction tr, CaseDbTransaction caseDbTransaction) { insertOrUpdateFile(f, tr, insertFileStmt, caseDbTransaction); } @@ -795,7 +776,6 @@ public final class DrawableDB { for (DrawableAttribute attr : DrawableAttribute.getGroupableAttrs()) { Collection> vals = attr.getValue(f); for (Comparable val : vals) { - //use empty string for null values (mime_type), this shouldn't happen! if (null != val) { if (attr == DrawableAttribute.PATH) { insertGroup(f.getAbstractFile().getDataSource().getId(), val.toString(), attr, caseDbTransaction); @@ -1065,9 +1045,10 @@ public final class DrawableDB { * * @return */ - public > List findValuesForAttribute(DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder, DataSource dataSource) { + @SuppressWarnings("unchecked") + public > Multimap findValuesForAttribute(DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder, DataSource dataSource) throws TskDataException, TskCoreException { - List vals = new ArrayList<>(); + Multimap values = HashMultimap.create(); switch (groupBy.attrName) { case ANALYZED: @@ -1080,13 +1061,13 @@ public final class DrawableDB { dbReadLock(); //TODO: convert this to prepared statement - StringBuilder query = new StringBuilder("SELECT " + groupBy.attrName.toString() + ", COUNT(*) FROM drawable_files "); //NON-NLS + StringBuilder query = new StringBuilder("SELECT data_source_obj_id, " + groupBy.attrName.toString() + ", COUNT(*) FROM drawable_files "); //NON-NLS if (dataSource != null) { query.append(" WHERE data_source_obj_id = ").append(dataSource.getId()); } - query.append(" GROUP BY ").append(groupBy.attrName.toString()); + query.append(" GROUP BY data_source_obj_id, ").append(groupBy.attrName.toString()); String orderByClause = ""; @@ -1115,17 +1096,16 @@ public final class DrawableDB { } try (Statement stmt = con.createStatement(); - ResultSet valsResults = stmt.executeQuery(query.toString())) { - while (valsResults.next()) { + ResultSet results = stmt.executeQuery(query.toString())) { + while (results.next()) { /* * I don't like that we have to do this cast here, but * can't think of a better alternative at the momment * unless something has gone seriously wrong, we know * this should be of type A even if JAVA doesn't */ - @SuppressWarnings("unchecked") - A value = (A) valsResults.getObject(groupBy.attrName.toString()); - vals.add(value); + values.put(tskCase.getDataSource(results.getLong("data_source_obj_id")), + (A) results.getObject(groupBy.attrName.toString())); } } catch (SQLException ex) { logger.log(Level.WARNING, "Unable to get values for attribute", ex); //NON-NLS @@ -1134,7 +1114,7 @@ public final class DrawableDB { } } - return vals; + return values; } /** @@ -1157,12 +1137,14 @@ public final class DrawableDB { * @param caseDbTransaction transaction to use for CaseDB insert/updates */ private void insertGroup(long ds_obj_id, final String value, DrawableAttribute groupBy, CaseDbTransaction caseDbTransaction) { - String insertSQL = ""; + String insertSQL; try { - insertSQL = String.format(" (data_source_obj_id, value, attribute) VALUES (\'%d\', \'%s\', \'%s\')", ds_obj_id, value, groupBy.attrName.toString()); + insertSQL = String.format(" (data_source_obj_id, value, attribute) VALUES (\'%d\', \'%s\', \'%s\')", + ds_obj_id, value, groupBy.attrName.toString()); if (DbType.POSTGRESQL == tskCase.getDatabaseType()) { - insertSQL += String.format(" ON CONFLICT (data_source_obj_id, value, attribute) DO UPDATE SET value = \'%s\', attribute=\'%s\'", value, groupBy.attrName.toString()); + insertSQL += String.format(" ON CONFLICT (data_source_obj_id, value, attribute) DO UPDATE SET value = \'%s\', attribute=\'%s\'", + value, groupBy.attrName.toString()); } tskCase.getCaseDbAccessManager().insertOrUpdate(GROUPS_TABLENAME, insertSQL, caseDbTransaction); @@ -1170,7 +1152,6 @@ public final class DrawableDB { // Don't need to report it if the case was closed if (Case.isCaseOpen()) { logger.log(Level.SEVERE, "Unable to insert group", ex); //NON-NLS - } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index cc65732917..1e3eca1605 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.imagegallery.datamodel.grouping; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import com.google.common.eventbus.Subscribe; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; @@ -92,6 +94,7 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData.DbType; +import org.sleuthkit.datamodel.TskDataException; /** * Provides an abstraction layer on top of DrawableDB ( and to some extent @@ -137,7 +140,7 @@ public class GroupManager { private volatile GroupSortBy sortBy = GroupSortBy.PRIORITY; private volatile DrawableAttribute groupBy = DrawableAttribute.PATH; private volatile SortOrder sortOrder = SortOrder.ASCENDING; - private volatile DataSource dataSource = null; + private volatile DataSource dataSource = null; //null indicates all datasources private final ReadOnlyObjectWrapper< Comparator> sortByProp = new ReadOnlyObjectWrapper<>(sortBy); private final ReadOnlyObjectWrapper< DrawableAttribute> groupByProp = new ReadOnlyObjectWrapper<>(groupBy); @@ -285,12 +288,12 @@ public class GroupManager { * @return A ListenableFuture that encapsulates saving the seen state to the * DB. */ - public ListenableFuture saveGroupSeen(DrawableGroup group, boolean seen) { + public ListenableFuture setGroupSeen(DrawableGroup group, boolean seen) { DrawableDB db = getDB(); if (nonNull(db)) { return exec.submit(() -> { try { - db.markGroupSeen(group.getGroupKey(), seen); + db.setGroupSeen(group.getGroupKey(), seen); group.setSeen(seen); Platform.runLater(() -> updateUnSeenGroups(group, seen)); } catch (TskCoreException ex) { @@ -657,8 +660,8 @@ public class GroupManager { } else { group = new DrawableGroup(groupKey, fileIDs, groupSeen); controller.getCategoryManager().registerListener(group); - group.seenProperty().addListener((o, oldSeen, newSeen) - -> saveGroupSeen(group, newSeen)); +// group.seenProperty().addListener((o, oldSeen, newSeen) +// -> saveGroupSeen(group, newSeen)); groupMap.put(groupKey, group); } @@ -721,16 +724,16 @@ public class GroupManager { "# {0} - groupBy attribute Name", "# {1} - atribute value", "ReGroupTask.progressUpdate=regrouping files by {0} : {1}"}) - private class ReGroupTask> extends LoggedTask { + private class ReGroupTask> extends LoggedTask { private final DataSource dataSource; - private final DrawableAttribute groupBy; + private final DrawableAttribute groupBy; private final GroupSortBy sortBy; private final SortOrder sortOrder; private ProgressHandle groupProgress; - ReGroupTask(DataSource dataSource, DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder) { + ReGroupTask(DataSource dataSource, DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder) { super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), true); this.dataSource = dataSource; this.groupBy = groupBy; @@ -757,23 +760,24 @@ public class GroupManager { }); // Get the list of group keys - final List vals = findValuesForAttribute(); + final Multimap valsByDataSource = findValuesForAttribute(); - groupProgress.start(vals.size()); + groupProgress.start(valsByDataSource.size()); int p = 0; // For each key value, partially create the group and add it to the list. - for (final AttrType val : vals) { + for (final Map.Entry val : valsByDataSource.entries()) { if (isCancelled()) { return null;//abort } p++; - updateMessage(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val)); - updateProgress(p, vals.size()); + updateMessage(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val.getValue())); + updateProgress(p, valsByDataSource.size()); groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); - popuplateIfAnalyzed(new GroupKey<>(groupBy, val, dataSource), this); + popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); } Platform.runLater(() -> FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy))); + Platform.runLater(() -> FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy))); updateProgress(1, 1); return null; @@ -797,28 +801,27 @@ public class GroupManager { * * @return */ - @SuppressWarnings({"unchecked"}) - public List findValuesForAttribute() { - List values = Collections.emptyList(); + public Multimap findValuesForAttribute() { DrawableDB db = getDB(); + Multimap results = HashMultimap.create(); try { switch (groupBy.attrName) { //these cases get special treatment case CATEGORY: - values = (List) Arrays.asList(DhsImageCategory.values()); + results.putAll(null, Arrays.asList(DhsImageCategory.values())); break; case TAGS: - values = (List) controller.getTagsManager().getTagNamesInUse().stream() + results.putAll(null, controller.getTagsManager().getTagNamesInUse().stream() .filter(CategoryManager::isNotCategoryTagName) - .collect(Collectors.toList()); + .collect(Collectors.toList())); break; + case ANALYZED: - values = (List) Arrays.asList(false, true); + results.putAll(null, Arrays.asList(false, true)); break; case HASHSET: if (nonNull(db)) { - TreeSet names = new TreeSet<>((Collection) db.getHashSetNames()); - values = new ArrayList<>(names); + results.putAll(null, new TreeSet<>(db.getHashSetNames())); } break; case MIME_TYPE: @@ -848,21 +851,20 @@ public class GroupManager { } catch (SQLException | TskCoreException ex) { Exceptions.printStackTrace(ex); } - values = new ArrayList<>((Collection) types); + results.putAll(null, types); } break; default: //otherwise do straight db query if (nonNull(db)) { - values = db.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource); + results.putAll(db.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); } } - return values; - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS - return Collections.emptyList(); + } catch (TskCoreException | TskDataException ex) { + logger.log(Level.SEVERE, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS } + return results; } } From 6b5cc03fad32385954fa6959066f2a6b860e2e79 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 29 Aug 2018 11:56:38 +0200 Subject: [PATCH 41/84] make non path regrouping cancellable via dialog --- .../imagegallery/ImageGalleryController.java | 12 ++-- .../datamodel/grouping/GroupManager.java | 17 ++--- .../autopsy/imagegallery/gui/Toolbar.java | 68 ++++++++++++------- .../imagegallery/gui/navpanel/GroupTree.java | 14 ++-- .../imagegallery/gui/navpanel/NavPanel.java | 29 ++++---- 5 files changed, 76 insertions(+), 64 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 4918163d88..336ab8ed3d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -239,12 +239,12 @@ public final class ImageGalleryController { } }); - groupManager.getUnSeenGroups().addListener((Observable observable) -> { - //if there are unseen groups and none being viewed - if (groupManager.getUnSeenGroups().isEmpty() == false && (getViewState() == null || getViewState().getGroup() == null)) { - advance(GroupViewState.tile(groupManager.getUnSeenGroups().get(0)), true); - } - }); +// groupManager.getUnSeenGroups().addListener((Observable observable) -> { +// //if there are unseen groups and none being viewed +// if (groupManager.getUnSeenGroups().isEmpty() == false && (getViewState() == null || getViewState().getGroup() == null)) { +// advance(GroupViewState.tile(groupManager.getUnSeenGroups().get(0)), true); +// } +// }); viewState().addListener((Observable observable) -> { //when the viewed group changes, clear the selection and the undo/redo history diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 1e3eca1605..37149d3243 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -112,22 +112,18 @@ public class GroupManager { private final ImageGalleryController controller; /** - * map from {@link GroupKey}s to {@link DrawableGroup}s. All groups (even - * not fully analyzed or not visible groups could be in this map + * map from GroupKey} to DrawableGroupSs. All groups (even not fully + * analyzed or not visible groups could be in this map */ @GuardedBy("this") private final Map, DrawableGroup> groupMap = new HashMap<>(); - /** - * list of all analyzed groups - */ + /** list of all analyzed groups */ @ThreadConfined(type = ThreadType.JFX) private final ObservableList analyzedGroups = FXCollections.observableArrayList(); private final ObservableList unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups); - /** - * list of unseen groups - */ + /** list of unseen groups */ @ThreadConfined(type = ThreadType.JFX) private final ObservableList unSeenGroups = FXCollections.observableArrayList(); private final ObservableList unmodifiableUnSeenGroups = FXCollections.unmodifiableObservableList(unSeenGroups); @@ -776,8 +772,6 @@ public class GroupManager { groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); } - Platform.runLater(() -> FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy))); - Platform.runLater(() -> FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy))); updateProgress(1, 1); return null; @@ -790,6 +784,9 @@ public class GroupManager { groupProgress.finish(); groupProgress = null; } + if (unSeenGroups.isEmpty() == false) { + controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true); + } } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 0ab052f45f..7317d10c33 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -31,6 +31,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.Executors; +import java.util.function.Consumer; import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.InvalidationListener; @@ -42,10 +43,13 @@ import javafx.fxml.FXML; import javafx.geometry.Insets; import javafx.scene.Cursor; import javafx.scene.Node; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.MenuItem; +import javafx.scene.control.SingleSelectionModel; import javafx.scene.control.Slider; import javafx.scene.control.SplitMenuButton; import javafx.scene.control.ToolBar; @@ -54,6 +58,7 @@ import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.scene.text.Text; +import javafx.stage.Modality; import javafx.util.Duration; import javafx.util.StringConverter; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; @@ -114,14 +119,15 @@ public class Toolbar extends ToolBar { @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private final ObservableList> dataSources = FXCollections.observableArrayList(); + private SingleSelectionModel> dataSourceSelectionModel; private final InvalidationListener queryInvalidationListener = new InvalidationListener() { @Override public void invalidated(Observable invalidated) { - Optional selectedItem = dataSourceComboBox.getSelectionModel().getSelectedItem(); - selectedItem = defaultIfNull(selectedItem, Optional.empty()); + Optional selectedDataSource + = defaultIfNull(dataSourceSelectionModel.getSelectedItem(), Optional.empty()); - controller.getGroupManager().regroup(selectedItem.orElse(null), + controller.getGroupManager().regroup(selectedDataSource.orElse(null), groupByBox.getSelectionModel().getSelectedItem(), sortChooser.getComparator(), sortChooser.getSortOrder(), @@ -141,7 +147,9 @@ public class Toolbar extends ToolBar { "Toolbar.sortHelp=The sort direction (ascending/descending) affects the queue of unseen groups that Image Gallery maintains, but changes to this queue aren't apparent until the \"Next Unseen Group\" button is pressed.", "Toolbar.sortHelpTitle=Group Sorting", "Toolbar.getDataSources.errMessage=Unable to get datasources for current case.", - "Toolbar.nonPathGroupingWarning.message=Grouping by attributes other than path does not support the data source filter.\nFiles and groups from all data sources will be shown."}) + "Toolbar.nonPathGroupingWarning.content=Proceed with regrouping?", + "Toolbar.nonPathGroupingWarning.header=Grouping by attributes other than path does not support the data source filter.\nFiles and groups from all data sources will be shown.", + "Toolbar.nonPathGroupingWarning.title=Image Gallery"}) void initialize() { assert groupByBox != null : "fx:id=\"groupByBox\" was not injected: check your FXML file 'Toolbar.fxml'."; assert dataSourceComboBox != null : "fx:id=\"dataSourceComboBox\" was not injected: check your FXML file 'Toolbar.fxml'."; @@ -152,6 +160,7 @@ public class Toolbar extends ToolBar { assert catGroupMenuButton != null : "fx:id=\"catGroupMenuButton\" was not injected: check your FXML file 'Toolbar.fxml'."; assert thumbnailSizeLabel != null : "fx:id=\"thumbnailSizeLabel\" was not injected: check your FXML file 'Toolbar.fxml'."; assert sizeSlider != null : "fx:id=\"sizeSlider\" was not injected: check your FXML file 'Toolbar.fxml'."; + this.dataSourceSelectionModel = dataSourceComboBox.getSelectionModel(); //set internationalized label text groupByLabel.setText(Bundle.Toolbar_groupByLabel()); @@ -173,22 +182,31 @@ public class Toolbar extends ToolBar { groupByBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { if (oldValue == DrawableAttribute.PATH && newValue != DrawableAttribute.PATH) { - Notifications.create().owner(getScene().getRoot()) - .text(Bundle.Toolbar_nonPathGroupingWarning_message()) - .hideAfter(Duration.seconds(20)) - .showWarning(); + + Alert alert = new Alert(Alert.AlertType.CONFIRMATION, Bundle.Toolbar_nonPathGroupingWarning_content()); + alert.setHeaderText(Bundle.Toolbar_nonPathGroupingWarning_header()); + alert.setTitle(Bundle.Toolbar_nonPathGroupingWarning_title()); + alert.initModality(Modality.APPLICATION_MODAL); + alert.initOwner(getScene().getWindow()); + GuiUtils.setDialogIcons(alert); + if (alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) { + queryInvalidationListener.invalidated(observable); + } else { + Platform.runLater(() -> groupByBox.getSelectionModel().select(DrawableAttribute.PATH)); + } + } else { + queryInvalidationListener.invalidated(observable); } - queryInvalidationListener.invalidated(observable); }); sortChooser = new SortChooser<>(GroupSortBy.getValues()); sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> { - final boolean orderDisabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY; - sortChooser.setSortOrderDisabled(orderDisabled); + final boolean orderDisabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY; + sortChooser.setSortOrderDisabled(orderDisabled); - final SortChooser.ValueType valueType = newComparator == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC; - sortChooser.setValueType(valueType); - queryInvalidationListener.invalidated(observable); + final SortChooser.ValueType valueType = newComparator == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC; + sortChooser.setValueType(valueType); + queryInvalidationListener.invalidated(observable); }); sortChooser.setComparator(controller.getGroupManager().getSortBy()); sortChooser.sortOrderProperty().addListener(queryInvalidationListener); @@ -209,11 +227,11 @@ public class Toolbar extends ToolBar { catGroupMenuButton.setText(cat5GroupAction.getText()); catGroupMenuButton.setGraphic(cat5GroupAction.getGraphic()); catGroupMenuButton.showingProperty().addListener(showing -> { - if (catGroupMenuButton.isShowing()) { - List categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()), - cat -> GuiUtils.createAutoAssigningMenuItem(catGroupMenuButton, new CategorizeGroupAction(cat, controller))); - catGroupMenuButton.getItems().setAll(categoryMenues); - } + if (catGroupMenuButton.isShowing()) { + List categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()), + cat -> GuiUtils.createAutoAssigningMenuItem(catGroupMenuButton, new CategorizeGroupAction(cat, controller))); + catGroupMenuButton.getItems().setAll(categoryMenues); + } }); } @@ -237,19 +255,18 @@ public class Toolbar extends ToolBar { Case.addEventTypeSubscriber(ImmutableSet.of(DATA_SOURCE_ADDED, DATA_SOURCE_DELETED), evt -> { Platform.runLater(() -> { - Optional selectedItem = dataSourceComboBox.getSelectionModel().getSelectedItem(); - syncDataSources() - .addListener(() -> dataSourceComboBox.getSelectionModel().select(selectedItem), Platform::runLater); + Optional selectedItem = dataSourceSelectionModel.getSelectedItem(); + syncDataSources().addListener(() -> dataSourceSelectionModel.select(selectedItem), Platform::runLater); }); }); syncDataSources(); controller.getGroupManager().getDataSourceProperty().addListener((observable, oldDataSource, newDataSource) -> { - dataSourceComboBox.getSelectionModel().select(Optional.ofNullable(newDataSource)); + dataSourceSelectionModel.select(Optional.ofNullable(newDataSource)); }); - dataSourceComboBox.getSelectionModel().select(Optional.ofNullable(controller.getGroupManager().getDataSource())); + dataSourceSelectionModel.select(Optional.ofNullable(controller.getGroupManager().getDataSource())); dataSourceComboBox.disableProperty().bind(groupByBox.getSelectionModel().selectedItemProperty().isNotEqualTo(DrawableAttribute.PATH)); - dataSourceComboBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener); + dataSourceSelectionModel.selectedItemProperty().addListener(queryInvalidationListener); } private void initTagMenuButton() { @@ -368,6 +385,7 @@ public class Toolbar extends ToolBar { } public Toolbar(ImageGalleryController controller) { + this.controller = controller; FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java index f874aa4c69..31b8a57871 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java @@ -84,9 +84,7 @@ final public class GroupTree extends NavPanel> { sortGroups(); }); - for (DrawableGroup g : getGroupManager().getAnalyzedGroups()) { - insertGroup(g); - } + getGroupManager().getAnalyzedGroups().forEach(this::insertGroup); sortGroups(); } @@ -102,12 +100,10 @@ final public class GroupTree extends NavPanel> { if (treeItemForGroup != null) { groupTree.getSelectionModel().select(treeItemForGroup); - Platform.runLater(() -> { - int row = groupTree.getRow(treeItemForGroup); - if (row != -1) { - groupTree.scrollTo(row - 2); //put newly selected row 3 from the top - } - }); + int row = groupTree.getRow(treeItemForGroup); + if (row != -1) { + groupTree.scrollTo(row - 2); //put newly selected row 3 from the top + } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java index 5d937be412..037d3c3344 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java @@ -22,7 +22,10 @@ import com.google.common.eventbus.Subscribe; import java.util.Comparator; import java.util.Optional; import java.util.function.Function; +import javafx.application.Platform; import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.control.SelectionModel; import javafx.scene.control.Tab; @@ -91,12 +94,20 @@ abstract class NavPanel extends Tab { //keep selection in sync with controller controller.viewState().addListener(observable -> { - Optional.ofNullable(controller.viewState().get()) - .map(GroupViewState::getGroup) - .ifPresent(this::setFocusedGroup); + Platform.runLater(() -> { + Optional.ofNullable(controller.viewState().get()) + .map(GroupViewState::getGroup) + .ifPresent(this::setFocusedGroup); + }); }); - getSelectionModel().selectedItemProperty().addListener(o -> updateControllersGroup()); + // notify controller about group selection in this view + getSelectionModel().selectedItemProperty() + .addListener((observable, oldItem, newSelectedItem) -> { + Optional.ofNullable(newSelectedItem) + .map(getDataItemMapper()) + .ifPresent(group -> controller.advance(GroupViewState.tile(group), false)); + }); } /** @@ -121,16 +132,6 @@ abstract class NavPanel extends Tab { : comparator.reversed(); } - /** - * notify controller about group selection in this view - */ - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - void updateControllersGroup() { - Optional.ofNullable(getSelectionModel().getSelectedItem()) - .map(getDataItemMapper()) - .ifPresent(group -> controller.advance(GroupViewState.tile(group), false)); - } - /** * Sort the groups in this view according to the currently selected sorting * options. Attempts to maintain selection. From 99100f0557053f185bca8d762a7eb7b2331121fe Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 29 Aug 2018 12:28:54 +0200 Subject: [PATCH 42/84] force regroup when using datasource dialog --- .../autopsy/imagegallery/ImageGalleryTopComponent.java | 5 ++++- .../imagegallery/datamodel/grouping/GroupManager.java | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 774f0854de..a1d2e773be 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -47,6 +47,7 @@ import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; +import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager; import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils; import org.sleuthkit.autopsy.imagegallery.gui.StatusBar; import org.sleuthkit.autopsy.imagegallery.gui.SummaryTablePane; @@ -165,7 +166,9 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl GuiUtils.setDialogIcons(d); Optional dataSourceName = d.showAndWait(); - dataSourceName.map(dataSourceNames::get).ifPresent(ds -> controller.getGroupManager().setDataSource(ds)); + DataSource ds = dataSourceName.map(dataSourceNames::get).orElse(null); + GroupManager groupManager = controller.getGroupManager(); + groupManager.regroup(ds,groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true ); SwingUtilities.invokeLater(() -> { tc.open(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 37149d3243..7e246ae091 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -479,7 +479,7 @@ public class GroupManager { return dataSource; } - public void setDataSource(DataSource dataSource) { + void setDataSource(DataSource dataSource) { this.dataSource = dataSource; Platform.runLater(() -> dataSourceProp.set(dataSource)); } From cc59b5c046d755e2c7c961c5e2981241438106a5 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 29 Aug 2018 12:30:08 +0200 Subject: [PATCH 43/84] don't show regrouping confirmation when already showing all data sources --- .../sleuthkit/autopsy/imagegallery/gui/Toolbar.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 7317d10c33..f7831e9c95 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -124,10 +124,7 @@ public class Toolbar extends ToolBar { private final InvalidationListener queryInvalidationListener = new InvalidationListener() { @Override public void invalidated(Observable invalidated) { - Optional selectedDataSource - = defaultIfNull(dataSourceSelectionModel.getSelectedItem(), Optional.empty()); - - controller.getGroupManager().regroup(selectedDataSource.orElse(null), + controller.getGroupManager().regroup(getSelectedDataSource(), groupByBox.getSelectionModel().getSelectedItem(), sortChooser.getComparator(), sortChooser.getSortOrder(), @@ -181,7 +178,8 @@ public class Toolbar extends ToolBar { groupByBox.setButtonCell(new AttributeListCell()); groupByBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { if (oldValue == DrawableAttribute.PATH - && newValue != DrawableAttribute.PATH) { + && newValue != DrawableAttribute.PATH + && getSelectedDataSource() != null) { Alert alert = new Alert(Alert.AlertType.CONFIRMATION, Bundle.Toolbar_nonPathGroupingWarning_content()); alert.setHeaderText(Bundle.Toolbar_nonPathGroupingWarning_header()); @@ -236,6 +234,11 @@ public class Toolbar extends ToolBar { } + private DataSource getSelectedDataSource() { + Optional empty = Optional.empty(); + return defaultIfNull(dataSourceSelectionModel.getSelectedItem(), empty).orElse(null); + } + private void initDataSourceComboBox() { dataSourceComboBox.setCellFactory(param -> new DataSourceCell()); dataSourceComboBox.setButtonCell(new DataSourceCell()); From 15b36b234933bfa51f86cfe0b913211584b93895 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 29 Aug 2018 12:38:36 +0200 Subject: [PATCH 44/84] remove dead code --- .../autopsy/imagegallery/ImageGalleryController.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 336ab8ed3d..236afcd170 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -239,13 +239,6 @@ public final class ImageGalleryController { } }); -// groupManager.getUnSeenGroups().addListener((Observable observable) -> { -// //if there are unseen groups and none being viewed -// if (groupManager.getUnSeenGroups().isEmpty() == false && (getViewState() == null || getViewState().getGroup() == null)) { -// advance(GroupViewState.tile(groupManager.getUnSeenGroups().get(0)), true); -// } -// }); - viewState().addListener((Observable observable) -> { //when the viewed group changes, clear the selection and the undo/redo history selectionModel.clearSelection(); From b838c17c11482ed4d700630b456cc55bcc5e9a4c Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 30 Aug 2018 14:02:09 +0200 Subject: [PATCH 45/84] codacy fixes --- CoreLibs/nbproject/project.xml | 1 + .../autopsy/imagegallery/ImageGalleryController.java | 4 +--- .../imagegallery/actions/CategorizeGroupAction.java | 2 +- .../autopsy/imagegallery/actions/NextUnseenGroup.java | 3 +-- .../autopsy/imagegallery/actions/OpenAction.java | 6 ------ .../autopsy/imagegallery/actions/TagGroupAction.java | 2 +- .../autopsy/imagegallery/datamodel/DrawableDB.java | 8 ++++---- .../imagegallery/datamodel/grouping/GroupKey.java | 5 +---- .../imagegallery/datamodel/grouping/GroupManager.java | 10 +++------- .../sleuthkit/autopsy/imagegallery/gui/Toolbar.java | 2 -- .../autopsy/imagegallery/gui/navpanel/GroupTree.java | 3 +-- .../autopsy/imagegallery/gui/navpanel/NavPanel.java | 2 +- 12 files changed, 15 insertions(+), 33 deletions(-) diff --git a/CoreLibs/nbproject/project.xml b/CoreLibs/nbproject/project.xml index b2aa57c54e..38da548a38 100644 --- a/CoreLibs/nbproject/project.xml +++ b/CoreLibs/nbproject/project.xml @@ -230,6 +230,7 @@ org.apache.commons.codec.digest org.apache.commons.codec.language org.apache.commons.codec.net + org.apache.commons.collections4 org.apache.commons.csv org.apache.commons.io org.apache.commons.io.comparator diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 236afcd170..add5980574 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -161,7 +161,7 @@ public final class ImageGalleryController { return thumbnailSize.getReadOnlyProperty(); } - private GroupViewState getViewState() { + public GroupViewState getViewState() { return historyManager.getCurrentState(); } @@ -956,8 +956,6 @@ public final class ImageGalleryController { @NbBundle.Messages({"PrePopulateDataSourceFiles.committingDb.status=committing image/video database"}) static private class PrePopulateDataSourceFiles extends BulkTransferTask { - private static final Logger LOGGER = Logger.getLogger(PrePopulateDataSourceFiles.class.getName()); - /** * * @param dataSourceId Data source object ID diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java index f568fbd105..be2d9417c3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java @@ -52,7 +52,7 @@ public class CategorizeGroupAction extends CategorizeAction { public CategorizeGroupAction(DhsImageCategory newCat, ImageGalleryController controller) { super(controller, newCat, null); setEventHandler(actionEvent -> { - ObservableList fileIDs = controller.viewState().get().getGroup().getFileIDs(); + ObservableList fileIDs = controller.getViewState().getGroup().getFileIDs(); if (ImageGalleryPreferences.isGroupCategorizationWarningDisabled()) { //if they have preveiously disabled the warning, just go ahead and apply categories. diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index d2b983b693..ae5655c258 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -64,8 +64,7 @@ public class NextUnseenGroup extends Action { setEventHandler(event -> { //on fx-thread //if there is a group assigned to the view, mark it as seen - Optional.ofNullable(controller.viewState()) - .map(ObservableValue::getValue) + Optional.ofNullable(controller.getViewState()) .map(GroupViewState::getGroup) .ifPresent(group -> { groupManager.setGroupSeen(group, true) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java index 418b62f30d..cfd56a0874 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java @@ -21,16 +21,11 @@ package org.sleuthkit.autopsy.imagegallery.actions; import java.awt.Component; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.io.IOException; -import java.net.URL; import java.util.logging.Level; import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.ButtonType; -import javafx.scene.control.Dialog; -import javafx.scene.image.Image; import javafx.stage.Modality; -import javafx.stage.Stage; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JMenuItem; @@ -49,7 +44,6 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.core.Installer; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule; import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagGroupAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagGroupAction.java index afd6a7dcfc..a9229c0315 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagGroupAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagGroupAction.java @@ -30,7 +30,7 @@ public class TagGroupAction extends AddTagAction { public TagGroupAction(final TagName tagName, ImageGalleryController controller) { super(controller, tagName, null); setEventHandler(actionEvent -> - new AddTagAction(controller, tagName, ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs())). + new AddTagAction(controller, tagName, ImmutableSet.copyOf(controller.getViewState().getGroup().getFileIDs())). handle(actionEvent) ); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 7f84fbdfb7..a0430bd45c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.imagegallery.datamodel; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; -import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -51,6 +50,7 @@ import javax.swing.SortOrder; import org.apache.commons.lang3.StringUtils; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.imagegallery.FileTypeUtils; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule; @@ -61,11 +61,11 @@ import static org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy. import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback; import org.sleuthkit.datamodel.DataSource; +import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; @@ -83,7 +83,7 @@ import org.sqlite.SQLiteJDBCLoader; */ public final class DrawableDB { - private static final org.sleuthkit.autopsy.coreutils.Logger logger = Logger.getLogger(DrawableDB.class.getName()); + private static final Logger logger = Logger.getLogger(DrawableDB.class.getName()); //column name constants////////////////////// private static final String ANALYZED = "analyzed"; //NON-NLS diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java index 9c5140f4d2..8b9cb845d1 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupKey.java @@ -98,10 +98,7 @@ public class GroupKey> implements Comparable if (!Objects.equals(this.attr, other.attr)) { return false; } - if (!Objects.equals(this.dataSource, other.dataSource)) { - return false; - } - return true; + return Objects.equals(this.dataSource, other.dataSource); } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 7e246ae091..4143c09b42 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -21,14 +21,12 @@ package org.sleuthkit.autopsy.imagegallery.datamodel.grouping; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.eventbus.Subscribe; -import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -42,9 +40,6 @@ import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Set; import java.util.TreeSet; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.regex.Pattern; @@ -67,6 +62,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import javax.swing.SortOrder; +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; @@ -508,7 +504,7 @@ public class GroupManager { //only re-query the db if the data source or group by attribute changed or it is forced if (dataSource != getDataSource() || groupBy != getGroupBy() - || force == true) { + || force) { setDataSource(dataSource); setGroupBy(groupBy); @@ -784,7 +780,7 @@ public class GroupManager { groupProgress.finish(); groupProgress = null; } - if (unSeenGroups.isEmpty() == false) { + if (isNotEmpty(unSeenGroups)) { controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index f7831e9c95..bcdc0ef416 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -31,7 +31,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.Executors; -import java.util.function.Consumer; import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.InvalidationListener; @@ -59,7 +58,6 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.scene.text.Text; import javafx.stage.Modality; -import javafx.util.Duration; import javafx.util.StringConverter; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; import org.controlsfx.control.Notifications; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java index 31b8a57871..d8320dfe6e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2016 Basis Technology Corp. + * Copyright 2016-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,6 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Function; -import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.collections.ListChangeListener; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java index 037d3c3344..e3be8953ab 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java @@ -95,7 +95,7 @@ abstract class NavPanel extends Tab { //keep selection in sync with controller controller.viewState().addListener(observable -> { Platform.runLater(() -> { - Optional.ofNullable(controller.viewState().get()) + Optional.ofNullable(controller.getViewState()) .map(GroupViewState::getGroup) .ifPresent(this::setFocusedGroup); }); From 6716216e0b7de2ded72c7a226910bd0efb755cd2 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 30 Aug 2018 14:22:00 +0200 Subject: [PATCH 46/84] cleanup DrawableTagsManager --- .../datamodel/DrawableTagsManager.java | 61 ++++++++----------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java index cb30c37bab..23b684ac34 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2013-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,12 +18,11 @@ */ package org.sleuthkit.autopsy.imagegallery.datamodel; -import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import com.google.common.eventbus.AsyncEventBus; import com.google.common.eventbus.EventBus; import java.util.Collections; import java.util.List; -import java.util.Objects; +import static java.util.Objects.isNull; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.stream.Collectors; @@ -37,6 +36,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TagName; @@ -50,10 +50,10 @@ import org.sleuthkit.datamodel.TskCoreException; "DrawableTagsManager.bookMark=Bookmark"}) public class DrawableTagsManager { - private static final Logger LOGGER = Logger.getLogger(DrawableTagsManager.class.getName()); + private static final Logger logger = Logger.getLogger(DrawableTagsManager.class.getName()); - private static Image FOLLOW_UP_IMAGE; - private static Image BOOKMARK_IMAGE; + private static final Image FOLLOW_UP_IMAGE = new Image("/org/sleuthkit/autopsy/imagegallery/images/flag_red.png"); + private static final Image BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png"); final private Object autopsyTagsManagerLock = new Object(); private TagsManager autopsyTagsManager; @@ -63,9 +63,11 @@ public class DrawableTagsManager { */ private final EventBus tagsEventBus = new AsyncEventBus( Executors.newSingleThreadExecutor( - new BasicThreadFactory.Builder().namingPattern("Tags Event Bus").uncaughtExceptionHandler((Thread t, Throwable e) -> { //NON-NLS - LOGGER.log(Level.SEVERE, "uncaught exception in event bus handler", e); //NON-NLS - }).build() + new BasicThreadFactory.Builder() + .namingPattern("Tags Event Bus")//NON-NLS + .uncaughtExceptionHandler((Thread thread, Throwable throwable) + -> logger.log(Level.SEVERE, "uncaught exception in event bus handler", throwable))//NON-NLS + .build() )); /** @@ -137,8 +139,8 @@ public class DrawableTagsManager { */ public TagName getFollowUpTagName() throws TskCoreException { synchronized (autopsyTagsManagerLock) { - if (Objects.isNull(followUpTagName)) { - followUpTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.followUp")); + if (isNull(followUpTagName)) { + followUpTagName = getTagName(Bundle.DrawableTagsManager_followUp()); } return followUpTagName; } @@ -146,14 +148,13 @@ public class DrawableTagsManager { private Object getBookmarkTagName() throws TskCoreException { synchronized (autopsyTagsManagerLock) { - if (Objects.isNull(bookmarkTagName)) { - bookmarkTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.bookMark")); + if (isNull(bookmarkTagName)) { + bookmarkTagName = getTagName(Bundle.DrawableTagsManager_bookMark()); } return bookmarkTagName; } } - /** * get all the TagNames that are not categories * @@ -170,7 +171,7 @@ public class DrawableTagsManager { .distinct().sorted() .collect(Collectors.toList()); } catch (TskCoreException | IllegalStateException ex) { - LOGGER.log(Level.WARNING, "couldn't access case", ex); //NON-NLS + logger.log(Level.WARNING, "couldn't access case", ex); //NON-NLS } return Collections.emptyList(); } @@ -223,7 +224,7 @@ public class DrawableTagsManager { throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex); } } catch (NullPointerException | IllegalStateException ex) { - LOGGER.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS + logger.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS throw new TskCoreException("Case was closed out from underneath", ex); } } @@ -243,9 +244,9 @@ public class DrawableTagsManager { } } - public List getContentTagsByTagName(TagName t) throws TskCoreException { + public List getContentTagsByTagName(TagName tagName) throws TskCoreException { synchronized (autopsyTagsManagerLock) { - return autopsyTagsManager.getContentTagsByTagName(t); + return autopsyTagsManager.getContentTagsByTagName(tagName); } } @@ -261,37 +262,23 @@ public class DrawableTagsManager { } } - public void deleteContentTag(ContentTag ct) throws TskCoreException { + public void deleteContentTag(ContentTag contentTag) throws TskCoreException { synchronized (autopsyTagsManagerLock) { - autopsyTagsManager.deleteContentTag(ct); + autopsyTagsManager.deleteContentTag(contentTag); } } public Node getGraphic(TagName tagname) { try { if (tagname.equals(getFollowUpTagName())) { - return new ImageView(getFollowUpImage()); + return new ImageView(FOLLOW_UP_IMAGE); } else if (tagname.equals(getBookmarkTagName())) { - return new ImageView(getBookmarkImage()); + return new ImageView(BOOKMARK_IMAGE); } } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to get \"Follow Up\" or \"Bookmark\"tag name from db.", ex); + logger.log(Level.SEVERE, "Failed to get \"Follow Up\" or \"Bookmark\"tag name from db.", ex); } return DrawableAttribute.TAGS.getGraphicForValue(tagname); } - synchronized private static Image getFollowUpImage() { - if (FOLLOW_UP_IMAGE == null) { - FOLLOW_UP_IMAGE = new Image("/org/sleuthkit/autopsy/imagegallery/images/flag_red.png"); - } - return FOLLOW_UP_IMAGE; - } - - synchronized private static Image getBookmarkImage() { - if (BOOKMARK_IMAGE == null) { - BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png"); - } - return BOOKMARK_IMAGE; - } - } From 19a6a60289cbd73f3428fd0c124fde8c6412ee29 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 30 Aug 2018 18:59:25 +0200 Subject: [PATCH 47/84] cleanup focusing on GroupManager and thread safety --- .../imagegallery/ImageGalleryController.java | 98 ++-- .../imagegallery/actions/NextUnseenGroup.java | 42 +- .../datamodel/grouping/GroupManager.java | 452 +++++++++--------- .../autopsy/imagegallery/gui/Toolbar.java | 6 +- .../imagegallery/gui/navpanel/GroupTree.java | 6 +- .../gui/navpanel/GroupTreeItem.java | 5 +- 6 files changed, 303 insertions(+), 306 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index add5980574..bbc7f1e5e5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -232,12 +232,7 @@ public final class ImageGalleryController { } }); - groupManager.getAnalyzedGroups().addListener((Observable o) -> { - //analyzed groups is confined to JFX thread - if (Case.isCaseOpen()) { - checkForGroups(); - } - }); + groupManager.getAnalyzedGroups().addListener((Observable o) -> checkForGroups()); viewState().addListener((Observable observable) -> { //when the viewed group changes, clear the selection and the undo/redo history @@ -291,7 +286,6 @@ public final class ImageGalleryController { * GroupManager and remove blocking progress spinners if there are. If there * aren't, add a blocking progress spinner with appropriate message. */ - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) @NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. " + " No groups will be available until ingest is finished and listening is re-enabled.", "ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.", @@ -303,45 +297,46 @@ public final class ImageGalleryController { + " the current Group By setting resulted in no groups, " + "or no groups are fully analyzed but ingest is not running."}) synchronized private void checkForGroups() { - if (groupManager.getAnalyzedGroups().isEmpty()) { - if (IngestManager.getInstance().isIngestRunning()) { - if (listeningEnabled.not().get()) { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); - } else { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), - new ProgressIndicator())); - } - - } else if (dbTaskQueueSize.get() > 0) { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), - new ProgressIndicator())); - } else if (db != null) { - try { - if (db.countAllFiles() <= 0) { - - // there are no files in db - if (listeningEnabled.not().get()) { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4())); - } else { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5())); - } + if (Case.isCaseOpen()) { + if (groupManager.getAnalyzedGroups().isEmpty()) { + if (IngestManager.getInstance().isIngestRunning()) { + if (listeningEnabled.get()) { + replaceNotification(fullUIStackPane, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), + new ProgressIndicator())); + } else { + replaceNotification(fullUIStackPane, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error counting files in drawable db.", ex); + + } else if (dbTaskQueueSize.get() > 0) { + replaceNotification(fullUIStackPane, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), + new ProgressIndicator())); + } else if (db != null) { + try { + if (db.countAllFiles() <= 0) { + // there are no files in db + if (listeningEnabled.get()) { + replaceNotification(fullUIStackPane, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5())); + } else { + replaceNotification(fullUIStackPane, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4())); + } + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error counting files in drawable db.", ex); + } + + } else if (false == groupManager.isRegrouping()) { + replaceNotification(centralStackPane, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6())); } - } else if (!groupManager.isRegrouping()) { - replaceNotification(centralStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6())); + } else { + Platform.runLater(this::clearNotification); } - - } else { - clearNotification(); } } @@ -357,14 +352,15 @@ public final class ImageGalleryController { } } - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void replaceNotification(StackPane stackPane, Node newNode) { - clearNotification(); + Platform.runLater(() -> { + clearNotification(); - infoOverlay = new StackPane(infoOverLayBackground, newNode); - if (stackPane != null) { - stackPane.getChildren().add(infoOverlay); - } + infoOverlay = new StackPane(infoOverLayBackground, newNode); + if (stackPane != null) { + stackPane.getChildren().add(infoOverlay); + } + }); } /** @@ -385,7 +381,7 @@ public final class ImageGalleryController { // if we add this line icons are made as files are analyzed rather than on demand. // db.addUpdatedFileListener(IconCache.getDefault()); historyManager.clear(); - groupManager.setDB(db); + groupManager.reset(); hashSetManager.setDb(db); categoryManager.setDb(db); tagsManager.setAutopsyTagsManager(theNewCase.getServices().getTagsManager()); @@ -416,7 +412,7 @@ public final class ImageGalleryController { setListeningEnabled(false); ThumbnailCache.getDefault().clearCache(); historyManager.clear(); - groupManager.clear(); + groupManager.reset(); tagsManager.clearFollowUpTagName(); tagsManager.unregisterListener(groupManager); tagsManager.unregisterListener(categoryManager); @@ -850,8 +846,8 @@ public final class ImageGalleryController { updateProgress(1.0); progressHandle.start(); - taskDB.commitTransaction(drawableDbTransaction, true); caseDbTransaction.commit(); + taskDB.commitTransaction(drawableDbTransaction, true); } catch (TskCoreException ex) { if (null != drawableDbTransaction) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index ae5655c258..6f947d7d9c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -18,8 +18,10 @@ */ package org.sleuthkit.autopsy.imagegallery.actions; +import com.google.common.util.concurrent.MoreExecutors; import java.util.Optional; import javafx.application.Platform; +import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; @@ -52,15 +54,19 @@ public class NextUnseenGroup extends Action { private final ImageGalleryController controller; private final ObservableList unSeenGroups; + private final GroupManager groupManager; public NextUnseenGroup(ImageGalleryController controller) { super(NEXT_UNSEEN_GROUP); setGraphic(new ImageView(ADVANCE_IMAGE)); this.controller = controller; - GroupManager groupManager = controller.getGroupManager(); + groupManager = controller.getGroupManager(); unSeenGroups = groupManager.getUnSeenGroups(); - unSeenGroups.addListener((Observable observable) -> this.updateButton()); + unSeenGroups.addListener((Observable observable) -> { + updateButton(); + + }); setEventHandler(event -> { //on fx-thread //if there is a group assigned to the view, mark it as seen @@ -68,29 +74,33 @@ public class NextUnseenGroup extends Action { .map(GroupViewState::getGroup) .ifPresent(group -> { groupManager.setGroupSeen(group, true) - .addListener(this::advanceToNextUnseenGroup, Platform::runLater); + .addListener(this::advanceToNextUnseenGroup, MoreExecutors.newDirectExecutorService()); }); }); updateButton(); } - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void advanceToNextUnseenGroup() { - if (unSeenGroups.isEmpty() == false) { - controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true); + synchronized (groupManager) { + if (unSeenGroups.isEmpty() == false) { + controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true); + } + + updateButton(); } - updateButton(); } - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void updateButton() { - setDisabled(unSeenGroups.isEmpty()); - if (unSeenGroups.size() <= 1) { - setText(MARK_GROUP_SEEN); - setGraphic(new ImageView(END_IMAGE)); - } else { - setText(NEXT_UNSEEN_GROUP); - setGraphic(new ImageView(ADVANCE_IMAGE)); - } + int size = unSeenGroups.size(); + Platform.runLater(() -> { + setDisabled(size == 0); + if (size <= 1) { + setText(MARK_GROUP_SEEN); + setGraphic(new ImageView(END_IMAGE)); + } else { + setText(NEXT_UNSEEN_GROUP); + setGraphic(new ImageView(ADVANCE_IMAGE)); + } + }); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 4143c09b42..39ad92f246 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -40,6 +40,8 @@ import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.regex.Pattern; @@ -74,8 +76,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.coreutils.LoggedTask; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.ThreadConfined; -import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; @@ -107,6 +107,15 @@ public class GroupManager { private final ImageGalleryController controller; + /** list of all analyzed groups */ + @GuardedBy("this") + private final ObservableList analyzedGroups = FXCollections.observableArrayList(); + private final ObservableList unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups); + + /** list of unseen groups */ + @GuardedBy("this") + private final ObservableList unSeenGroups = FXCollections.observableArrayList(); + private final ObservableList unmodifiableUnSeenGroups = FXCollections.unmodifiableObservableList(unSeenGroups); /** * map from GroupKey} to DrawableGroupSs. All groups (even not fully * analyzed or not visible groups could be in this map @@ -114,38 +123,21 @@ public class GroupManager { @GuardedBy("this") private final Map, DrawableGroup> groupMap = new HashMap<>(); - /** list of all analyzed groups */ - @ThreadConfined(type = ThreadType.JFX) - private final ObservableList analyzedGroups = FXCollections.observableArrayList(); - private final ObservableList unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups); - - /** list of unseen groups */ - @ThreadConfined(type = ThreadType.JFX) - private final ObservableList unSeenGroups = FXCollections.observableArrayList(); - private final ObservableList unmodifiableUnSeenGroups = FXCollections.unmodifiableObservableList(unSeenGroups); - + @GuardedBy("this") private ReGroupTask groupByTask; /* * --- current grouping/sorting attributes --- */ - private volatile GroupSortBy sortBy = GroupSortBy.PRIORITY; - private volatile DrawableAttribute groupBy = DrawableAttribute.PATH; - private volatile SortOrder sortOrder = SortOrder.ASCENDING; - private volatile DataSource dataSource = null; //null indicates all datasources - - private final ReadOnlyObjectWrapper< Comparator> sortByProp = new ReadOnlyObjectWrapper<>(sortBy); - private final ReadOnlyObjectWrapper< DrawableAttribute> groupByProp = new ReadOnlyObjectWrapper<>(groupBy); - private final ReadOnlyObjectWrapper sortOrderProp = new ReadOnlyObjectWrapper<>(sortOrder); - private final ReadOnlyObjectWrapper dataSourceProp = new ReadOnlyObjectWrapper<>(dataSource); + @GuardedBy("this") + private final ReadOnlyObjectWrapper< GroupSortBy> sortByProp = new ReadOnlyObjectWrapper<>(GroupSortBy.PRIORITY); + private final ReadOnlyObjectWrapper< DrawableAttribute> groupByProp = new ReadOnlyObjectWrapper<>(DrawableAttribute.PATH); + private final ReadOnlyObjectWrapper sortOrderProp = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING); + private final ReadOnlyObjectWrapper dataSourceProp = new ReadOnlyObjectWrapper<>(null);//null indicates all datasources private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper(); - public void setDB(DrawableDB db) { - regroup(dataSource, groupBy, sortBy, sortOrder, true); - } - - DrawableDB getDB() { + synchronized DrawableDB getDB() { return controller.getDatabase(); } @@ -154,7 +146,6 @@ public class GroupManager { return unmodifiableAnalyzedGroups; } - @ThreadConfined(type = ThreadType.JFX) @SuppressWarnings("ReturnOfCollectionOrArrayField") public ObservableList getUnSeenGroups() { return unmodifiableUnSeenGroups; @@ -182,26 +173,26 @@ public class GroupManager { @SuppressWarnings({"rawtypes", "unchecked"}) synchronized public Set> getGroupKeysForFile(DrawableFile file) { Set> resultSet = new HashSet<>(); - for (Comparable val : groupBy.getValue(file)) { - if (groupBy == DrawableAttribute.TAGS) { + for (Comparable val : getGroupBy().getValue(file)) { + if (getGroupBy() == DrawableAttribute.TAGS) { if (CategoryManager.isNotCategoryTagName((TagName) val)) { - resultSet.add(new GroupKey(groupBy, val, dataSource)); + resultSet.add(new GroupKey(getGroupBy(), val, getDataSource())); } } else { - resultSet.add(new GroupKey(groupBy, val, dataSource)); + resultSet.add(new GroupKey(getGroupBy(), val, getDataSource())); } } return resultSet; } /** - * Using the current groupBy set for this manager, find groupkeys for all - * the groups the given file is a part of + * Using the current grouping paramaters set for this manager, find + * GroupKeys for all the Groups the given file is a part of. * * @param fileID The Id of the file to get group keys for. * - * @return a a set of {@link GroupKey}s representing the group(s) the given - * file is a part of + * @return A set of GroupKeys representing the group(s) the given file is a + * part of */ synchronized public Set> getGroupKeysForFileID(Long fileID) { try { @@ -225,34 +216,29 @@ public class GroupManager { * or null if no group exists for that key. */ @Nullable - public DrawableGroup getGroupForKey(@Nonnull GroupKey groupKey) { - synchronized (groupMap) { - return groupMap.get(groupKey); - } + synchronized public DrawableGroup getGroupForKey(@Nonnull GroupKey groupKey) { + return groupMap.get(groupKey); } - synchronized public void clear() { - + synchronized public void reset() { if (groupByTask != null) { groupByTask.cancel(true); } - sortBy = GroupSortBy.GROUP_BY_VALUE; - groupBy = DrawableAttribute.PATH; - sortOrder = SortOrder.ASCENDING; - Platform.runLater(() -> { - unSeenGroups.forEach(controller.getCategoryManager()::unregisterListener); - unSeenGroups.clear(); - analyzedGroups.forEach(controller.getCategoryManager()::unregisterListener); - analyzedGroups.clear(); + setSortBy(GroupSortBy.GROUP_BY_VALUE); + setGroupBy(DrawableAttribute.PATH); + setSortOrder(SortOrder.ASCENDING); + setDataSource(null); - }); - synchronized (groupMap) { - groupMap.values().forEach(controller.getCategoryManager()::unregisterListener); - groupMap.clear(); - } + unSeenGroups.forEach(controller.getCategoryManager()::unregisterListener); + unSeenGroups.clear(); + analyzedGroups.forEach(controller.getCategoryManager()::unregisterListener); + analyzedGroups.clear(); + + groupMap.values().forEach(controller.getCategoryManager()::unregisterListener); + groupMap.clear(); } - public boolean isRegrouping() { + synchronized public boolean isRegrouping() { if (groupByTask == null) { return false; } @@ -264,7 +250,6 @@ public class GroupManager { return true; case CANCELLED: case FAILED: - case SUCCEEDED: default: return false; @@ -280,14 +265,15 @@ public class GroupManager { * @return A ListenableFuture that encapsulates saving the seen state to the * DB. */ - public ListenableFuture setGroupSeen(DrawableGroup group, boolean seen) { + synchronized public ListenableFuture setGroupSeen(DrawableGroup group, boolean seen) { DrawableDB db = getDB(); if (nonNull(db)) { return exec.submit(() -> { try { + db.setGroupSeen(group.getGroupKey(), seen); group.setSeen(seen); - Platform.runLater(() -> updateUnSeenGroups(group, seen)); + updateUnSeenGroups(group, seen); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS } @@ -297,14 +283,13 @@ public class GroupManager { return Futures.immediateFuture(null); } - @ThreadConfined(type = ThreadType.JFX) - private void updateUnSeenGroups(DrawableGroup group, boolean seen) { + synchronized private void updateUnSeenGroups(DrawableGroup group, boolean seen) { if (seen) { unSeenGroups.removeAll(group); } else if (unSeenGroups.contains(group) == false) { unSeenGroups.add(group); } - FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy)); + sortUnseenGroups(); } /** @@ -315,39 +300,48 @@ public class GroupManager { * @param groupKey the value of groupKey * @param fileID the value of file * + * @return The DrawableGroup the file was removed from. + * */ public synchronized DrawableGroup removeFromGroup(GroupKey groupKey, final Long fileID) { //get grouping this file would be in final DrawableGroup group = getGroupForKey(groupKey); if (group != null) { - Platform.runLater(() -> { + synchronized (group) { group.removeFile(fileID); - }); - // If we're grouping by category, we don't want to remove empty groups. - if (groupKey.getAttribute() != DrawableAttribute.CATEGORY) { - if (group.getFileIDs().isEmpty()) { - Platform.runLater(() -> { + // If we're grouping by category, we don't want to remove empty groups. + if (groupKey.getAttribute() != DrawableAttribute.CATEGORY) { + if (group.getFileIDs().isEmpty()) { if (analyzedGroups.contains(group)) { analyzedGroups.remove(group); - FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy)); + sortAnalyzedGroups(); } if (unSeenGroups.contains(group)) { unSeenGroups.remove(group); - FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy)); + sortUnseenGroups(); } - }); + + } } - } else { //group == null - // It may be that this was the last unanalyzed file in the group, so test - // whether the group is now fully analyzed. - popuplateIfAnalyzed(groupKey, null); + return group; } + } else { //group == null + // It may be that this was the last unanalyzed file in the group, so test + // whether the group is now fully analyzed. + return popuplateIfAnalyzed(groupKey, null); } - return group; } - public Set getFileIDsInGroup(GroupKey groupKey) throws TskCoreException { + synchronized private void sortUnseenGroups() { + FXCollections.sort(unSeenGroups, makeGroupComparator(getSortOrder(), getSortBy())); + } + + synchronized private void sortAnalyzedGroups() { + FXCollections.sort(analyzedGroups, makeGroupComparator(getSortOrder(), getSortBy())); + } + + synchronized public Set getFileIDsInGroup(GroupKey groupKey) throws TskCoreException { Set fileIDsToReturn = Collections.emptySet(); switch (groupKey.getAttribute().attrName) { //these cases get special treatment @@ -374,14 +368,15 @@ public class GroupManager { // @@@ This was kind of slow in the profiler. Maybe we should cache it. // Unless the list of file IDs is necessary, use countFilesWithCategory() to get the counts. - public Set getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException { + synchronized public Set getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException { Set fileIDsToReturn = Collections.emptySet(); DrawableDB db = getDB(); if (nonNull(db)) { try { final DrawableTagsManager tagsManager = controller.getTagsManager(); if (category == DhsImageCategory.ZERO) { - List< TagName> tns = Stream.of(DhsImageCategory.ONE, DhsImageCategory.TWO, DhsImageCategory.THREE, DhsImageCategory.FOUR, DhsImageCategory.FIVE) + List< TagName> tns = Stream.of(DhsImageCategory.ONE, DhsImageCategory.TWO, + DhsImageCategory.THREE, DhsImageCategory.FOUR, DhsImageCategory.FIVE) .map(tagsManager::getTagName) .collect(Collectors.toList()); @@ -415,9 +410,10 @@ public class GroupManager { return fileIDsToReturn; } - public Set getFileIDsWithTag(TagName tagName) throws TskCoreException { + synchronized public Set getFileIDsWithTag(TagName tagName) throws TskCoreException { + Set files = new HashSet<>(); try { - Set files = new HashSet<>(); + List contentTags = controller.getTagsManager().getContentTagsByTagName(tagName); DrawableDB db = getDB(); for (ContentTag ct : contentTags) { @@ -432,52 +428,48 @@ public class GroupManager { } } - public GroupSortBy getSortBy() { - return sortBy; + public synchronized GroupSortBy getSortBy() { + return sortByProp.get(); } - void setSortBy(GroupSortBy sortBy) { - this.sortBy = sortBy; - Platform.runLater(() -> sortByProp.set(sortBy)); + synchronized void setSortBy(GroupSortBy sortBy) { + sortByProp.set(sortBy); } - public ReadOnlyObjectProperty< Comparator> getSortByProperty() { + public ReadOnlyObjectProperty< GroupSortBy> getSortByProperty() { return sortByProp.getReadOnlyProperty(); } - public DrawableAttribute getGroupBy() { - return groupBy; + public synchronized DrawableAttribute getGroupBy() { + return groupByProp.get(); } - void setGroupBy(DrawableAttribute groupBy) { - this.groupBy = groupBy; - Platform.runLater(() -> groupByProp.set(groupBy)); + synchronized void setGroupBy(DrawableAttribute groupBy) { + groupByProp.set(groupBy); } public ReadOnlyObjectProperty> getGroupByProperty() { return groupByProp.getReadOnlyProperty(); } - public SortOrder getSortOrder() { - return sortOrder; + public synchronized SortOrder getSortOrder() { + return sortOrderProp.get(); } - void setSortOrder(SortOrder sortOrder) { - this.sortOrder = sortOrder; - Platform.runLater(() -> sortOrderProp.set(sortOrder)); + synchronized void setSortOrder(SortOrder sortOrder) { + sortOrderProp.set(sortOrder); } public ReadOnlyObjectProperty getSortOrderProperty() { return sortOrderProp.getReadOnlyProperty(); } - public DataSource getDataSource() { - return dataSource; + public synchronized DataSource getDataSource() { + return dataSourceProp.get(); } - void setDataSource(DataSource dataSource) { - this.dataSource = dataSource; - Platform.runLater(() -> dataSourceProp.set(dataSource)); + synchronized void setDataSource(DataSource dataSource) { + dataSourceProp.set(dataSource); } public ReadOnlyObjectProperty getDataSourceProperty() { @@ -485,17 +477,17 @@ public class GroupManager { } /** - * Regroup all files in the database using given {@link DrawableAttribute} - * see {@link ReGroupTask} for more details. + * Regroup all files in the database. see ReGroupTask for more details. * * @param The type of the values of the groupBy attriubte. * @param dataSource The DataSource to show. Null for all data sources. - * @param groupBy - * @param sortBy - * @param sortOrder - * @param force true to force a full db query regroup + * @param groupBy The DrawableAttribute to group by + * @param sortBy The GroupSortBy to sort the groups by + * @param sortOrder The SortOrder to use when sorting the groups. + * @param force true to force a full db query regroup, even if only the + * sorting has changed. */ - public synchronized > void regroup(DataSource dataSource, final DrawableAttribute groupBy, final GroupSortBy sortBy, final SortOrder sortOrder, Boolean force) { + public synchronized > void regroup(DataSource dataSource, DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder, Boolean force) { if (!Case.isCaseOpen()) { return; @@ -522,8 +514,8 @@ public class GroupManager { setSortBy(sortBy); setSortOrder(sortOrder); Platform.runLater(() -> { - FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy)); - FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy)); + FXCollections.sort(analyzedGroups, makeGroupComparator(sortOrder, sortBy)); + FXCollections.sort(unSeenGroups, makeGroupComparator(sortOrder, sortBy)); }); } } @@ -533,18 +525,18 @@ public class GroupManager { } @Subscribe - public void handleTagAdded(ContentTagAddedEvent evt) { + synchronized public void handleTagAdded(ContentTagAddedEvent evt) { GroupKey newGroupKey = null; final long fileID = evt.getAddedTag().getContent().getId(); - if (groupBy == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(evt.getAddedTag().getName())) { - newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(evt.getAddedTag().getName()), dataSource); + if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(evt.getAddedTag().getName())) { + newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(evt.getAddedTag().getName()), getDataSource()); for (GroupKey oldGroupKey : groupMap.keySet()) { if (oldGroupKey.equals(newGroupKey) == false) { removeFromGroup(oldGroupKey, fileID); } } - } else if (groupBy == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(evt.getAddedTag().getName())) { - newGroupKey = new GroupKey<>(DrawableAttribute.TAGS, evt.getAddedTag().getName(), dataSource); + } else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(evt.getAddedTag().getName())) { + newGroupKey = new GroupKey<>(DrawableAttribute.TAGS, evt.getAddedTag().getName(), getDataSource()); } if (newGroupKey != null) { DrawableGroup g = getGroupForKey(newGroupKey); @@ -553,7 +545,7 @@ public class GroupManager { } @SuppressWarnings("AssignmentToMethodParameter") - private void addFileToGroup(DrawableGroup g, final GroupKey groupKey, final long fileID) { + synchronized private void addFileToGroup(DrawableGroup g, final GroupKey groupKey, final long fileID) { if (g == null) { //if there wasn't already a group check if there should be one now g = popuplateIfAnalyzed(groupKey, null); @@ -561,19 +553,19 @@ public class GroupManager { DrawableGroup group = g; if (group != null) { //if there is aleady a group that was previously deemed fully analyzed, then add this newly analyzed file to it. - Platform.runLater(() -> group.addFile(fileID)); + group.addFile(fileID); } } @Subscribe - public void handleTagDeleted(ContentTagDeletedEvent evt) { + synchronized public void handleTagDeleted(ContentTagDeletedEvent evt) { GroupKey groupKey = null; final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo(); final TagName tagName = deletedTagInfo.getName(); - if (groupBy == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(tagName)) { - groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(tagName), dataSource); - } else if (groupBy == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(tagName)) { - groupKey = new GroupKey<>(DrawableAttribute.TAGS, tagName, dataSource); + if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(tagName)) { + groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(tagName), getDataSource()); + } else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(tagName)) { + groupKey = new GroupKey<>(DrawableAttribute.TAGS, tagName, getDataSource()); } if (groupKey != null) { final long fileID = deletedTagInfo.getContentID(); @@ -622,7 +614,7 @@ public class GroupManager { controller.getCategoryManager().fireChange(updatedFileIDs, null); } - private DrawableGroup popuplateIfAnalyzed(GroupKey groupKey, ReGroupTask task) { + synchronized private DrawableGroup popuplateIfAnalyzed(GroupKey groupKey, ReGroupTask task) { /* * If this method call is part of a ReGroupTask and that task is * cancelled, no-op. @@ -644,30 +636,24 @@ public class GroupManager { if (Objects.nonNull(fileIDs)) { DrawableGroup group; final boolean groupSeen = db.isGroupSeen(groupKey); - synchronized (groupMap) { - if (groupMap.containsKey(groupKey)) { - group = groupMap.get(groupKey); - group.setFiles(ObjectUtils.defaultIfNull(fileIDs, Collections.emptySet())); - group.setSeen(groupSeen); - } else { - group = new DrawableGroup(groupKey, fileIDs, groupSeen); - controller.getCategoryManager().registerListener(group); -// group.seenProperty().addListener((o, oldSeen, newSeen) -// -> saveGroupSeen(group, newSeen)); - - groupMap.put(groupKey, group); - } + if (groupMap.containsKey(groupKey)) { + group = groupMap.get(groupKey); + group.setFiles(ObjectUtils.defaultIfNull(fileIDs, Collections.emptySet())); + group.setSeen(groupSeen); + } else { + group = new DrawableGroup(groupKey, fileIDs, groupSeen); + controller.getCategoryManager().registerListener(group); + groupMap.put(groupKey, group); } - Platform.runLater(() -> { - if (analyzedGroups.contains(group) == false) { - analyzedGroups.add(group); - if (isNull(task)) { - FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy)); - } + if (analyzedGroups.contains(group) == false) { + analyzedGroups.add(group); + if (isNull(task)) { + sortAnalyzedGroups(); } - updateUnSeenGroups(group, groupSeen); - }); + } + updateUnSeenGroups(group, groupSeen); + return group; } @@ -680,7 +666,7 @@ public class GroupManager { return null; } - public Set getFileIDsWithMimeType(String mimeType) throws TskCoreException { + synchronized public Set getFileIDsWithMimeType(String mimeType) throws TskCoreException { HashSet hashSet = new HashSet<>(); String query = (null == mimeType) @@ -698,9 +684,7 @@ public class GroupManager { return hashSet; } catch (Exception ex) { - Exceptions.printStackTrace(ex); throw new TskCoreException("Failed to get file ids with mime type " + mimeType, ex); - } } @@ -723,7 +707,7 @@ public class GroupManager { private final GroupSortBy sortBy; private final SortOrder sortOrder; - private ProgressHandle groupProgress; + private final ProgressHandle groupProgress; ReGroupTask(DataSource dataSource, DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder) { super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), true); @@ -731,11 +715,13 @@ public class GroupManager { this.groupBy = groupBy; this.sortBy = sortBy; this.sortOrder = sortOrder; + + groupProgress = ProgressHandle.createHandle(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), this); } @Override public boolean isCancelled() { - return super.isCancelled() || groupBy != getGroupBy() || sortBy != getSortBy() || sortOrder != getSortOrder(); + return super.isCancelled(); } @Override @@ -744,31 +730,33 @@ public class GroupManager { if (isCancelled()) { return null; } + groupProgress.start(); - groupProgress = ProgressHandle.createHandle(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), this); - Platform.runLater(() -> { + synchronized (GroupManager.this) { analyzedGroups.clear(); unSeenGroups.clear(); - }); - // Get the list of group keys - final Multimap valsByDataSource = findValuesForAttribute(); - - groupProgress.start(valsByDataSource.size()); - - int p = 0; - // For each key value, partially create the group and add it to the list. - for (final Map.Entry val : valsByDataSource.entries()) { - if (isCancelled()) { - return null;//abort + // Get the list of group keys + final Multimap valsByDataSource = findValuesForAttribute(); + groupProgress.switchToDeterminate(valsByDataSource.size()); + int p = 0; + // For each key value, partially create the group and add it to the list. + for (final Map.Entry val : valsByDataSource.entries()) { + if (isCancelled()) { + return null; + } + p++; + updateMessage(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val.getValue())); + updateProgress(p, valsByDataSource.size()); + groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); + popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); } - p++; - updateMessage(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val.getValue())); - updateProgress(p, valsByDataSource.size()); - groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); - popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); } + if (isNotEmpty(unSeenGroups)) { + controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true); + } + groupProgress.finish(); updateProgress(1, 1); return null; } @@ -776,12 +764,12 @@ public class GroupManager { @Override protected void done() { super.done(); - if (groupProgress != null) { - groupProgress.finish(); - groupProgress = null; - } - if (isNotEmpty(unSeenGroups)) { - controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true); + try { + get(); + } catch (CancellationException cancelEx) { + //cancellation is normal + } catch (InterruptedException | ExecutionException ex) { + logger.log(Level.SEVERE, "Error while regrouping.", ex); } } @@ -795,73 +783,75 @@ public class GroupManager { * @return */ public Multimap findValuesForAttribute() { - DrawableDB db = getDB(); - Multimap results = HashMultimap.create(); - try { - switch (groupBy.attrName) { - //these cases get special treatment - case CATEGORY: - results.putAll(null, Arrays.asList(DhsImageCategory.values())); - break; - case TAGS: - results.putAll(null, controller.getTagsManager().getTagNamesInUse().stream() - .filter(CategoryManager::isNotCategoryTagName) - .collect(Collectors.toList())); - break; + synchronized (GroupManager.this) { + DrawableDB db = getDB(); + Multimap results = HashMultimap.create(); + try { + switch (groupBy.attrName) { + //these cases get special treatment + case CATEGORY: + results.putAll(null, Arrays.asList(DhsImageCategory.values())); + break; + case TAGS: + results.putAll(null, controller.getTagsManager().getTagNamesInUse().stream() + .filter(CategoryManager::isNotCategoryTagName) + .collect(Collectors.toList())); + break; - case ANALYZED: - results.putAll(null, Arrays.asList(false, true)); - break; - case HASHSET: - if (nonNull(db)) { - results.putAll(null, new TreeSet<>(db.getHashSetNames())); - } - break; - case MIME_TYPE: - if (nonNull(db)) { - HashSet types = new HashSet<>(); - - // Use the group_concat function to get a list of files for each mime type. - // This has different syntax on Postgres vs SQLite - String groupConcatClause; - if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) { - groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; - } else { - groupConcatClause = " group_concat(obj_id) as object_ids"; + case ANALYZED: + results.putAll(null, Arrays.asList(false, true)); + break; + case HASHSET: + if (nonNull(db)) { + results.putAll(null, new TreeSet<>(db.getHashSetNames())); } - String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type "; - try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS - ResultSet resultSet = executeQuery.getResultSet();) { - while (resultSet.next()) { - final String mimeType = resultSet.getString("mime_type"); //NON-NLS - String objIds = resultSet.getString("object_ids"); //NON-NLS + break; + case MIME_TYPE: + if (nonNull(db)) { + HashSet types = new HashSet<>(); - Pattern.compile(",").splitAsStream(objIds) - .map(Long::valueOf) - .filter(db::isInDB) - .findAny().ifPresent(obj_id -> types.add(mimeType)); + // Use the group_concat function to get a list of files for each mime type. + // This has different syntax on Postgres vs SQLite + String groupConcatClause; + if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) { + groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; + } else { + groupConcatClause = " group_concat(obj_id) as object_ids"; } - } catch (SQLException | TskCoreException ex) { - Exceptions.printStackTrace(ex); - } - results.putAll(null, types); - } - break; - default: - //otherwise do straight db query - if (nonNull(db)) { - results.putAll(db.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); - } - } + String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type "; + try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS + ResultSet resultSet = executeQuery.getResultSet();) { + while (resultSet.next()) { + final String mimeType = resultSet.getString("mime_type"); //NON-NLS + String objIds = resultSet.getString("object_ids"); //NON-NLS - } catch (TskCoreException | TskDataException ex) { - logger.log(Level.SEVERE, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS + Pattern.compile(",").splitAsStream(objIds) + .map(Long::valueOf) + .filter(db::isInDB) + .findAny().ifPresent(obj_id -> types.add(mimeType)); + } + } catch (SQLException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + } + results.putAll(null, types); + } + break; + default: + //otherwise do straight db query + if (nonNull(db)) { + results.putAll(db.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); + } + } + + } catch (TskCoreException | TskDataException ex) { + logger.log(Level.SEVERE, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS + } + return results; } - return results; } } - private static Comparator applySortOrder(final SortOrder sortOrder, Comparator comparator) { + private static Comparator makeGroupComparator(final SortOrder sortOrder, GroupSortBy comparator) { switch (sortOrder) { case ASCENDING: return comparator; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index bcdc0ef416..2bb0b9be3b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -262,9 +262,9 @@ public class Toolbar extends ToolBar { }); syncDataSources(); - controller.getGroupManager().getDataSourceProperty().addListener((observable, oldDataSource, newDataSource) -> { - dataSourceSelectionModel.select(Optional.ofNullable(newDataSource)); - }); + controller.getGroupManager().getDataSourceProperty() + .addListener((observable, oldDataSource, newDataSource) + -> dataSourceSelectionModel.select(Optional.ofNullable(newDataSource))); dataSourceSelectionModel.select(Optional.ofNullable(controller.getGroupManager().getDataSource())); dataSourceComboBox.disableProperty().bind(groupByBox.getSelectionModel().selectedItemProperty().isNotEqualTo(DrawableAttribute.PATH)); dataSourceSelectionModel.selectedItemProperty().addListener(queryInvalidationListener); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java index d8320dfe6e..33cd84916f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Function; +import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; import javafx.collections.ListChangeListener; @@ -76,15 +77,16 @@ final public class GroupTree extends NavPanel> { groupTree.setShowRoot(false); getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change change) -> { + while (change.next()) { change.getAddedSubList().stream().forEach(this::insertGroup); change.getRemoved().stream().forEach(this::removeFromTree); } - sortGroups(); + Platform.runLater(this::sortGroups); }); getGroupManager().getAnalyzedGroups().forEach(this::insertGroup); - sortGroups(); + Platform.runLater(this::sortGroups); } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeItem.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeItem.java index 0a18668247..e5b67222dd 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeItem.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeItem.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.imagegallery.gui.navpanel; import java.util.Collections; +import static java.util.Collections.singleton; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -154,9 +155,7 @@ class GroupTreeItem extends TreeItem { if (parent != null) { parent.childMap.remove(getValue().getPath()); - Platform.runLater(() -> { - parent.getChildren().removeAll(Collections.singleton(GroupTreeItem.this)); - }); + Platform.runLater(() -> parent.getChildren().removeAll(singleton(GroupTreeItem.this))); if (parent.childMap.isEmpty()) { parent.removeFromParent(); From 7484533c9cc0ec5e97e78c8da67bc9c1cf19639b Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 30 Aug 2018 19:05:01 +0200 Subject: [PATCH 48/84] release locks /commit transactions in reverse order of a acquiring/starting them --- .../sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index a0430bd45c..f7a6c51cba 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -83,7 +83,7 @@ import org.sqlite.SQLiteJDBCLoader; */ public final class DrawableDB { - private static final Logger logger = Logger.getLogger(DrawableDB.class.getName()); + private static final Logger logger = Logger.getLogger(DrawableDB.class.getName()); //column name constants////////////////////// private static final String ANALYZED = "analyzed"; //NON-NLS @@ -686,8 +686,9 @@ public final class DrawableDB { trans = beginTransaction(); caseDbTransaction = tskCase.beginTransaction(); updateFile(f, trans, caseDbTransaction); - commitTransaction(trans, true); caseDbTransaction.commit(); + commitTransaction(trans, true); + } catch (TskCoreException ex) { if (null != trans) { rollbackTransaction(trans); From 71f55702256b33306fb8e2eb4526367ac61dee61 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 30 Aug 2018 15:12:48 -0400 Subject: [PATCH 49/84] Make the selection context work in group by data source mode --- .../autopsy/directorytree/SelectionContext.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java b/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java index c204be3b00..bf1d5db9fe 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java @@ -23,11 +23,13 @@ import org.openide.util.NbBundle; import static org.sleuthkit.autopsy.directorytree.Bundle.*; @NbBundle.Messages({"SelectionContext.dataSources=Data Sources", + "SelectionContext.dataSourceFiles=Data Source Files", "SelectionContext.views=Views"}) enum SelectionContext { DATA_SOURCES(SelectionContext_dataSources()), VIEWS(SelectionContext_views()), - OTHER(""); // Subnode of another node. + OTHER(""), + DATA_SOURCE_FILES(SelectionContext_dataSourceFiles()); // Subnode of another node. private final String displayName; @@ -36,7 +38,7 @@ enum SelectionContext { } public static SelectionContext getContextFromName(String name) { - if (name.equals(DATA_SOURCES.getName())) { + if (name.equals(DATA_SOURCES.getName()) || name.equals(DATA_SOURCE_FILES.getName())) { return DATA_SOURCES; } else if (name.equals(VIEWS.getName())) { return VIEWS; @@ -64,6 +66,16 @@ enum SelectionContext { // One level below root node. Should be one of DataSources, Views, or Results return SelectionContext.getContextFromName(n.getDisplayName()); } else { + // In Group by Data Source mode, the node under root is the data source name, and + // under that is Data Source Files, Views, or Results. Before moving up the tree, check + // if one of those applies + if (n.getParentNode().getParentNode().getParentNode() == null) { + SelectionContext context = SelectionContext.getContextFromName(n.getDisplayName()); + if (context != SelectionContext.OTHER) { + return context; + } + } + return getSelectionContext(n.getParentNode()); } } From 0eff6537d60647fa02c234b98cf5824145151d4b Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 31 Aug 2018 14:26:18 +0200 Subject: [PATCH 50/84] only show message over group are so user can change the datasource and bring other groups into the display. --- .../sleuthkit/autopsy/imagegallery/ImageGalleryController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index bbc7f1e5e5..dc313085e4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -301,7 +301,7 @@ public final class ImageGalleryController { if (groupManager.getAnalyzedGroups().isEmpty()) { if (IngestManager.getInstance().isIngestRunning()) { if (listeningEnabled.get()) { - replaceNotification(fullUIStackPane, + replaceNotification(centralStackPane, new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), new ProgressIndicator())); } else { From 9c666b52bf36dad6c3ae64b84706ef49f6a6b090 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 31 Aug 2018 16:10:50 +0200 Subject: [PATCH 51/84] make GroupViewState.getGroup return an optional --- .../actions/CategorizeGroupAction.java | 68 ++++++++++--------- .../imagegallery/actions/NextUnseenGroup.java | 4 +- .../imagegallery/actions/TagGroupAction.java | 9 +-- .../datamodel/grouping/GroupViewState.java | 25 +++---- .../gui/drawableviews/GroupPane.java | 4 +- .../imagegallery/gui/navpanel/NavPanel.java | 2 +- 6 files changed, 57 insertions(+), 55 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java index be2d9417c3..2b4946aa8b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java @@ -34,11 +34,13 @@ import javafx.scene.control.Label; import javafx.scene.control.Separator; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; +import org.apache.commons.lang.ObjectUtils; +import static org.apache.commons.lang.ObjectUtils.notEqual; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryPreferences; -import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.datamodel.TskCoreException; /** @@ -52,32 +54,35 @@ public class CategorizeGroupAction extends CategorizeAction { public CategorizeGroupAction(DhsImageCategory newCat, ImageGalleryController controller) { super(controller, newCat, null); setEventHandler(actionEvent -> { - ObservableList fileIDs = controller.getViewState().getGroup().getFileIDs(); + controller.getViewState().getGroup().ifPresent(group -> { + ObservableList fileIDs = group.getFileIDs(); - if (ImageGalleryPreferences.isGroupCategorizationWarningDisabled()) { - //if they have preveiously disabled the warning, just go ahead and apply categories. - addCatToFiles(ImmutableSet.copyOf(fileIDs)); - } else { - final Map catCountMap = new HashMap<>(); - - for (Long fileID : fileIDs) { - try { - DhsImageCategory category = controller.getFileFromId(fileID).getCategory(); - if (false == DhsImageCategory.ZERO.equals(category) && newCat.equals(category) == false) { - catCountMap.merge(category, 1L, Long::sum); - } - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to categorize files.", ex); - } - } - - if (catCountMap.isEmpty()) { - //if there are not going to be any categories overwritten, skip the warning. + if (ImageGalleryPreferences.isGroupCategorizationWarningDisabled()) { + //if they have preveiously disabled the warning, just go ahead and apply categories. addCatToFiles(ImmutableSet.copyOf(fileIDs)); } else { - showConfirmationDialog(catCountMap, newCat, fileIDs); + final Map catCountMap = new HashMap<>(); + + for (Long fileID : fileIDs) { + try { + DhsImageCategory category = controller.getFileFromId(fileID).getCategory(); + if (false == DhsImageCategory.ZERO.equals(category) && newCat.equals(category) == false) { + catCountMap.merge(category, 1L, Long::sum); + } + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to categorize files.", ex); + } + } + + if (catCountMap.isEmpty()) { + //if there are not going to be any categories overwritten, skip the warning. + addCatToFiles(ImmutableSet.copyOf(fileIDs)); + } else { + showConfirmationDialog(catCountMap, newCat, fileIDs); + } } - } + }); + }); } @@ -88,19 +93,18 @@ public class CategorizeGroupAction extends CategorizeAction { "CategorizeGroupAction.fileCountHeader=Files in the following categories will have their categories overwritten: "}) private void showConfirmationDialog(final Map catCountMap, DhsImageCategory newCat, ObservableList fileIDs) { - ButtonType categorizeButtonType = - new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY); + ButtonType categorizeButtonType + = new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY); VBox textFlow = new VBox(); for (Map.Entry entry : catCountMap.entrySet()) { - if (entry.getKey().equals(newCat) == false) { - if (entry.getValue() > 0) { - Label label = new Label(Bundle.CategorizeGroupAction_fileCountMessage(entry.getValue(), entry.getKey().getDisplayName()), - entry.getKey().getGraphic()); - label.setContentDisplay(ContentDisplay.RIGHT); - textFlow.getChildren().add(label); - } + if (entry.getValue() > 0 + && notEqual(entry.getKey(), newCat)) { + Label label = new Label(Bundle.CategorizeGroupAction_fileCountMessage(entry.getValue(), entry.getKey().getDisplayName()), + entry.getKey().getGraphic()); + label.setContentDisplay(ContentDisplay.RIGHT); + textFlow.getChildren().add(label); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index 6f947d7d9c..61f10055aa 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -71,7 +71,7 @@ public class NextUnseenGroup extends Action { setEventHandler(event -> { //on fx-thread //if there is a group assigned to the view, mark it as seen Optional.ofNullable(controller.getViewState()) - .map(GroupViewState::getGroup) + .flatMap(GroupViewState::getGroup) .ifPresent(group -> { groupManager.setGroupSeen(group, true) .addListener(this::advanceToNextUnseenGroup, MoreExecutors.newDirectExecutorService()); @@ -83,7 +83,7 @@ public class NextUnseenGroup extends Action { private void advanceToNextUnseenGroup() { synchronized (groupManager) { if (unSeenGroups.isEmpty() == false) { - controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true); + controller.advance(GroupViewState.tile(unSeenGroups.get(0)), false); } updateButton(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagGroupAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagGroupAction.java index a9229c0315..d48349ac22 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagGroupAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/TagGroupAction.java @@ -29,9 +29,10 @@ public class TagGroupAction extends AddTagAction { public TagGroupAction(final TagName tagName, ImageGalleryController controller) { super(controller, tagName, null); - setEventHandler(actionEvent -> - new AddTagAction(controller, tagName, ImmutableSet.copyOf(controller.getViewState().getGroup().getFileIDs())). - handle(actionEvent) - ); + setEventHandler(actionEvent -> { + controller.getViewState().getGroup().ifPresent(group -> { + new AddTagAction(controller, tagName, ImmutableSet.copyOf(group.getFileIDs())).handle(actionEvent); + }); + }); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupViewState.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupViewState.java index 86679340ab..fa578021ef 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupViewState.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupViewState.java @@ -22,9 +22,9 @@ import java.util.Objects; import java.util.Optional; /** - * + * Encapsulate information about the state of the group section of the UI. */ -public class GroupViewState { +public final class GroupViewState { private final DrawableGroup group; @@ -32,8 +32,8 @@ public class GroupViewState { private final Optional slideShowfileID; - public DrawableGroup getGroup() { - return group; + public Optional getGroup() { + return Optional.ofNullable(group); } public GroupViewMode getMode() { @@ -44,18 +44,18 @@ public class GroupViewState { return slideShowfileID; } - private GroupViewState(DrawableGroup g, GroupViewMode mode, Long slideShowfileID) { - this.group = g; + private GroupViewState(DrawableGroup group, GroupViewMode mode, Long slideShowfileID) { + this.group = group; this.mode = mode; this.slideShowfileID = Optional.ofNullable(slideShowfileID); } - public static GroupViewState tile(DrawableGroup g) { - return new GroupViewState(g, GroupViewMode.TILE, null); + public static GroupViewState tile(DrawableGroup group) { + return new GroupViewState(group, GroupViewMode.TILE, null); } - public static GroupViewState slideShow(DrawableGroup g, Long fileID) { - return new GroupViewState(g, GroupViewMode.SLIDE_SHOW, fileID); + public static GroupViewState slideShow(DrawableGroup group, Long fileID) { + return new GroupViewState(group, GroupViewMode.SLIDE_SHOW, fileID); } @Override @@ -82,10 +82,7 @@ public class GroupViewState { if (this.mode != other.mode) { return false; } - if (!Objects.equals(this.slideShowfileID, other.slideShowfileID)) { - return false; - } - return true; + return Objects.equals(this.slideShowfileID, other.slideShowfileID); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 169856ab88..95d2bbe67b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -613,11 +613,11 @@ public class GroupPane extends BorderPane { }); } else { - if (getGroup() != viewState.getGroup()) { + if (getGroup() != viewState.getGroup().orElse(null) ) { if (nonNull(getGroup())) { getGroup().getFileIDs().removeListener(filesSyncListener); } - this.grouping.set(viewState.getGroup()); + this.grouping.set(viewState.getGroup().orElse(null) ); getGroup().getFileIDs().addListener(filesSyncListener); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java index e3be8953ab..cd654cd390 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java @@ -96,7 +96,7 @@ abstract class NavPanel extends Tab { controller.viewState().addListener(observable -> { Platform.runLater(() -> { Optional.ofNullable(controller.getViewState()) - .map(GroupViewState::getGroup) + .flatMap(GroupViewState::getGroup) .ifPresent(this::setFocusedGroup); }); }); From 49dfc6ae4bab8d949686922862b329008cb102bb Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 31 Aug 2018 16:11:16 +0200 Subject: [PATCH 52/84] only change group after regrouping if the datasources are different. --- .../datamodel/grouping/GroupManager.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 39ad92f246..e063d8cebb 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -38,6 +38,7 @@ import java.util.Map; import java.util.Objects; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; +import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CancellationException; @@ -753,9 +754,23 @@ public class GroupManager { } } - if (isNotEmpty(unSeenGroups)) { - controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true); + DataSource dataSourceOfCurrentGroup + = Optional.ofNullable(controller.getViewState()) + .flatMap(GroupViewState::getGroup) + .map(DrawableGroup::getGroupKey) + .flatMap(GroupKey::getDataSource) + .orElse(null); + if (getDataSource() == null + || Objects.equals(dataSourceOfCurrentGroup, getDataSource())) { + //the current group is for the given datasource, so just keep it in view. + } else { //the current group should not be visible so ... + if (isNotEmpty(unSeenGroups)) {// show then next unseen group + controller.advance(GroupViewState.tile(unSeenGroups.get(0)), false); + } else { // clear the group area. + controller.advance(GroupViewState.tile(null), false); + } } + groupProgress.finish(); updateProgress(1, 1); return null; From 31903d04d84f60db76b1197cdeaccfda1fbbe398 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 31 Aug 2018 16:55:04 +0200 Subject: [PATCH 53/84] aquire db locks in consistent order : drawable then case. release in reverse order --- .../autopsy/imagegallery/ImageGalleryController.java | 9 +++++---- .../autopsy/imagegallery/datamodel/DrawableDB.java | 10 +++++----- .../imagegallery/datamodel/grouping/GroupManager.java | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index add5980574..fcdf9f6864 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -850,13 +850,10 @@ public final class ImageGalleryController { updateProgress(1.0); progressHandle.start(); - taskDB.commitTransaction(drawableDbTransaction, true); caseDbTransaction.commit(); + taskDB.commitTransaction(drawableDbTransaction, true); } catch (TskCoreException ex) { - if (null != drawableDbTransaction) { - taskDB.rollbackTransaction(drawableDbTransaction); - } if (null != caseDbTransaction) { try { caseDbTransaction.rollback(); @@ -864,6 +861,10 @@ public final class ImageGalleryController { logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS } } + if (null != drawableDbTransaction) { + taskDB.rollbackTransaction(drawableDbTransaction); + } + progressHandle.progress(Bundle.BulkTask_stopCopy_status()); logger.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS MessageNotifyUtil.Notify.warn(Bundle.BulkTask_errPopulating_errMsg(), ex.getMessage()); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index a0430bd45c..a687ada9dd 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -83,7 +83,7 @@ import org.sqlite.SQLiteJDBCLoader; */ public final class DrawableDB { - private static final Logger logger = Logger.getLogger(DrawableDB.class.getName()); + private static final Logger logger = Logger.getLogger(DrawableDB.class.getName()); //column name constants////////////////////// private static final String ANALYZED = "analyzed"; //NON-NLS @@ -686,12 +686,9 @@ public final class DrawableDB { trans = beginTransaction(); caseDbTransaction = tskCase.beginTransaction(); updateFile(f, trans, caseDbTransaction); - commitTransaction(trans, true); caseDbTransaction.commit(); + commitTransaction(trans, true); } catch (TskCoreException ex) { - if (null != trans) { - rollbackTransaction(trans); - } if (null != caseDbTransaction) { try { caseDbTransaction.rollback(); @@ -699,6 +696,9 @@ public final class DrawableDB { logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS } } + if (null != trans) { + rollbackTransaction(trans); + } logger.log(Level.SEVERE, "Error updating file", ex); //NON-NLS } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 4143c09b42..6c4583a922 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -595,7 +595,7 @@ public class GroupManager { } /** - * Handle notificationsS sent from Db when files are inserted/updated + * Handle notifications sent from Db when files are inserted/updated * * @param updatedFileIDs The ID of the inserted/updated files. */ From f8cdcc7f4dcad10bf27c09220581360387792bd3 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 3 Sep 2018 17:32:39 +0200 Subject: [PATCH 54/84] cleanup --- .../imagegallery/ImageGalleryController.java | 286 +++++++++--------- .../imagegallery/actions/AddTagAction.java | 20 +- .../imagegallery/datamodel/DrawableDB.java | 2 + .../datamodel/grouping/GroupManager.java | 1 + .../datamodel/grouping/GroupSortBy.java | 29 +- .../autopsy/imagegallery/gui/Toolbar.java | 33 +- .../gui/drawableviews/DrawableTileBase.java | 107 +++---- .../gui/drawableviews/GroupPane.java | 92 +++--- .../autopsy/imagegallery/utils/TaskUtils.java | 13 +- .../netbeans/core/startup/Bundle.properties | 2 +- .../core/windows/view/ui/Bundle.properties | 2 +- 11 files changed, 318 insertions(+), 269 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index dc313085e4..c4dab8605c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -60,6 +60,7 @@ import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.netbeans.api.progress.ProgressHandle; import org.openide.util.Cancellable; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case.CaseType; @@ -98,7 +99,7 @@ import org.sleuthkit.datamodel.TskData; * control. */ public final class ImageGalleryController { - + private static final Logger logger = Logger.getLogger(ImageGalleryController.class.getName()); private static ImageGalleryController instance; @@ -107,24 +108,24 @@ public final class ImageGalleryController { * not listen to speed up ingest */ private final SimpleBooleanProperty listeningEnabled = new SimpleBooleanProperty(false); - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private final ReadOnlyBooleanWrapper stale = new ReadOnlyBooleanWrapper(false); - + private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false); private final ReadOnlyDoubleWrapper thumbnailSize = new ReadOnlyDoubleWrapper(100); private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false); private final ReadOnlyIntegerWrapper dbTaskQueueSize = new ReadOnlyIntegerWrapper(0); - + private final FileIDSelectionModel selectionModel = new FileIDSelectionModel(this); - + private final History historyManager = new History<>(); private final UndoRedoManager undoManager = new UndoRedoManager(); private final GroupManager groupManager = new GroupManager(this); private final HashSetManager hashSetManager = new HashSetManager(); private final CategoryManager categoryManager = new CategoryManager(this); - private final DrawableTagsManager tagsManager = new DrawableTagsManager(null); - + private DrawableTagsManager tagsManager; + private Runnable showTree; private Toolbar toolbar; private StackPane fullUIStackPane; @@ -136,83 +137,87 @@ public final class ImageGalleryController { setOpacity(.4); } }; - + private ListeningExecutorService dbExecutor; - + private SleuthkitCase sleuthKitCase; private DrawableDB db; - + public static synchronized ImageGalleryController getDefault() { if (instance == null) { - instance = new ImageGalleryController(); + try { + instance = new ImageGalleryController(); + } catch (NoClassDefFoundError error) { + Exceptions.printStackTrace(error); + } } return instance; } - + public ReadOnlyBooleanProperty getMetaDataCollapsed() { return metaDataCollapsed.getReadOnlyProperty(); } - + public void setMetaDataCollapsed(Boolean metaDataCollapsed) { this.metaDataCollapsed.set(metaDataCollapsed); } - + public ReadOnlyDoubleProperty thumbnailSizeProperty() { return thumbnailSize.getReadOnlyProperty(); } - + public GroupViewState getViewState() { return historyManager.getCurrentState(); } - + public ReadOnlyBooleanProperty regroupDisabled() { return regroupDisabled.getReadOnlyProperty(); } - + public ReadOnlyObjectProperty viewState() { return historyManager.currentState(); } - + public FileIDSelectionModel getSelectionModel() { return selectionModel; } - + public GroupManager getGroupManager() { return groupManager; } - + synchronized public DrawableDB getDatabase() { return db; } - + public void setListeningEnabled(boolean enabled) { synchronized (listeningEnabled) { listeningEnabled.set(enabled); } } - + boolean isListeningEnabled() { synchronized (listeningEnabled) { return listeningEnabled.get(); } } - + @ThreadConfined(type = ThreadConfined.ThreadType.ANY) void setStale(Boolean b) { Platform.runLater(() -> { stale.set(b); }); } - + public ReadOnlyBooleanProperty stale() { return stale.getReadOnlyProperty(); } - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) boolean isStale() { return stale.get(); } - + private ImageGalleryController() { // listener for the boolean property about when IG is listening / enabled @@ -226,40 +231,40 @@ public final class ImageGalleryController { //populate the db this.rebuildDB(); } - + } catch (NoCurrentCaseException ex) { logger.log(Level.WARNING, "Exception while getting open case.", ex); } }); - + groupManager.getAnalyzedGroups().addListener((Observable o) -> checkForGroups()); - + viewState().addListener((Observable observable) -> { //when the viewed group changes, clear the selection and the undo/redo history selectionModel.clearSelection(); undoManager.clear(); }); - + regroupDisabled.addListener(observable -> checkForGroups()); - + IngestManager ingestManager = IngestManager.getInstance(); PropertyChangeListener ingestEventHandler = propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled); - + ingestManager.addIngestModuleEventListener(ingestEventHandler); ingestManager.addIngestJobEventListener(ingestEventHandler); - + dbTaskQueueSize.addListener(obs -> this.updateRegroupDisabled()); } - + public ReadOnlyBooleanProperty getCanAdvance() { return historyManager.getCanAdvance(); } - + public ReadOnlyBooleanProperty getCanRetreat() { return historyManager.getCanRetreat(); } - + @ThreadConfined(type = ThreadConfined.ThreadType.ANY) public void advance(GroupViewState newState, boolean forceShowTree) { if (forceShowTree && showTree != null) { @@ -267,15 +272,15 @@ public final class ImageGalleryController { } historyManager.advance(newState); } - + public GroupViewState advance() { return historyManager.advance(); } - + public GroupViewState retreat() { return historyManager.retreat(); } - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void updateRegroupDisabled() { regroupDisabled.set((dbTaskQueueSize.get() > 0) || IngestManager.getInstance().isIngestRunning()); @@ -308,7 +313,7 @@ public final class ImageGalleryController { replaceNotification(fullUIStackPane, new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); } - + } else if (dbTaskQueueSize.get() > 0) { replaceNotification(fullUIStackPane, new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), @@ -328,18 +333,18 @@ public final class ImageGalleryController { } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error counting files in drawable db.", ex); } - + } else if (false == groupManager.isRegrouping()) { replaceNotification(centralStackPane, new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6())); } - + } else { Platform.runLater(this::clearNotification); } } } - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void clearNotification() { //remove the ingest spinner @@ -351,11 +356,11 @@ public final class ImageGalleryController { centralStackPane.getChildren().remove(infoOverlay); } } - + private void replaceNotification(StackPane stackPane, Node newNode) { Platform.runLater(() -> { clearNotification(); - + infoOverlay = new StackPane(infoOverLayBackground, newNode); if (stackPane != null) { stackPane.getChildren().add(infoOverlay); @@ -367,14 +372,16 @@ public final class ImageGalleryController { * configure the controller for a specific case. * * @param theNewCase the case to configure the controller for + * + * @throws org.sleuthkit.datamodel.TskCoreException */ - public synchronized void setCase(Case theNewCase) { + public synchronized void setCase(Case theNewCase) throws TskCoreException { if (null == theNewCase) { reset(); } else { this.sleuthKitCase = theNewCase.getSleuthkitCase(); this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), this); - + setListeningEnabled(ImageGalleryModule.isEnabledforCase(theNewCase)); setStale(ImageGalleryModule.isDrawableDBStale(theNewCase)); @@ -384,7 +391,9 @@ public final class ImageGalleryController { groupManager.reset(); hashSetManager.setDb(db); categoryManager.setDb(db); - tagsManager.setAutopsyTagsManager(theNewCase.getServices().getTagsManager()); + tagsManager.unregisterListener(groupManager); + tagsManager.unregisterListener(categoryManager); + tagsManager = new DrawableTagsManager(theNewCase.getServices().getTagsManager()); tagsManager.registerListener(groupManager); tagsManager.registerListener(categoryManager); shutDownDBExecutor(); @@ -413,15 +422,16 @@ public final class ImageGalleryController { ThumbnailCache.getDefault().clearCache(); historyManager.clear(); groupManager.reset(); - tagsManager.clearFollowUpTagName(); + tagsManager.unregisterListener(groupManager); tagsManager.unregisterListener(categoryManager); + tagsManager = null; shutDownDBExecutor(); - + if (toolbar != null) { toolbar.reset(); } - + if (db != null) { db.closeDBCon(); } @@ -447,17 +457,17 @@ public final class ImageGalleryController { * @return list of data source object ids that are stale. */ Set getStaleDataSourceIds() { - + Set staleDataSourceIds = new HashSet<>(); // no current case open to check if ((null == getDatabase()) || (null == getSleuthKitCase())) { return staleDataSourceIds; } - + try { Map knownDataSourceIds = getDatabase().getDataSourceDbBuildStatus(); - + List dataSources = getSleuthKitCase().getDataSources(); Set caseDataSourceIds = new HashSet<>(); dataSources.forEach((dataSource) -> { @@ -478,15 +488,15 @@ public final class ImageGalleryController { staleDataSourceIds.add(id); } }); - + return staleDataSourceIds; } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Image Gallery failed to check if datasources table is stale.", ex); return staleDataSourceIds; } - + } - + synchronized private void shutDownDBExecutor() { if (dbExecutor != null) { dbExecutor.shutdownNow(); @@ -497,7 +507,7 @@ public final class ImageGalleryController { } } } - + private static ListeningExecutorService getNewDBExecutor() { return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( new ThreadFactoryBuilder().setNameFormat("DB-Worker-Thread-%d").build())); @@ -514,17 +524,17 @@ public final class ImageGalleryController { } incrementQueueSize(); dbExecutor.submit(bgTask).addListener(this::decrementQueueSize, MoreExecutors.directExecutor()); - + } - + private void incrementQueueSize() { Platform.runLater(() -> dbTaskQueueSize.set(dbTaskQueueSize.get() + 1)); } - + private void decrementQueueSize() { Platform.runLater(() -> dbTaskQueueSize.set(dbTaskQueueSize.get() - 1)); } - + @Nullable synchronized public DrawableFile getFileFromId(Long fileID) throws TskCoreException { if (Objects.isNull(db)) { @@ -533,13 +543,13 @@ public final class ImageGalleryController { } return db.getFileFromID(fileID); } - + public void setStacks(StackPane fullUIStack, StackPane centralStack) { fullUIStackPane = fullUIStack; this.centralStackPane = centralStack; Platform.runLater(this::checkForGroups); } - + public synchronized void setToolbar(Toolbar toolbar) { if (this.toolbar != null) { throw new IllegalStateException("Can not set the toolbar a second time!"); @@ -549,7 +559,7 @@ public final class ImageGalleryController { // RAMAN TBD: bind filterByDataSourceId to the data source dropdown in the toolbar. } - + public ReadOnlyDoubleProperty regroupProgress() { return groupManager.regroupProgress(); } @@ -566,34 +576,34 @@ public final class ImageGalleryController { IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener()); Case.addPropertyChangeListener(new CaseEventListener()); } - + public HashSetManager getHashSetManager() { return hashSetManager; } - + public CategoryManager getCategoryManager() { return categoryManager; } - + public DrawableTagsManager getTagsManager() { return tagsManager; } - + public void setShowTree(Runnable showTree) { this.showTree = showTree; } - + public UndoRedoManager getUndoManager() { return undoManager; } - + public ReadOnlyIntegerProperty getDBTasksQueueSizeProperty() { return dbTaskQueueSize.getReadOnlyProperty(); } - + public synchronized SleuthkitCase getSleuthKitCase() { return sleuthKitCase; - + } /** @@ -602,56 +612,56 @@ public final class ImageGalleryController { @NbBundle.Messages({"ImageGalleryController.InnerTask.progress.name=progress", "ImageGalleryController.InnerTask.message.name=status"}) static public abstract class BackgroundTask implements Runnable, Cancellable { - + private final SimpleObjectProperty state = new SimpleObjectProperty<>(Worker.State.READY); private final SimpleDoubleProperty progress = new SimpleDoubleProperty(this, Bundle.ImageGalleryController_InnerTask_progress_name()); private final SimpleStringProperty message = new SimpleStringProperty(this, Bundle.ImageGalleryController_InnerTask_message_name()); - + protected BackgroundTask() { } - + public double getProgress() { return progress.get(); } - + public final void updateProgress(Double workDone) { this.progress.set(workDone); } - + public String getMessage() { return message.get(); } - + public final void updateMessage(String Status) { this.message.set(Status); } - + public SimpleDoubleProperty progressProperty() { return progress; } - + public SimpleStringProperty messageProperty() { return message; } - + public Worker.State getState() { return state.get(); } - + public ReadOnlyObjectProperty stateProperty() { return new ReadOnlyObjectWrapper<>(state.get()); } - + @Override public synchronized boolean cancel() { updateState(Worker.State.CANCELLED); return true; } - + protected void updateState(Worker.State newState) { state.set(newState); } - + protected synchronized boolean isCancelled() { return getState() == Worker.State.CANCELLED; } @@ -661,18 +671,18 @@ public final class ImageGalleryController { * Abstract base class for tasks associated with a file in the database */ static abstract class FileTask extends BackgroundTask { - + private final AbstractFile file; private final DrawableDB taskDB; - + public DrawableDB getTaskDB() { return taskDB; } - + public AbstractFile getFile() { return file; } - + public FileTask(AbstractFile f, DrawableDB taskDB) { super(); this.file = f; @@ -684,7 +694,7 @@ public final class ImageGalleryController { * task that updates one file in database with results from ingest */ static private class UpdateFileTask extends FileTask { - + UpdateFileTask(AbstractFile f, DrawableDB taskDB) { super(f, taskDB); } @@ -711,7 +721,7 @@ public final class ImageGalleryController { * task that updates one file in database with results from ingest */ static private class RemoveFileTask extends FileTask { - + RemoveFileTask(AbstractFile f, DrawableDB taskDB) { super(f, taskDB); } @@ -732,7 +742,7 @@ public final class ImageGalleryController { } } } - + @NbBundle.Messages({"BulkTask.committingDb.status=committing image/video database", "BulkTask.stopCopy.status=Stopping copy to drawable db task.", "BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."}) @@ -741,36 +751,36 @@ public final class ImageGalleryController { * a given data source, into the Image gallery DB. */ abstract static private class BulkTransferTask extends BackgroundTask { - + static private final String FILE_EXTENSION_CLAUSE = "(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 + "') "; - + final String DRAWABLE_QUERY; final String DATASOURCE_CLAUSE; - + final ImageGalleryController controller; final DrawableDB taskDB; final SleuthkitCase tskCase; final long dataSourceObjId; - + ProgressHandle progressHandle; private boolean taskCompletionStatus; - + BulkTransferTask(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { this.controller = controller; this.taskDB = taskDB; this.tskCase = tskCase; this.dataSourceObjId = dataSourceObjId; - + DATASOURCE_CLAUSE = " (data_source_obj_id = " + dataSourceObjId + ") "; - + DRAWABLE_QUERY = DATASOURCE_CLAUSE + " AND ( " @@ -798,24 +808,24 @@ public final class ImageGalleryController { List getFiles() throws TskCoreException { return tskCase.findAllFilesWhere(DRAWABLE_QUERY); } - + abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) throws TskCoreException; - + @Override public void run() { progressHandle = getInitialProgressHandle(); progressHandle.start(); updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status()); - + DrawableDB.DrawableTransaction drawableDbTransaction = null; CaseDbTransaction caseDbTransaction = null; try { //grab all files with supported extension or detected mime types final List files = getFiles(); progressHandle.switchToDeterminate(files.size()); - + taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS); - + updateProgress(0.0); taskCompletionStatus = true; int workDone = 0; @@ -828,27 +838,27 @@ public final class ImageGalleryController { logger.log(Level.WARNING, "Task cancelled or interrupted: not all contents may be transfered to drawable database."); //NON-NLS taskCompletionStatus = false; progressHandle.finish(); - + break; } - + processFile(f, drawableDbTransaction, caseDbTransaction); - + workDone++; progressHandle.progress(f.getName(), workDone); updateProgress(workDone - 1 / (double) files.size()); updateMessage(f.getName()); } - + progressHandle.finish(); progressHandle = ProgressHandle.createHandle(Bundle.BulkTask_committingDb_status()); updateMessage(Bundle.BulkTask_committingDb_status()); updateProgress(1.0); - + progressHandle.start(); caseDbTransaction.commit(); taskDB.commitTransaction(drawableDbTransaction, true); - + } catch (TskCoreException ex) { if (null != drawableDbTransaction) { taskDB.rollbackTransaction(drawableDbTransaction); @@ -860,6 +870,7 @@ public final class ImageGalleryController { logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS } } + progressHandle.progress(Bundle.BulkTask_stopCopy_status()); logger.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS MessageNotifyUtil.Notify.warn(Bundle.BulkTask_errPopulating_errMsg(), ex.getMessage()); @@ -875,9 +886,9 @@ public final class ImageGalleryController { } cleanup(taskCompletionStatus); } - + abstract ProgressHandle getInitialProgressHandle(); - + protected void setTaskCompletionStatus(boolean status) { taskCompletionStatus = status; } @@ -894,26 +905,26 @@ public final class ImageGalleryController { "CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.", "CopyAnalyzedFiles.errPopulating.errMsg=There was an error populating Image Gallery database."}) private class CopyAnalyzedFiles extends BulkTransferTask { - + CopyAnalyzedFiles(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { super(dataSourceObjId, controller, taskDB, tskCase); } - + @Override protected void cleanup(boolean success) { // at the end of the task, set the stale status based on the // cumulative status of all data sources controller.setStale(isDataSourcesTableStale()); } - + @Override void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDbTransaction) throws TskCoreException { final boolean known = f.getKnown() == TskData.FileKnown.KNOWN; - + if (known) { taskDB.removeFile(f.getId(), tr); //remove known files } else { - + try { //supported mimetype => analyzed if (null != f.getMIMEType() && FileTypeUtils.hasDrawableMIMEType(f)) { @@ -933,7 +944,7 @@ public final class ImageGalleryController { } } } - + @Override @NbBundle.Messages({"CopyAnalyzedFiles.populatingDb.status=populating analyzed image/video database",}) ProgressHandle getInitialProgressHandle() { @@ -959,25 +970,25 @@ public final class ImageGalleryController { PrePopulateDataSourceFiles(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { super(dataSourceObjId, controller, taskDB, tskCase); } - + @Override protected void cleanup(boolean success) { } - + @Override void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) { taskDB.insertFile(DrawableFile.create(f, false, false), tr, caseDBTransaction); } - + @Override @NbBundle.Messages({"PrePopulateDataSourceFiles.prepopulatingDb.status=prepopulating image/video database",}) ProgressHandle getInitialProgressHandle() { return ProgressHandle.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this); } } - + private class IngestModuleEventListener implements PropertyChangeListener { - + @Override public void propertyChange(PropertyChangeEvent evt) { if (RuntimeProperties.runningWithGUI() == false) { @@ -1008,7 +1019,7 @@ public final class ImageGalleryController { * getOldValue has fileID getNewValue has * {@link Abstractfile} */ - + AbstractFile file = (AbstractFile) evt.getNewValue(); // only process individual files in realtime on the node that is running the ingest @@ -1039,9 +1050,9 @@ public final class ImageGalleryController { } } } - + private class CaseEventListener implements PropertyChangeListener { - + @Override public void propertyChange(PropertyChangeEvent evt) { if (RuntimeProperties.runningWithGUI() == false) { @@ -1060,8 +1071,13 @@ public final class ImageGalleryController { //close window, reset everything SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent); reset(); - } else { // a new case has been opened - setCase(newCase); //connect db, groupmanager, start worker thread + } else { + try { + // a new case has been opened + setCase(newCase); //connect db, groupmanager, start worker thread + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error changing case in ImageGallery.", ex); + } } break; case DATA_SOURCE_ADDED: @@ -1073,7 +1089,7 @@ public final class ImageGalleryController { } } break; - + case CONTENT_TAG_ADDED: final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt; if (getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) { @@ -1094,7 +1110,7 @@ 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. " @@ -1109,15 +1125,15 @@ public final class ImageGalleryController { // 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(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java index cd40efdb55..522c264a6b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2017 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,7 +36,6 @@ import javax.swing.SwingWorker; import org.controlsfx.control.action.Action; import org.controlsfx.control.action.ActionUtils; import org.openide.util.NbBundle; -import org.openide.util.NbBundle.Messages; import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog; @@ -50,8 +49,8 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TagName; -import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; /** * Instances of this Action allow users to apply tags to content. @@ -75,14 +74,14 @@ public class AddTagAction extends Action { setEventHandler(actionEvent -> addTagWithComment("")); } - static public Menu getTagMenu(ImageGalleryController controller) { + static public Menu getTagMenu(ImageGalleryController controller) throws TskCoreException { return new TagMenu(controller); } private void addTagWithComment(String comment) { addTagsToFiles(tagName, comment, selectedFileIDs); } - + @NbBundle.Messages({"# {0} - fileID", "AddDrawableTagAction.addTagsToFiles.alert=Unable to tag file {0}."}) private void addTagsToFiles(TagName tagName, String comment, Set selectedFiles) { @@ -141,7 +140,7 @@ public class AddTagAction extends Action { "AddDrawableTagAction.displayName.singular=Tag File"}) private static class TagMenu extends Menu { - TagMenu(ImageGalleryController controller) { + TagMenu(ImageGalleryController controller) throws TskCoreException { setGraphic(new ImageView(DrawableAttribute.TAGS.getIcon())); ObservableSet selectedFileIDs = controller.getSelectionModel().getSelected(); setText(selectedFileIDs.size() > 1 @@ -163,11 +162,10 @@ public class AddTagAction extends Action { empty.setDisable(true); quickTagMenu.getItems().add(empty); } else { - for (final TagName tagName : tagNames) { - AddTagAction addDrawableTagAction = new AddTagAction(controller, tagName, selectedFileIDs); - MenuItem tagNameItem = ActionUtils.createMenuItem(addDrawableTagAction); - quickTagMenu.getItems().add(tagNameItem); - } + tagNames.stream() + .map(tagName -> new AddTagAction(controller, tagName, selectedFileIDs)) + .map(ActionUtils::createMenuItem) + .forEachOrdered(quickTagMenu.getItems()::add); } /* diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index f7a6c51cba..b8d9acefd6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -42,12 +42,14 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; import java.util.logging.Level; import java.util.stream.Collectors; 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.datamodel.DhsImageCategory; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index e063d8cebb..d38120e654 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -66,6 +66,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import javax.swing.SortOrder; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; +import org.apache.commons.collections4.comparators.ComparableComparator; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java index 65e1870de8..97a75f0f5b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupSortBy.java @@ -28,7 +28,8 @@ import org.openide.util.NbBundle; /** * Pseudo enum of possible properties to sort groups by. */ -@NbBundle.Messages({"GroupSortBy.groupSize=Group Size", +@NbBundle.Messages({ + "GroupSortBy.groupSize=Group Size", "GroupSortBy.groupName=Group Name", "GroupSortBy.none=None", "GroupSortBy.priority=Priority"}) @@ -37,40 +38,35 @@ public class GroupSortBy implements Comparator { /** * sort the groups by the number of files in each */ - public final static GroupSortBy FILE_COUNT = - new GroupSortBy(Bundle.GroupSortBy_groupSize(), "folder-open-image.png", + public final static GroupSortBy FILE_COUNT + = new GroupSortBy(Bundle.GroupSortBy_groupSize(), "folder-open-image.png", Comparator.comparing(DrawableGroup::getSize)); /** * sort the groups by the natural order of the grouping value ( eg group * them by path alphabetically ) */ - public final static GroupSortBy GROUP_BY_VALUE = - new GroupSortBy(Bundle.GroupSortBy_groupName(), "folder-rename.png", + public final static GroupSortBy GROUP_BY_VALUE + = new GroupSortBy(Bundle.GroupSortBy_groupName(), "folder-rename.png", Comparator.comparing(DrawableGroup::getGroupByValueDislpayName)); /** * don't sort the groups just use what ever order they come in (ingest * order) */ - public final static GroupSortBy NONE = - new GroupSortBy(Bundle.GroupSortBy_none(), "prohibition.png", + public final static GroupSortBy NONE + = new GroupSortBy(Bundle.GroupSortBy_none(), "prohibition.png", new AllEqualComparator<>()); /** * sort the groups by some priority metric to be determined and implemented */ - public final static GroupSortBy PRIORITY = - new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png", + public final static GroupSortBy PRIORITY + = new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png", Comparator.comparing(DrawableGroup::getHashHitDensity) .thenComparing(Comparator.comparing(DrawableGroup::getUncategorizedCount)) .reversed()); - @Override - public int compare(DrawableGroup o1, DrawableGroup o2) { - return delegate.compare(o1, o2); - } - private final static ObservableList values = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(PRIORITY, NONE, GROUP_BY_VALUE, FILE_COUNT)); /** @@ -109,6 +105,11 @@ public class GroupSortBy implements Comparator { return icon; } + @Override + public int compare(DrawableGroup o1, DrawableGroup o2) { + return delegate.compare(o1, o2); + } + static class AllEqualComparator implements Comparator { @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 2bb0b9be3b..85c67226a5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.logging.Level; import javafx.application.Platform; @@ -77,6 +78,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; +import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; import org.sleuthkit.datamodel.DataSource; /** @@ -84,7 +86,7 @@ import org.sleuthkit.datamodel.DataSource; */ public class Toolbar extends ToolBar { - private static final Logger LOGGER = Logger.getLogger(Toolbar.class.getName()); + private static final Logger logger = Logger.getLogger(Toolbar.class.getName()); private static final int SIZE_SLIDER_DEFAULT = 100; @FXML @@ -109,9 +111,7 @@ public class Toolbar extends ToolBar { private Label thumbnailSizeLabel; private SortChooser sortChooser; - private final ListeningExecutorService exec - = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( - new ThreadFactoryBuilder().setNameFormat("Image Gallery Toolbar BG Thread").build())); + private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(Toolbar.class); private final ImageGalleryController controller; @@ -291,19 +291,31 @@ public class Toolbar extends ToolBar { * TODO (JIRA-3010): SEVERE error logged by image Gallery UI */ if (Case.isCaseOpen()) { - LOGGER.log(Level.WARNING, "Could not create Follow Up tag menu item", t); //NON-NLS + logger.log(Level.WARNING, "Could not create Follow Up tag menu item", t); //NON-NLS } else { // don't add stack trace to log because it makes looking for real errors harder - LOGGER.log(Level.INFO, "Unable to get tag name. Case is closed."); //NON-NLS + logger.log(Level.INFO, "Unable to get tag name. Case is closed."); //NON-NLS } } }, Platform::runLater); tagGroupMenuButton.showingProperty().addListener(showing -> { if (tagGroupMenuButton.isShowing()) { - List selTagMenues = Lists.transform(controller.getTagsManager().getNonCategoryTagNames(), - tagName -> GuiUtils.createAutoAssigningMenuItem(tagGroupMenuButton, new TagGroupAction(tagName, controller))); - tagGroupMenuButton.getItems().setAll(selTagMenues); + ListenableFuture> getTagsFuture = exec.submit(() -> { + return Lists.transform(controller.getTagsManager().getNonCategoryTagNames(), + tagName -> GuiUtils.createAutoAssigningMenuItem(tagGroupMenuButton, new TagGroupAction(tagName, controller))); + }); + Futures.addCallback(getTagsFuture, new FutureCallback>() { + @Override + public void onSuccess(List result) { + tagGroupMenuButton.getItems().setAll(result); + } + + @Override + public void onFailure(Throwable t) { + logger.log(Level.SEVERE, "Error getting non-gategory tag names.", t); + } + }, Platform::runLater); } }); } @@ -320,7 +332,7 @@ public class Toolbar extends ToolBar { @Override public void onFailure(Throwable t) { - LOGGER.log(Level.SEVERE, "Unable to get datasources for current case.", t); //NON-NLS + logger.log(Level.SEVERE, "Unable to get datasources for current case.", t); //NON-NLS Notifications.create().owner(getScene().getRoot()) .title("Image Gallery Error") .text(Bundle.Toolbar_getDataSources_errMessage()) @@ -389,6 +401,7 @@ public class Toolbar extends ToolBar { this.controller = controller; FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS + } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java index 243045d789..4336c52fff 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2017 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -79,9 +79,9 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** - * An abstract base class for {@link DrawableTile} and {@link SlideShowView}, - * since they share a similar node tree and many behaviors, other implementors - * of {@link DrawableView}s should implement the interface directly + * An abstract base class for DrawableTile and SlideShowView, since they share a + * similar node tree and many behaviors, other implementors of DrawableViews + * should implement the interface directly * * * TODO: refactor ExternalViewerAction to supply its own name @@ -89,7 +89,7 @@ import org.sleuthkit.datamodel.TskCoreException; @NbBundle.Messages({"DrawableTileBase.externalViewerAction.text=Open in External Viewer"}) public abstract class DrawableTileBase extends DrawableUIBase { - private static final Logger LOGGER = Logger.getLogger(DrawableTileBase.class.getName()); + private static final Logger logger = Logger.getLogger(DrawableTileBase.class.getName()); private static final Border UNSELECTED_BORDER = new Border(new BorderStroke(Color.GRAY, BorderStrokeStyle.SOLID, new CornerRadii(2), new BorderWidths(3))); private static final Border SELECTED_BORDER = new Border(new BorderStroke(Color.BLUE, BorderStrokeStyle.SOLID, new CornerRadii(2), new BorderWidths(3))); @@ -187,28 +187,31 @@ public abstract class DrawableTileBase extends DrawableUIBase { final ArrayList menuItems = new ArrayList<>(); menuItems.add(CategorizeAction.getCategoriesMenu(getController())); - menuItems.add(AddTagAction.getTagMenu(getController())); - + try { + menuItems.add(AddTagAction.getTagMenu(getController())); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error building tagging context menu.", ex); + } + final Collection selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - if(selectedFilesList.size() == 1) { + if (selectedFilesList.size() == 1) { menuItems.add(DeleteTagAction.getTagMenu(getController())); } final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles()); - extractMenuItem.setOnAction(actionEvent -> { - SwingUtilities.invokeLater(() -> { - TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID); - ExtractAction.getInstance().actionPerformed(new java.awt.event.ActionEvent(etc, 0, null)); - }); - }); + extractMenuItem.setOnAction(actionEvent + -> SwingUtilities.invokeLater(() -> { + TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID); + ExtractAction.getInstance().actionPerformed(new java.awt.event.ActionEvent(etc, 0, null)); + })); menuItems.add(extractMenuItem); MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer()); - contentViewer.setOnAction(actionEvent -> { - SwingUtilities.invokeLater(() -> { - new NewWindowViewAction(Bundle.DrawableTileBase_menuItem_showContentViewer(), new FileNode(file.getAbstractFile())).actionPerformed(null); - }); - }); + contentViewer.setOnAction(actionEvent + -> SwingUtilities.invokeLater(() -> { + new NewWindowViewAction(Bundle.DrawableTileBase_menuItem_showContentViewer(), new FileNode(file.getAbstractFile())) + .actionPerformed(null); + })); menuItems.add(contentViewer); OpenExternalViewerAction openExternalViewerAction = new OpenExternalViewerAction(file); @@ -243,32 +246,24 @@ public abstract class DrawableTileBase extends DrawableUIBase { protected abstract String getTextForLabel(); protected void initialize() { - followUpToggle.setOnAction(actionEvent -> { - getFile().ifPresent(file -> { - if (followUpToggle.isSelected() == true) { - try { + followUpToggle.setOnAction(actionEvent + -> getFile().ifPresent(file -> { + if (followUpToggle.isSelected()) { selectionModel.clearAndSelect(file.getId()); - new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex); //NON-NLS + new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()) + .handle(actionEvent); + } else { + new DeleteFollowUpTagAction(getController(), file).handle(actionEvent); } - } else { - new DeleteFollowUpTagAction(getController(), file).handle(actionEvent); - } - }); - }); + }) + ); } protected boolean hasFollowUp() { if (getFileID().isPresent()) { - try { - TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); - return DrawableAttribute.TAGS.getValue(getFile().get()).stream() - .anyMatch(followUpTagName::equals); - } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "failed to get follow up tag name ", ex); //NON-NLS - return true; - } + TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); + return DrawableAttribute.TAGS.getValue(getFile().get()).stream() + .anyMatch(followUpTagName::equals); } else { return false; } @@ -342,18 +337,14 @@ public abstract class DrawableTileBase extends DrawableUIBase { @Override public void handleTagAdded(ContentTagAddedEvent evt) { getFileID().ifPresent(fileID -> { - try { - final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); - final ContentTag addedTag = evt.getAddedTag(); - if (fileID == addedTag.getContent().getId() - && addedTag.getName().equals(followUpTagName)) { - Platform.runLater(() -> { - followUpImageView.setImage(followUpIcon); - followUpToggle.setSelected(true); - }); - } - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to get followup tag name. Unable to update follow up status for file. ", ex); //NON-NLS + final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS + final ContentTag addedTag = evt.getAddedTag(); + if (fileID == addedTag.getContent().getId() + && addedTag.getName().equals(followUpTagName)) { + Platform.runLater(() -> { + followUpImageView.setImage(followUpIcon); + followUpToggle.setSelected(true); + }); } }); } @@ -362,15 +353,11 @@ public abstract class DrawableTileBase extends DrawableUIBase { @Override public void handleTagDeleted(ContentTagDeletedEvent evt) { getFileID().ifPresent(fileID -> { - try { - final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); - final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo(); - if (fileID == deletedTagInfo.getContentID() - && deletedTagInfo.getName().equals(followUpTagName)) { - updateFollowUpIcon(); - } - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to get followup tag name. Unable to update follow up status for file. ", ex); //NON-NLS + final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS + final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo(); + if (fileID == deletedTagInfo.getContentID() + && deletedTagInfo.getName().equals(followUpTagName)) { + updateFollowUpIcon(); } }); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 95d2bbe67b..0e6242456c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -21,6 +21,10 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -31,8 +35,10 @@ import java.util.Map; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Optional; +import java.util.function.Function; import java.util.logging.Level; import java.util.stream.IntStream; +import java.util.stream.Stream; import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; @@ -97,6 +103,7 @@ import org.controlsfx.control.GridCell; import org.controlsfx.control.GridView; import org.controlsfx.control.SegmentedButton; import org.controlsfx.control.action.ActionUtils; +import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.actions.Presenter; @@ -107,6 +114,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; +import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; @@ -122,12 +130,12 @@ import org.sleuthkit.autopsy.imagegallery.actions.RedoAction; import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter; import org.sleuthkit.autopsy.imagegallery.actions.TagSelectedFilesAction; import org.sleuthkit.autopsy.imagegallery.actions.UndoAction; -import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils; +import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; import org.sleuthkit.datamodel.TskCoreException; /** @@ -145,10 +153,11 @@ import org.sleuthkit.datamodel.TskCoreException; */ public class GroupPane extends BorderPane { - private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName()); + private static final Logger logger = Logger.getLogger(GroupPane.class.getName()); + private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(GroupPane.class); + private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2); private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2); - private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE); private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)), @@ -156,8 +165,9 @@ public class GroupPane extends BorderPane { ); private final FileIDSelectionModel selectionModel; - private static final List categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5, - KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5); + private static final List categoryKeyCodes + = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5, + KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5); private final Back backAction; @@ -306,7 +316,7 @@ public class GroupPane extends BorderPane { slideShowPane.requestFocus(); } - + void syncCatToggle(DrawableFile file) { getToggleForCategory(file.getCategory()).setSelected(true); } @@ -426,27 +436,35 @@ public class GroupPane extends BorderPane { catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); + TagSelectedFilesAction followUpSelectedAction = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); //NON-NLS Platform.runLater(() -> { - try { - TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); - tagSelectedSplitMenu.setText(followUpSelectedACtion.getText()); - tagSelectedSplitMenu.setGraphic(followUpSelectedACtion.getGraphic()); - tagSelectedSplitMenu.setOnAction(followUpSelectedACtion); - } catch (TskCoreException tskCoreException) { - LOGGER.log(Level.WARNING, "failed to load FollowUpTagName", tskCoreException); //NON-NLS - } + tagSelectedSplitMenu.setText(followUpSelectedAction.getText()); + tagSelectedSplitMenu.setGraphic(followUpSelectedAction.getGraphic()); + tagSelectedSplitMenu.setOnAction(followUpSelectedAction); tagSelectedSplitMenu.showingProperty().addListener(showing -> { if (tagSelectedSplitMenu.isShowing()) { - List selTagMenues = Lists.transform(controller.getTagsManager().getNonCategoryTagNames(), - tagName -> GuiUtils.createAutoAssigningMenuItem(tagSelectedSplitMenu, new TagSelectedFilesAction(tagName, controller))); - tagSelectedSplitMenu.getItems().setAll(selTagMenues); + + ListenableFuture> getTagsFuture = exec.submit(() + -> Lists.transform(controller.getTagsManager().getNonCategoryTagNames(), + tagName -> GuiUtils.createAutoAssigningMenuItem(tagSelectedSplitMenu, new TagSelectedFilesAction(tagName, controller)))); + Futures.addCallback(getTagsFuture, new FutureCallback>() { + @Override + public void onSuccess(List result) { + tagSelectedSplitMenu.getItems().setAll(result); + } + + @Override + public void onFailure(Throwable t) { + logger.log(Level.SEVERE, "Error getting tag names.", t); + } + }, Platform::runLater); } }); - }); - CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller); + catSelectedSplitMenu.setOnAction(cat5SelectedAction); + catSelectedSplitMenu.setText(cat5SelectedAction.getText()); catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic()); catSelectedSplitMenu.showingProperty().addListener(showing -> { @@ -516,7 +534,7 @@ public class GroupPane extends BorderPane { //listen to tile selection and make sure it is visible in scroll area selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> { if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW - && slideShowPane != null) { + && slideShowPane != null) { slideShowPane.setFile(newFileId); } else { scrollToFileID(newFileId); @@ -831,24 +849,26 @@ public class GroupPane extends BorderPane { private ContextMenu buildContextMenu() { ArrayList menuItems = new ArrayList<>(); - menuItems.add(CategorizeAction.getCategoriesMenu(controller)); - menuItems.add(AddTagAction.getTagMenu(controller)); - - - Collection menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class); - - for (ContextMenuActionsProvider provider : menuProviders) { - for (final Action act : provider.getActions()) { - if (act instanceof Presenter.Popup) { - Presenter.Popup aact = (Presenter.Popup) act; - menuItems.add(SwingMenuItemAdapter.create(aact.getPopupPresenter())); - } - } + try { + menuItems.add(AddTagAction.getTagMenu(controller)); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error building tagging context menu.", ex); } + + Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class).stream() + .map(ContextMenuActionsProvider::getActions) + .flatMap(Collection::stream) + .filter(Presenter.Popup.class::isInstance) + .map(Presenter.Popup.class::cast) + .map(Presenter.Popup::getPopupPresenter) + .map(SwingMenuItemAdapter::create) + .forEachOrdered(menuItems::add); + final MenuItem extractMenuItem = new MenuItem(Bundle.GroupPane_gridViewContextMenuItem_extractFiles()); - extractMenuItem.setOnAction((ActionEvent t) -> { + + extractMenuItem.setOnAction(actionEvent -> { SwingUtilities.invokeLater(() -> { TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID); ExtractAction.getInstance().actionPerformed(new java.awt.event.ActionEvent(etc, 0, null)); @@ -857,7 +877,9 @@ public class GroupPane extends BorderPane { menuItems.add(extractMenuItem); ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{})); - contextMenu.setAutoHide(true); + + contextMenu.setAutoHide( + true); return contextMenu; } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/utils/TaskUtils.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/utils/TaskUtils.java index 7b648b5db7..9c26b9788c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/utils/TaskUtils.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/utils/TaskUtils.java @@ -18,13 +18,20 @@ */ package org.sleuthkit.autopsy.imagegallery.utils; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.concurrent.Callable; +import java.util.concurrent.Executors; import javafx.concurrent.Task; /** * */ -public class TaskUtils { +public final class TaskUtils { + + private TaskUtils() { + } public static Task taskFrom(Callable callable) { return new Task() { @@ -35,6 +42,8 @@ public class TaskUtils { }; } - private TaskUtils() { + public static ListeningExecutorService getExecutorForClass(Class clazz) { + return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( + new ThreadFactoryBuilder().setNameFormat("Image Gallery " + clazz.getSimpleName() + " BG Thread").build())); } } diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index 693f4b9f89..21c4fbf529 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Mon, 25 Jun 2018 17:19:36 -0400 +#Mon, 03 Sep 2018 17:29:44 +0200 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 3de464ea54..a730d6a65b 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Mon, 25 Jun 2018 17:19:36 -0400 +#Mon, 03 Sep 2018 17:29:44 +0200 CTL_MainWindow_Title=Autopsy 4.8.0 CTL_MainWindow_Title_No_Project=Autopsy 4.8.0 From a37b8dc743fbfeec738455425d584a69fa27a6e4 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Mon, 3 Sep 2018 17:31:14 +0200 Subject: [PATCH 55/84] simplify locking in DrawableTagsManager.java by making the backing tagsManager final --- .../imagegallery/ImageGalleryController.java | 134 ++++++------ .../datamodel/DrawableTagsManager.java | 191 ++++++----------- .../datamodel/grouping/GroupManager.java | 2 +- .../gui/drawableviews/DrawableTileBase.java | 74 +++---- .../gui/drawableviews/GroupPane.java | 192 +++++++++--------- 5 files changed, 261 insertions(+), 332 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index fcdf9f6864..29f915b6b0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -60,6 +60,7 @@ import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.netbeans.api.progress.ProgressHandle; import org.openide.util.Cancellable; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case.CaseType; @@ -123,7 +124,7 @@ public final class ImageGalleryController { private final GroupManager groupManager = new GroupManager(this); private final HashSetManager hashSetManager = new HashSetManager(); private final CategoryManager categoryManager = new CategoryManager(this); - private final DrawableTagsManager tagsManager = new DrawableTagsManager(null); + private DrawableTagsManager tagsManager; private Runnable showTree; private Toolbar toolbar; @@ -144,7 +145,11 @@ public final class ImageGalleryController { public static synchronized ImageGalleryController getDefault() { if (instance == null) { - instance = new ImageGalleryController(); + try { + instance = new ImageGalleryController(); + } catch (NoClassDefFoundError error) { + Exceptions.printStackTrace(error); + } } return instance; } @@ -232,12 +237,7 @@ public final class ImageGalleryController { } }); - groupManager.getAnalyzedGroups().addListener((Observable o) -> { - //analyzed groups is confined to JFX thread - if (Case.isCaseOpen()) { - checkForGroups(); - } - }); + groupManager.getAnalyzedGroups().addListener((Observable o) -> checkForGroups()); viewState().addListener((Observable observable) -> { //when the viewed group changes, clear the selection and the undo/redo history @@ -291,7 +291,6 @@ public final class ImageGalleryController { * GroupManager and remove blocking progress spinners if there are. If there * aren't, add a blocking progress spinner with appropriate message. */ - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) @NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. " + " No groups will be available until ingest is finished and listening is re-enabled.", "ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.", @@ -303,45 +302,46 @@ public final class ImageGalleryController { + " the current Group By setting resulted in no groups, " + "or no groups are fully analyzed but ingest is not running."}) synchronized private void checkForGroups() { - if (groupManager.getAnalyzedGroups().isEmpty()) { - if (IngestManager.getInstance().isIngestRunning()) { - if (listeningEnabled.not().get()) { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); - } else { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), - new ProgressIndicator())); - } - - } else if (dbTaskQueueSize.get() > 0) { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), - new ProgressIndicator())); - } else if (db != null) { - try { - if (db.countAllFiles() <= 0) { - - // there are no files in db - if (listeningEnabled.not().get()) { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4())); - } else { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5())); - } + if (Case.isCaseOpen()) { + if (groupManager.getAnalyzedGroups().isEmpty()) { + if (IngestManager.getInstance().isIngestRunning()) { + if (listeningEnabled.get()) { + replaceNotification(centralStackPane, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), + new ProgressIndicator())); + } else { + replaceNotification(fullUIStackPane, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error counting files in drawable db.", ex); + + } else if (dbTaskQueueSize.get() > 0) { + replaceNotification(fullUIStackPane, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), + new ProgressIndicator())); + } else if (db != null) { + try { + if (db.countAllFiles() <= 0) { + // there are no files in db + if (listeningEnabled.get()) { + replaceNotification(fullUIStackPane, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5())); + } else { + replaceNotification(fullUIStackPane, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4())); + } + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error counting files in drawable db.", ex); + } + + } else if (false == groupManager.isRegrouping()) { + replaceNotification(centralStackPane, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6())); } - } else if (!groupManager.isRegrouping()) { - replaceNotification(centralStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6())); + } else { + Platform.runLater(this::clearNotification); } - - } else { - clearNotification(); } } @@ -357,22 +357,25 @@ public final class ImageGalleryController { } } - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void replaceNotification(StackPane stackPane, Node newNode) { - clearNotification(); + Platform.runLater(() -> { + clearNotification(); - infoOverlay = new StackPane(infoOverLayBackground, newNode); - if (stackPane != null) { - stackPane.getChildren().add(infoOverlay); - } + infoOverlay = new StackPane(infoOverLayBackground, newNode); + if (stackPane != null) { + stackPane.getChildren().add(infoOverlay); + } + }); } /** * configure the controller for a specific case. * * @param theNewCase the case to configure the controller for + * + * @throws org.sleuthkit.datamodel.TskCoreException */ - public synchronized void setCase(Case theNewCase) { + public synchronized void setCase(Case theNewCase) throws TskCoreException { if (null == theNewCase) { reset(); } else { @@ -385,10 +388,15 @@ public final class ImageGalleryController { // if we add this line icons are made as files are analyzed rather than on demand. // db.addUpdatedFileListener(IconCache.getDefault()); historyManager.clear(); - groupManager.setDB(db); + groupManager.reset(); hashSetManager.setDb(db); categoryManager.setDb(db); - tagsManager.setAutopsyTagsManager(theNewCase.getServices().getTagsManager()); + + if (tagsManager != null) { + tagsManager.unregisterListener(groupManager); + tagsManager.unregisterListener(categoryManager); + } + tagsManager = new DrawableTagsManager(theNewCase.getServices().getTagsManager()); tagsManager.registerListener(groupManager); tagsManager.registerListener(categoryManager); shutDownDBExecutor(); @@ -416,10 +424,11 @@ public final class ImageGalleryController { setListeningEnabled(false); ThumbnailCache.getDefault().clearCache(); historyManager.clear(); - groupManager.clear(); - tagsManager.clearFollowUpTagName(); + groupManager.reset(); + tagsManager.unregisterListener(groupManager); tagsManager.unregisterListener(categoryManager); + tagsManager = null; shutDownDBExecutor(); if (toolbar != null) { @@ -854,6 +863,9 @@ public final class ImageGalleryController { taskDB.commitTransaction(drawableDbTransaction, true); } catch (TskCoreException ex) { + if (null != drawableDbTransaction) { + taskDB.rollbackTransaction(drawableDbTransaction); + } if (null != caseDbTransaction) { try { caseDbTransaction.rollback(); @@ -861,9 +873,6 @@ public final class ImageGalleryController { logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS } } - if (null != drawableDbTransaction) { - taskDB.rollbackTransaction(drawableDbTransaction); - } progressHandle.progress(Bundle.BulkTask_stopCopy_status()); logger.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS @@ -1065,8 +1074,13 @@ public final class ImageGalleryController { //close window, reset everything SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent); reset(); - } else { // a new case has been opened - setCase(newCase); //connect db, groupmanager, start worker thread + } else { + try { + // a new case has been opened + setCase(newCase); //connect db, groupmanager, start worker thread + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error changing case in ImageGallery.", ex); + } } break; case DATA_SOURCE_ADDED: diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java index cb30c37bab..4cbd4bf7d3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2013-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,25 +18,24 @@ */ package org.sleuthkit.autopsy.imagegallery.datamodel; -import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import com.google.common.eventbus.AsyncEventBus; import com.google.common.eventbus.EventBus; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.stream.Collectors; import javafx.scene.Node; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javax.annotation.Nonnull; import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TagName; @@ -44,39 +43,40 @@ import org.sleuthkit.datamodel.TskCoreException; /** * Manages Tags, Tagging, and the relationship between Categories and Tags in - * the autopsy Db. Delegates some work to the backing {@link TagsManager}. + * the autopsy Db. Delegates some work to the backing autopsy TagsManager. */ @NbBundle.Messages({"DrawableTagsManager.followUp=Follow Up", "DrawableTagsManager.bookMark=Bookmark"}) -public class DrawableTagsManager { +public final class DrawableTagsManager { - private static final Logger LOGGER = Logger.getLogger(DrawableTagsManager.class.getName()); + private static final Logger logger = Logger.getLogger(DrawableTagsManager.class.getName()); - private static Image FOLLOW_UP_IMAGE; - private static Image BOOKMARK_IMAGE; + private static final Image FOLLOW_UP_IMAGE = new Image("/org/sleuthkit/autopsy/imagegallery/images/flag_red.png"); + private static final Image BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png"); - final private Object autopsyTagsManagerLock = new Object(); - private TagsManager autopsyTagsManager; + private final TagsManager autopsyTagsManager; + + /** The tag name corresponding to the "built-in" tag "Follow Up" */ + private final TagName followUpTagName; + private final TagName bookmarkTagName; /** * Used to distribute {@link TagsChangeEvent}s */ - private final EventBus tagsEventBus = new AsyncEventBus( - Executors.newSingleThreadExecutor( - new BasicThreadFactory.Builder().namingPattern("Tags Event Bus").uncaughtExceptionHandler((Thread t, Throwable e) -> { //NON-NLS - LOGGER.log(Level.SEVERE, "uncaught exception in event bus handler", e); //NON-NLS - }).build() - )); + private final EventBus tagsEventBus + = new AsyncEventBus( + Executors.newSingleThreadExecutor( + new BasicThreadFactory.Builder() + .namingPattern("Tags Event Bus")//NON-NLS + .uncaughtExceptionHandler((Thread t, Throwable e) -> { + logger.log(Level.SEVERE, "Uncaught exception in DrawableTagsManager event bus handler.", e); //NON-NLS + }) + .build())); - /** - * The tag name corresponding to the "built-in" tag "Follow Up" - */ - private TagName followUpTagName; - private TagName bookmarkTagName; - - public DrawableTagsManager(TagsManager autopsyTagsManager) { + public DrawableTagsManager(TagsManager autopsyTagsManager) throws TskCoreException { this.autopsyTagsManager = autopsyTagsManager; - + followUpTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.followUp")); + bookmarkTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.bookMark")); } /** @@ -106,72 +106,37 @@ public class DrawableTagsManager { } /** - * assign a new TagsManager to back this one, ie when the current case - * changes + * Get the follow up TagName. * - * @param autopsyTagsManager + * @return The follow up TagName. */ - public void setAutopsyTagsManager(TagsManager autopsyTagsManager) { - synchronized (autopsyTagsManagerLock) { - this.autopsyTagsManager = autopsyTagsManager; - clearFollowUpTagName(); - } + public TagName getFollowUpTagName() { + return followUpTagName; } /** - * Use when closing a case to make sure everything is re-initialized in the - * next case. + * Get the bookmark TagName. + * + * @return The bookmark TagName. */ - public void clearFollowUpTagName() { - synchronized (autopsyTagsManagerLock) { - followUpTagName = null; - } + private TagName getBookmarkTagName() throws TskCoreException { + return bookmarkTagName; } /** - * get the (cached) follow up TagName + * Get all the TagNames that are not categories * - * @return - * - * @throws TskCoreException - */ - public TagName getFollowUpTagName() throws TskCoreException { - synchronized (autopsyTagsManagerLock) { - if (Objects.isNull(followUpTagName)) { - followUpTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.followUp")); - } - return followUpTagName; - } - } - - private Object getBookmarkTagName() throws TskCoreException { - synchronized (autopsyTagsManagerLock) { - if (Objects.isNull(bookmarkTagName)) { - bookmarkTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.bookMark")); - } - return bookmarkTagName; - } - } - - - /** - * get all the TagNames that are not categories - * - * @return all the TagNames that are not categories, in alphabetical order + * @return All the TagNames that are not categories, in alphabetical order * by displayName, or, an empty set if there was an exception * looking them up from the db. */ - @Nonnull public List getNonCategoryTagNames() { - synchronized (autopsyTagsManagerLock) { - try { - return autopsyTagsManager.getAllTagNames().stream() - .filter(CategoryManager::isNotCategoryTagName) - .distinct().sorted() - .collect(Collectors.toList()); - } catch (TskCoreException | IllegalStateException ex) { - LOGGER.log(Level.WARNING, "couldn't access case", ex); //NON-NLS - } + try { + return autopsyTagsManager.getAllTagNames().stream() + .filter(CategoryManager::isNotCategoryTagName) + .distinct().sorted() + .collect(Collectors.toList()); + } catch (TskCoreException tskCoreException) { return Collections.emptyList(); } } @@ -187,9 +152,7 @@ public class DrawableTagsManager { * @throws TskCoreException if there was an error reading from the db */ public List getContentTags(Content content) throws TskCoreException { - synchronized (autopsyTagsManagerLock) { - return autopsyTagsManager.getContentTagsByContent(content); - } + return autopsyTagsManager.getContentTagsByContent(content); } /** @@ -207,25 +170,23 @@ public class DrawableTagsManager { } public TagName getTagName(String displayName) throws TskCoreException { - synchronized (autopsyTagsManagerLock) { + try { + TagName returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName); + if (returnTagName != null) { + return returnTagName; + } try { - TagName returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName); + return autopsyTagsManager.addTagName(displayName); + } catch (TagsManager.TagNameAlreadyExistsException ex) { + returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName); if (returnTagName != null) { return returnTagName; } - try { - return autopsyTagsManager.addTagName(displayName); - } catch (TagsManager.TagNameAlreadyExistsException ex) { - returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName); - if (returnTagName != null) { - return returnTagName; - } - throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex); - } - } catch (NullPointerException | IllegalStateException ex) { - LOGGER.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS - throw new TskCoreException("Case was closed out from underneath", ex); + throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex); } + } catch (NullPointerException | IllegalStateException ex) { + logger.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS + throw new TskCoreException("Case was closed out from underneath", ex); } } @@ -233,65 +194,41 @@ public class DrawableTagsManager { try { return getTagName(cat.getDisplayName()); } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting tag for Category: " + cat.getDisplayName(), ex); return null; } } public ContentTag addContentTag(DrawableFile file, TagName tagName, String comment) throws TskCoreException { - synchronized (autopsyTagsManagerLock) { - return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment); - } + return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment); } public List getContentTagsByTagName(TagName t) throws TskCoreException { - synchronized (autopsyTagsManagerLock) { - return autopsyTagsManager.getContentTagsByTagName(t); - } + return autopsyTagsManager.getContentTagsByTagName(t); } public List getAllTagNames() throws TskCoreException { - synchronized (autopsyTagsManagerLock) { - return autopsyTagsManager.getAllTagNames(); - } + return autopsyTagsManager.getAllTagNames(); } public List getTagNamesInUse() throws TskCoreException { - synchronized (autopsyTagsManagerLock) { - return autopsyTagsManager.getTagNamesInUse(); - } + return autopsyTagsManager.getTagNamesInUse(); } public void deleteContentTag(ContentTag ct) throws TskCoreException { - synchronized (autopsyTagsManagerLock) { - autopsyTagsManager.deleteContentTag(ct); - } + autopsyTagsManager.deleteContentTag(ct); } public Node getGraphic(TagName tagname) { try { if (tagname.equals(getFollowUpTagName())) { - return new ImageView(getFollowUpImage()); + return new ImageView(FOLLOW_UP_IMAGE); } else if (tagname.equals(getBookmarkTagName())) { - return new ImageView(getBookmarkImage()); + return new ImageView(BOOKMARK_IMAGE); } } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to get \"Follow Up\" or \"Bookmark\"tag name from db.", ex); + logger.log(Level.SEVERE, "Failed to get \"Follow Up\" or \"Bookmark\"tag name from db.", ex); } return DrawableAttribute.TAGS.getGraphicForValue(tagname); } - - synchronized private static Image getFollowUpImage() { - if (FOLLOW_UP_IMAGE == null) { - FOLLOW_UP_IMAGE = new Image("/org/sleuthkit/autopsy/imagegallery/images/flag_red.png"); - } - return FOLLOW_UP_IMAGE; - } - - synchronized private static Image getBookmarkImage() { - if (BOOKMARK_IMAGE == null) { - BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png"); - } - return BOOKMARK_IMAGE; - } - } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 6c4583a922..4425f1e9c6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -231,7 +231,7 @@ public class GroupManager { } } - synchronized public void clear() { + synchronized public void reset() { if (groupByTask != null) { groupByTask.cancel(true); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java index 243045d789..138cec8477 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java @@ -188,9 +188,9 @@ public abstract class DrawableTileBase extends DrawableUIBase { menuItems.add(CategorizeAction.getCategoriesMenu(getController())); menuItems.add(AddTagAction.getTagMenu(getController())); - + final Collection selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); - if(selectedFilesList.size() == 1) { + if (selectedFilesList.size() == 1) { menuItems.add(DeleteTagAction.getTagMenu(getController())); } @@ -243,32 +243,24 @@ public abstract class DrawableTileBase extends DrawableUIBase { protected abstract String getTextForLabel(); protected void initialize() { - followUpToggle.setOnAction(actionEvent -> { - getFile().ifPresent(file -> { - if (followUpToggle.isSelected() == true) { - try { - selectionModel.clearAndSelect(file.getId()); - new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent); - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex); //NON-NLS - } - } else { - new DeleteFollowUpTagAction(getController(), file).handle(actionEvent); - } - }); - }); + followUpToggle.setOnAction( + actionEvent -> getFile().ifPresent( + file -> { + if (followUpToggle.isSelected() == true) { + selectionModel.clearAndSelect(file.getId()); + new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent); + } else { + new DeleteFollowUpTagAction(getController(), file).handle(actionEvent); + } + }) + ); } protected boolean hasFollowUp() { if (getFileID().isPresent()) { - try { - TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); - return DrawableAttribute.TAGS.getValue(getFile().get()).stream() - .anyMatch(followUpTagName::equals); - } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "failed to get follow up tag name ", ex); //NON-NLS - return true; - } + TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS + return DrawableAttribute.TAGS.getValue(getFile().get()).stream() + .anyMatch(followUpTagName::equals); } else { return false; } @@ -342,18 +334,14 @@ public abstract class DrawableTileBase extends DrawableUIBase { @Override public void handleTagAdded(ContentTagAddedEvent evt) { getFileID().ifPresent(fileID -> { - try { - final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); - final ContentTag addedTag = evt.getAddedTag(); - if (fileID == addedTag.getContent().getId() - && addedTag.getName().equals(followUpTagName)) { - Platform.runLater(() -> { - followUpImageView.setImage(followUpIcon); - followUpToggle.setSelected(true); - }); - } - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to get followup tag name. Unable to update follow up status for file. ", ex); //NON-NLS + final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS + final ContentTag addedTag = evt.getAddedTag(); + if (fileID == addedTag.getContent().getId() + && addedTag.getName().equals(followUpTagName)) { + Platform.runLater(() -> { + followUpImageView.setImage(followUpIcon); + followUpToggle.setSelected(true); + }); } }); } @@ -362,15 +350,11 @@ public abstract class DrawableTileBase extends DrawableUIBase { @Override public void handleTagDeleted(ContentTagDeletedEvent evt) { getFileID().ifPresent(fileID -> { - try { - final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); - final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo(); - if (fileID == deletedTagInfo.getContentID() - && deletedTagInfo.getName().equals(followUpTagName)) { - updateFollowUpIcon(); - } - } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to get followup tag name. Unable to update follow up status for file. ", ex); //NON-NLS + final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS + final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo(); + if (fileID == deletedTagInfo.getContentID() + && deletedTagInfo.getName().equals(followUpTagName)) { + updateFollowUpIcon(); } }); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 169856ab88..cb98a5e3b8 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -144,39 +144,39 @@ import org.sleuthkit.datamodel.TskCoreException; * https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview */ public class GroupPane extends BorderPane { - + private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName()); private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2); private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2); - + private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE); - + private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)), new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR)) ); - + private final FileIDSelectionModel selectionModel; private static final List categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5, KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5); - + private final Back backAction; - + private final Forward forwardAction; - + @FXML private Button undoButton; @FXML private Button redoButton; - + @FXML private SplitMenuButton catSelectedSplitMenu; - + @FXML private SplitMenuButton tagSelectedSplitMenu; - + @FXML private ToolBar headerToolBar; - + @FXML private ToggleButton cat0Toggle; @FXML @@ -189,30 +189,30 @@ public class GroupPane extends BorderPane { private ToggleButton cat4Toggle; @FXML private ToggleButton cat5Toggle; - + @FXML private SegmentedButton segButton; - + private SlideShowView slideShowPane; - + @FXML private ToggleButton slideShowToggle; - + @FXML private GridView gridView; - + @FXML private ToggleButton tileToggle; - + @FXML private Button nextButton; - + @FXML private Button backButton; - + @FXML private Button forwardButton; - + @FXML private Label groupLabel; @FXML @@ -223,24 +223,24 @@ public class GroupPane extends BorderPane { private Label catContainerLabel; @FXML private Label catHeadingLabel; - + @FXML private HBox catSegmentedContainer; @FXML private HBox catSplitMenuContainer; - + private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler(); - + private final NextUnseenGroup nextGroupAction; - + private final ImageGalleryController controller; - + private ContextMenu contextMenu; - + private Integer selectionAnchorIndex; private final UndoAction undoAction; private final RedoAction redoAction; - + GroupViewMode getGroupViewMode() { return groupViewMode.get(); } @@ -263,7 +263,7 @@ public class GroupPane extends BorderPane { */ @ThreadConfined(type = ThreadType.JFX) private final Map cellMap = new HashMap<>(); - + private final InvalidationListener filesSyncListener = (observable) -> { final String header = getHeaderString(); final List fileIds = getGroup().getFileIDs(); @@ -273,7 +273,7 @@ public class GroupPane extends BorderPane { groupLabel.setText(header); }); }; - + public GroupPane(ImageGalleryController controller) { this.controller = controller; this.selectionModel = controller.getSelectionModel(); @@ -282,10 +282,10 @@ public class GroupPane extends BorderPane { forwardAction = new Forward(controller); undoAction = new UndoAction(controller); redoAction = new RedoAction(controller); - + FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS } - + @ThreadConfined(type = ThreadType.JFX) public void activateSlideShowViewer(Long slideShowFileID) { groupViewMode.set(GroupViewMode.SLIDE_SHOW); @@ -301,16 +301,16 @@ public class GroupPane extends BorderPane { } else { slideShowPane.setFile(slideShowFileID); } - + setCenter(slideShowPane); slideShowPane.requestFocus(); - + } void syncCatToggle(DrawableFile file) { getToggleForCategory(file.getCategory()).setSelected(true); } - + public void activateTileViewer() { groupViewMode.set(GroupViewMode.TILE); tileToggle.setSelected(true); @@ -322,11 +322,11 @@ public class GroupPane extends BorderPane { slideShowPane = null; this.scrollToFileID(selectionModel.lastSelectedProperty().get()); } - + public DrawableGroup getGroup() { return grouping.get(); } - + private void selectAllFiles() { selectionModel.clearAndSelectAll(getGroup().getFileIDs()); } @@ -343,15 +343,15 @@ public class GroupPane extends BorderPane { : Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()), getGroup().getHashSetHitsCount(), getGroup().getSize()); } - + ContextMenu getContextMenu() { return contextMenu; } - + ReadOnlyObjectProperty grouping() { return grouping.getReadOnlyProperty(); } - + private ToggleButton getToggleForCategory(DhsImageCategory category) { switch (category) { case ZERO: @@ -396,7 +396,7 @@ public class GroupPane extends BorderPane { assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'."; - + for (DhsImageCategory cat : DhsImageCategory.values()) { ToggleButton toggleForCategory = getToggleForCategory(cat); toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2))); @@ -421,20 +421,16 @@ public class GroupPane extends BorderPane { gridView.cellHeightProperty().bind(cellSize); gridView.cellWidthProperty().bind(cellSize); gridView.setCellFactory((GridView param) -> new DrawableCell()); - + BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected()); catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); - + Platform.runLater(() -> { - try { - TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); - tagSelectedSplitMenu.setText(followUpSelectedACtion.getText()); - tagSelectedSplitMenu.setGraphic(followUpSelectedACtion.getGraphic()); - tagSelectedSplitMenu.setOnAction(followUpSelectedACtion); - } catch (TskCoreException tskCoreException) { - LOGGER.log(Level.WARNING, "failed to load FollowUpTagName", tskCoreException); //NON-NLS - } + TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); //NON-NLS + tagSelectedSplitMenu.setText(followUpSelectedACtion.getText()); + tagSelectedSplitMenu.setGraphic(followUpSelectedACtion.getGraphic()); + tagSelectedSplitMenu.setOnAction(followUpSelectedACtion); tagSelectedSplitMenu.showingProperty().addListener(showing -> { if (tagSelectedSplitMenu.isShowing()) { List selTagMenues = Lists.transform(controller.getTagsManager().getNonCategoryTagNames(), @@ -442,9 +438,9 @@ public class GroupPane extends BorderPane { tagSelectedSplitMenu.getItems().setAll(selTagMenues); } }); - + }); - + CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller); catSelectedSplitMenu.setOnAction(cat5SelectedAction); catSelectedSplitMenu.setText(cat5SelectedAction.getText()); @@ -456,12 +452,12 @@ public class GroupPane extends BorderPane { catSelectedSplitMenu.getItems().setAll(categoryMenues); } }); - + slideShowToggle.getStyleClass().remove("radio-button"); slideShowToggle.getStyleClass().add("toggle-button"); tileToggle.getStyleClass().remove("radio-button"); tileToggle.getStyleClass().add("toggle-button"); - + bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText()); headerLabel.setText(Bundle.GroupPane_hederLabel_displayText()); catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText()); @@ -481,12 +477,12 @@ public class GroupPane extends BorderPane { //listen to toggles and update view state slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get())); tileToggle.setOnAction(onAction -> activateTileViewer()); - + controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState)); - + addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler); gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler()); - + ActionUtils.configureButton(undoAction, undoButton); ActionUtils.configureButton(redoAction, redoButton); ActionUtils.configureButton(forwardAction, forwardButton); @@ -502,7 +498,7 @@ public class GroupPane extends BorderPane { nextButton.setEffect(null); onAction.handle(actionEvent); }); - + nextGroupAction.disabledProperty().addListener((Observable observable) -> { boolean newValue = nextGroupAction.isDisabled(); nextButton.setEffect(newValue ? null : DROP_SHADOW); @@ -516,13 +512,13 @@ public class GroupPane extends BorderPane { //listen to tile selection and make sure it is visible in scroll area selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> { if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW - && slideShowPane != null) { + && slideShowPane != null) { slideShowPane.setFile(newFileId); } else { scrollToFileID(newFileId); } }); - + setViewState(controller.viewState().get()); } @@ -532,16 +528,16 @@ public class GroupPane extends BorderPane { if (newFileID == null) { return; //scrolling to no file doesn't make sense, so abort. } - + final ObservableList fileIds = gridView.getItems(); - + int selectedIndex = fileIds.indexOf(newFileID); if (selectedIndex == -1) { //somehow we got passed a file id that isn't in the curent group. //this should never happen, but if it does everything is going to fail, so abort. return; } - + getScrollBar().ifPresent(scrollBar -> { DrawableCell cell = cellMap.get(newFileID); @@ -568,14 +564,14 @@ public class GroupPane extends BorderPane { } cell = cellMap.get(newFileID); } - + final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal()); Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal()); //while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates int i = 0; while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) { - + if (tileBounds.getMinY() < gridViewBounds.getMinY()) { scrollBar.decrement(); } else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) { @@ -593,13 +589,13 @@ public class GroupPane extends BorderPane { * @param grouping the new grouping assigned to this group */ void setViewState(GroupViewState viewState) { - + if (isNull(viewState) || isNull(viewState.getGroup())) { if (nonNull(getGroup())) { getGroup().getFileIDs().removeListener(filesSyncListener); } this.grouping.set(null); - + Platform.runLater(() -> { gridView.getItems().setAll(Collections.emptyList()); setCenter(null); @@ -611,18 +607,18 @@ public class GroupPane extends BorderPane { cellMap.clear(); } }); - + } else { if (getGroup() != viewState.getGroup()) { if (nonNull(getGroup())) { getGroup().getFileIDs().removeListener(filesSyncListener); } this.grouping.set(viewState.getGroup()); - + getGroup().getFileIDs().addListener(filesSyncListener); - + final String header = getHeaderString(); - + Platform.runLater(() -> { gridView.getItems().setAll(getGroup().getFileIDs()); slideShowToggle.setDisable(gridView.getItems().isEmpty()); @@ -637,14 +633,14 @@ public class GroupPane extends BorderPane { } } } - + @ThreadConfined(type = ThreadType.JFX) private void resetScrollBar() { getScrollBar().ifPresent((scrollBar) -> { scrollBar.setValue(0); }); } - + @ThreadConfined(type = ThreadType.JFX) private Optional getScrollBar() { if (gridView == null || gridView.getSkin() == null) { @@ -652,16 +648,16 @@ public class GroupPane extends BorderPane { } return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); //NON-NLS } - + void makeSelection(Boolean shiftDown, Long newFileID) { - + if (shiftDown) { //TODO: do more hear to implement slicker multiselect int endIndex = grouping.get().getFileIDs().indexOf(newFileID); int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt(); endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt(); List subList = grouping.get().getFileIDs().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().getFileIDs().size()) + 1); - + selectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()])); selectionModel.select(newFileID); } else { @@ -669,11 +665,11 @@ public class GroupPane extends BorderPane { selectionModel.clearAndSelect(newFileID); } } - + private class DrawableCell extends GridCell { - + private final DrawableTile tile = new DrawableTile(GroupPane.this, controller); - + DrawableCell() { itemProperty().addListener((ObservableValue observable, Long oldValue, Long newValue) -> { if (oldValue != null) { @@ -689,19 +685,19 @@ public class GroupPane extends BorderPane { } } cellMap.put(newValue, DrawableCell.this); - + } }); - + setGraphic(tile); } - + @Override protected void updateItem(Long item, boolean empty) { super.updateItem(item, empty); tile.setFile(item); } - + void resetItem() { tile.setFile(null); } @@ -712,10 +708,10 @@ public class GroupPane extends BorderPane { * arrows) */ private class KeyboardHandler implements EventHandler { - + @Override public void handle(KeyEvent t) { - + if (t.getEventType() == KeyEvent.KEY_PRESSED) { switch (t.getCode()) { case SHIFT: @@ -758,7 +754,7 @@ public class GroupPane extends BorderPane { t.consume(); break; } - + if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) { selectAllFiles(); t.consume(); @@ -772,7 +768,7 @@ public class GroupPane extends BorderPane { } } } - + private DhsImageCategory keyCodeToCat(KeyCode t) { if (t != null) { switch (t) { @@ -798,16 +794,16 @@ public class GroupPane extends BorderPane { } return null; } - + private void handleArrows(KeyEvent t) { Long lastSelectFileId = selectionModel.lastSelectedProperty().get(); - + int lastSelectedIndex = lastSelectFileId != null ? grouping.get().getFileIDs().indexOf(lastSelectFileId) : Optional.ofNullable(selectionAnchorIndex).orElse(0); - + final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1); - + final Map tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1); // implement proper keyboard based multiselect @@ -826,19 +822,17 @@ public class GroupPane extends BorderPane { } } } - + private class MouseHandler implements EventHandler { - + private ContextMenu buildContextMenu() { ArrayList menuItems = new ArrayList<>(); - menuItems.add(CategorizeAction.getCategoriesMenu(controller)); menuItems.add(AddTagAction.getTagMenu(controller)); - Collection menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class); - + for (ContextMenuActionsProvider provider : menuProviders) { for (final Action act : provider.getActions()) { if (act instanceof Presenter.Popup) { @@ -855,12 +849,12 @@ public class GroupPane extends BorderPane { }); }); menuItems.add(extractMenuItem); - + ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{})); contextMenu.setAutoHide(true); return contextMenu; } - + @Override public void handle(MouseEvent t) { switch (t.getButton()) { @@ -881,7 +875,7 @@ public class GroupPane extends BorderPane { if (contextMenu == null) { contextMenu = buildContextMenu(); } - + contextMenu.hide(); contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY()); } From 76668f53eca2e20be1e2c43ff6b5f478a9840f5f Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 4 Sep 2018 09:38:18 +0200 Subject: [PATCH 56/84] minor cleanup --- .../datamodel/grouping/GroupManager.java | 1 - .../imagegallery/gui/SummaryTablePane.java | 21 ++++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 1ad620d3e0..e1d9521f3d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -66,7 +66,6 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import javax.swing.SortOrder; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; -import org.apache.commons.collections4.comparators.ComparableComparator; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java index 44814e2c9a..264c7c9016 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2013-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,7 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TableColumn; +import javafx.scene.control.TableColumn.CellDataFeatures; import javafx.scene.control.TableView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Priority; @@ -34,9 +35,10 @@ import javafx.scene.layout.VBox; import javafx.util.Pair; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; -import org.sleuthkit.autopsy.datamodel.DhsImageCategory; +import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager.CategoryChangeEvent; /** * Displays summary statistics (counts) for each group @@ -51,11 +53,13 @@ public class SummaryTablePane extends AnchorPane { @FXML private TableView> tableView; + private final ImageGalleryController controller; @FXML - @NbBundle.Messages({"SummaryTablePane.catColumn=Category", - "SummaryTablePane.countColumn=# Files"}) + @NbBundle.Messages({ + "SummaryTablePane.catColumn=Category", + "SummaryTablePane.countColumn=# Files"}) void initialize() { assert catColumn != null : "fx:id=\"catColumn\" was not injected: check your FXML file 'SummaryTablePane.fxml'."; assert countColumn != null : "fx:id=\"countColumn\" was not injected: check your FXML file 'SummaryTablePane.fxml'."; @@ -67,11 +71,11 @@ public class SummaryTablePane extends AnchorPane { tableView.prefHeightProperty().set(7 * 25); //set up columns - catColumn.setCellValueFactory((TableColumn.CellDataFeatures, String> p) -> new SimpleObjectProperty<>(p.getValue().getKey().getDisplayName())); + catColumn.setCellValueFactory(params -> new SimpleObjectProperty<>(params.getValue().getKey().getDisplayName())); catColumn.setPrefWidth(USE_COMPUTED_SIZE); catColumn.setText(Bundle.SummaryTablePane_catColumn()); - countColumn.setCellValueFactory((TableColumn.CellDataFeatures, Long> p) -> new SimpleObjectProperty<>(p.getValue().getValue())); + countColumn.setCellValueFactory(params -> new SimpleObjectProperty<>(params.getValue().getValue())); countColumn.setPrefWidth(USE_COMPUTED_SIZE); countColumn.setText(Bundle.SummaryTablePane_countColumn()); @@ -85,14 +89,15 @@ public class SummaryTablePane extends AnchorPane { public SummaryTablePane(ImageGalleryController controller) { this.controller = controller; FXMLConstructor.construct(this, "SummaryTablePane.fxml"); //NON-NLS - } /** * listen to Category updates and rebuild the table + * + * @param evt The change event. */ @Subscribe - public void handleCategoryChanged(org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager.CategoryChangeEvent evt) { + public void handleCategoryChanged(CategoryChangeEvent evt) { final ObservableList> data = FXCollections.observableArrayList(); if (Case.isCaseOpen()) { for (DhsImageCategory cat : DhsImageCategory.values()) { From 8df6dfb8f7f1e4afbe5ed874f8b4b9776f16ec1b Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 4 Sep 2018 09:46:10 +0200 Subject: [PATCH 57/84] pass controller to DrawableTagsManager constructor, ImageGalleryController keeps reference to Case object --- .../imagegallery/ImageGalleryController.java | 267 +++++++++--------- .../datamodel/DrawableTagsManager.java | 13 +- 2 files changed, 145 insertions(+), 135 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index f96a0a5b1a..e0f253e491 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -99,7 +99,7 @@ import org.sleuthkit.datamodel.TskData; * control. */ public final class ImageGalleryController { - + private static final Logger logger = Logger.getLogger(ImageGalleryController.class.getName()); private static ImageGalleryController instance; @@ -108,24 +108,24 @@ public final class ImageGalleryController { * not listen to speed up ingest */ private final SimpleBooleanProperty listeningEnabled = new SimpleBooleanProperty(false); - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private final ReadOnlyBooleanWrapper stale = new ReadOnlyBooleanWrapper(false); - + private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false); private final ReadOnlyDoubleWrapper thumbnailSize = new ReadOnlyDoubleWrapper(100); private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false); private final ReadOnlyIntegerWrapper dbTaskQueueSize = new ReadOnlyIntegerWrapper(0); - + private final FileIDSelectionModel selectionModel = new FileIDSelectionModel(this); - + private final History historyManager = new History<>(); private final UndoRedoManager undoManager = new UndoRedoManager(); private final GroupManager groupManager = new GroupManager(this); private final HashSetManager hashSetManager = new HashSetManager(); private final CategoryManager categoryManager = new CategoryManager(this); private DrawableTagsManager tagsManager; - + private Runnable showTree; private Toolbar toolbar; private StackPane fullUIStackPane; @@ -137,12 +137,17 @@ public final class ImageGalleryController { setOpacity(.4); } }; - + private ListeningExecutorService dbExecutor; - + + private Case autopsyCase; + + public Case getAutopsyCase() { + return autopsyCase; + } private SleuthkitCase sleuthKitCase; private DrawableDB db; - + public static synchronized ImageGalleryController getDefault() { if (instance == null) { try { @@ -153,71 +158,71 @@ public final class ImageGalleryController { } return instance; } - + public ReadOnlyBooleanProperty getMetaDataCollapsed() { return metaDataCollapsed.getReadOnlyProperty(); } - + public void setMetaDataCollapsed(Boolean metaDataCollapsed) { this.metaDataCollapsed.set(metaDataCollapsed); } - + public ReadOnlyDoubleProperty thumbnailSizeProperty() { return thumbnailSize.getReadOnlyProperty(); } - + public GroupViewState getViewState() { return historyManager.getCurrentState(); } - + public ReadOnlyBooleanProperty regroupDisabled() { return regroupDisabled.getReadOnlyProperty(); } - + public ReadOnlyObjectProperty viewState() { return historyManager.currentState(); } - + public FileIDSelectionModel getSelectionModel() { return selectionModel; } - + public GroupManager getGroupManager() { return groupManager; } - + synchronized public DrawableDB getDatabase() { return db; } - + public void setListeningEnabled(boolean enabled) { synchronized (listeningEnabled) { listeningEnabled.set(enabled); } } - + boolean isListeningEnabled() { synchronized (listeningEnabled) { return listeningEnabled.get(); } } - + @ThreadConfined(type = ThreadConfined.ThreadType.ANY) void setStale(Boolean b) { Platform.runLater(() -> { stale.set(b); }); } - + public ReadOnlyBooleanProperty stale() { return stale.getReadOnlyProperty(); } - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) boolean isStale() { return stale.get(); } - + private ImageGalleryController() { // listener for the boolean property about when IG is listening / enabled @@ -231,40 +236,40 @@ public final class ImageGalleryController { //populate the db this.rebuildDB(); } - + } catch (NoCurrentCaseException ex) { logger.log(Level.WARNING, "Exception while getting open case.", ex); } }); - + groupManager.getAnalyzedGroups().addListener((Observable o) -> checkForGroups()); - + viewState().addListener((Observable observable) -> { //when the viewed group changes, clear the selection and the undo/redo history selectionModel.clearSelection(); undoManager.clear(); }); - + regroupDisabled.addListener(observable -> checkForGroups()); - + IngestManager ingestManager = IngestManager.getInstance(); PropertyChangeListener ingestEventHandler = propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled); - + ingestManager.addIngestModuleEventListener(ingestEventHandler); ingestManager.addIngestJobEventListener(ingestEventHandler); - + dbTaskQueueSize.addListener(obs -> this.updateRegroupDisabled()); } - + public ReadOnlyBooleanProperty getCanAdvance() { return historyManager.getCanAdvance(); } - + public ReadOnlyBooleanProperty getCanRetreat() { return historyManager.getCanRetreat(); } - + @ThreadConfined(type = ThreadConfined.ThreadType.ANY) public void advance(GroupViewState newState, boolean forceShowTree) { if (forceShowTree && showTree != null) { @@ -272,15 +277,15 @@ public final class ImageGalleryController { } historyManager.advance(newState); } - + public GroupViewState advance() { return historyManager.advance(); } - + public GroupViewState retreat() { return historyManager.retreat(); } - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void updateRegroupDisabled() { regroupDisabled.set((dbTaskQueueSize.get() > 0) || IngestManager.getInstance().isIngestRunning()); @@ -313,7 +318,7 @@ public final class ImageGalleryController { replaceNotification(fullUIStackPane, new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); } - + } else if (dbTaskQueueSize.get() > 0) { replaceNotification(fullUIStackPane, new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), @@ -333,18 +338,18 @@ public final class ImageGalleryController { } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error counting files in drawable db.", ex); } - + } else if (false == groupManager.isRegrouping()) { replaceNotification(centralStackPane, new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6())); } - + } else { Platform.runLater(this::clearNotification); } } } - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void clearNotification() { //remove the ingest spinner @@ -356,11 +361,11 @@ public final class ImageGalleryController { centralStackPane.getChildren().remove(infoOverlay); } } - + private void replaceNotification(StackPane stackPane, Node newNode) { Platform.runLater(() -> { clearNotification(); - + infoOverlay = new StackPane(infoOverLayBackground, newNode); if (stackPane != null) { stackPane.getChildren().add(infoOverlay); @@ -379,9 +384,10 @@ public final class ImageGalleryController { if (null == theNewCase) { reset(); } else { + this.autopsyCase = theNewCase; this.sleuthKitCase = theNewCase.getSleuthkitCase(); this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), this); - + setListeningEnabled(ImageGalleryModule.isEnabledforCase(theNewCase)); setStale(ImageGalleryModule.isDrawableDBStale(theNewCase)); @@ -396,7 +402,7 @@ public final class ImageGalleryController { tagsManager.unregisterListener(groupManager); tagsManager.unregisterListener(categoryManager); } - tagsManager = new DrawableTagsManager(theNewCase.getServices().getTagsManager()); + tagsManager = new DrawableTagsManager(this); tagsManager.registerListener(groupManager); tagsManager.registerListener(categoryManager); shutDownDBExecutor(); @@ -420,21 +426,22 @@ public final class ImageGalleryController { */ public synchronized void reset() { logger.info("resetting ImageGalleryControler to initial state."); //NON-NLS + autopsyCase = null; selectionModel.clearSelection(); setListeningEnabled(false); ThumbnailCache.getDefault().clearCache(); historyManager.clear(); groupManager.reset(); - + tagsManager.unregisterListener(groupManager); tagsManager.unregisterListener(categoryManager); tagsManager = null; shutDownDBExecutor(); - + if (toolbar != null) { toolbar.reset(); } - + if (db != null) { db.closeDBCon(); } @@ -460,17 +467,17 @@ public final class ImageGalleryController { * @return list of data source object ids that are stale. */ Set getStaleDataSourceIds() { - + Set staleDataSourceIds = new HashSet<>(); // no current case open to check if ((null == getDatabase()) || (null == getSleuthKitCase())) { return staleDataSourceIds; } - + try { Map knownDataSourceIds = getDatabase().getDataSourceDbBuildStatus(); - + List dataSources = getSleuthKitCase().getDataSources(); Set caseDataSourceIds = new HashSet<>(); dataSources.forEach((dataSource) -> { @@ -491,15 +498,15 @@ public final class ImageGalleryController { staleDataSourceIds.add(id); } }); - + return staleDataSourceIds; } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Image Gallery failed to check if datasources table is stale.", ex); return staleDataSourceIds; } - + } - + synchronized private void shutDownDBExecutor() { if (dbExecutor != null) { dbExecutor.shutdownNow(); @@ -510,7 +517,7 @@ public final class ImageGalleryController { } } } - + private static ListeningExecutorService getNewDBExecutor() { return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( new ThreadFactoryBuilder().setNameFormat("DB-Worker-Thread-%d").build())); @@ -527,17 +534,17 @@ public final class ImageGalleryController { } incrementQueueSize(); dbExecutor.submit(bgTask).addListener(this::decrementQueueSize, MoreExecutors.directExecutor()); - + } - + private void incrementQueueSize() { Platform.runLater(() -> dbTaskQueueSize.set(dbTaskQueueSize.get() + 1)); } - + private void decrementQueueSize() { Platform.runLater(() -> dbTaskQueueSize.set(dbTaskQueueSize.get() - 1)); } - + @Nullable synchronized public DrawableFile getFileFromId(Long fileID) throws TskCoreException { if (Objects.isNull(db)) { @@ -546,13 +553,13 @@ public final class ImageGalleryController { } return db.getFileFromID(fileID); } - + public void setStacks(StackPane fullUIStack, StackPane centralStack) { fullUIStackPane = fullUIStack; this.centralStackPane = centralStack; Platform.runLater(this::checkForGroups); } - + public synchronized void setToolbar(Toolbar toolbar) { if (this.toolbar != null) { throw new IllegalStateException("Can not set the toolbar a second time!"); @@ -562,7 +569,7 @@ public final class ImageGalleryController { // RAMAN TBD: bind filterByDataSourceId to the data source dropdown in the toolbar. } - + public ReadOnlyDoubleProperty regroupProgress() { return groupManager.regroupProgress(); } @@ -579,34 +586,34 @@ public final class ImageGalleryController { IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener()); Case.addPropertyChangeListener(new CaseEventListener()); } - + public HashSetManager getHashSetManager() { return hashSetManager; } - + public CategoryManager getCategoryManager() { return categoryManager; } - + public DrawableTagsManager getTagsManager() { return tagsManager; } - + public void setShowTree(Runnable showTree) { this.showTree = showTree; } - + public UndoRedoManager getUndoManager() { return undoManager; } - + public ReadOnlyIntegerProperty getDBTasksQueueSizeProperty() { return dbTaskQueueSize.getReadOnlyProperty(); } - + public synchronized SleuthkitCase getSleuthKitCase() { return sleuthKitCase; - + } /** @@ -615,56 +622,56 @@ public final class ImageGalleryController { @NbBundle.Messages({"ImageGalleryController.InnerTask.progress.name=progress", "ImageGalleryController.InnerTask.message.name=status"}) static public abstract class BackgroundTask implements Runnable, Cancellable { - + private final SimpleObjectProperty state = new SimpleObjectProperty<>(Worker.State.READY); private final SimpleDoubleProperty progress = new SimpleDoubleProperty(this, Bundle.ImageGalleryController_InnerTask_progress_name()); private final SimpleStringProperty message = new SimpleStringProperty(this, Bundle.ImageGalleryController_InnerTask_message_name()); - + protected BackgroundTask() { } - + public double getProgress() { return progress.get(); } - + public final void updateProgress(Double workDone) { this.progress.set(workDone); } - + public String getMessage() { return message.get(); } - + public final void updateMessage(String Status) { this.message.set(Status); } - + public SimpleDoubleProperty progressProperty() { return progress; } - + public SimpleStringProperty messageProperty() { return message; } - + public Worker.State getState() { return state.get(); } - + public ReadOnlyObjectProperty stateProperty() { return new ReadOnlyObjectWrapper<>(state.get()); } - + @Override public synchronized boolean cancel() { updateState(Worker.State.CANCELLED); return true; } - + protected void updateState(Worker.State newState) { state.set(newState); } - + protected synchronized boolean isCancelled() { return getState() == Worker.State.CANCELLED; } @@ -674,18 +681,18 @@ public final class ImageGalleryController { * Abstract base class for tasks associated with a file in the database */ static abstract class FileTask extends BackgroundTask { - + private final AbstractFile file; private final DrawableDB taskDB; - + public DrawableDB getTaskDB() { return taskDB; } - + public AbstractFile getFile() { return file; } - + public FileTask(AbstractFile f, DrawableDB taskDB) { super(); this.file = f; @@ -697,7 +704,7 @@ public final class ImageGalleryController { * task that updates one file in database with results from ingest */ static private class UpdateFileTask extends FileTask { - + UpdateFileTask(AbstractFile f, DrawableDB taskDB) { super(f, taskDB); } @@ -724,7 +731,7 @@ public final class ImageGalleryController { * task that updates one file in database with results from ingest */ static private class RemoveFileTask extends FileTask { - + RemoveFileTask(AbstractFile f, DrawableDB taskDB) { super(f, taskDB); } @@ -745,7 +752,7 @@ public final class ImageGalleryController { } } } - + @NbBundle.Messages({"BulkTask.committingDb.status=committing image/video database", "BulkTask.stopCopy.status=Stopping copy to drawable db task.", "BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."}) @@ -754,36 +761,36 @@ public final class ImageGalleryController { * a given data source, into the Image gallery DB. */ abstract static private class BulkTransferTask extends BackgroundTask { - + static private final String FILE_EXTENSION_CLAUSE = "(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 + "') "; - + final String DRAWABLE_QUERY; final String DATASOURCE_CLAUSE; - + final ImageGalleryController controller; final DrawableDB taskDB; final SleuthkitCase tskCase; final long dataSourceObjId; - + ProgressHandle progressHandle; private boolean taskCompletionStatus; - + BulkTransferTask(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { this.controller = controller; this.taskDB = taskDB; this.tskCase = tskCase; this.dataSourceObjId = dataSourceObjId; - + DATASOURCE_CLAUSE = " (data_source_obj_id = " + dataSourceObjId + ") "; - + DRAWABLE_QUERY = DATASOURCE_CLAUSE + " AND ( " @@ -811,24 +818,24 @@ public final class ImageGalleryController { List getFiles() throws TskCoreException { return tskCase.findAllFilesWhere(DRAWABLE_QUERY); } - + abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) throws TskCoreException; - + @Override public void run() { progressHandle = getInitialProgressHandle(); progressHandle.start(); updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status()); - + DrawableDB.DrawableTransaction drawableDbTransaction = null; CaseDbTransaction caseDbTransaction = null; try { //grab all files with supported extension or detected mime types final List files = getFiles(); progressHandle.switchToDeterminate(files.size()); - + taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS); - + updateProgress(0.0); taskCompletionStatus = true; int workDone = 0; @@ -841,27 +848,27 @@ public final class ImageGalleryController { logger.log(Level.WARNING, "Task cancelled or interrupted: not all contents may be transfered to drawable database."); //NON-NLS taskCompletionStatus = false; progressHandle.finish(); - + break; } - + processFile(f, drawableDbTransaction, caseDbTransaction); - + workDone++; progressHandle.progress(f.getName(), workDone); updateProgress(workDone - 1 / (double) files.size()); updateMessage(f.getName()); } - + progressHandle.finish(); progressHandle = ProgressHandle.createHandle(Bundle.BulkTask_committingDb_status()); updateMessage(Bundle.BulkTask_committingDb_status()); updateProgress(1.0); - + progressHandle.start(); caseDbTransaction.commit(); taskDB.commitTransaction(drawableDbTransaction, true); - + } catch (TskCoreException ex) { if (null != drawableDbTransaction) { taskDB.rollbackTransaction(drawableDbTransaction); @@ -889,9 +896,9 @@ public final class ImageGalleryController { } cleanup(taskCompletionStatus); } - + abstract ProgressHandle getInitialProgressHandle(); - + protected void setTaskCompletionStatus(boolean status) { taskCompletionStatus = status; } @@ -908,26 +915,26 @@ public final class ImageGalleryController { "CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.", "CopyAnalyzedFiles.errPopulating.errMsg=There was an error populating Image Gallery database."}) private class CopyAnalyzedFiles extends BulkTransferTask { - + CopyAnalyzedFiles(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { super(dataSourceObjId, controller, taskDB, tskCase); } - + @Override protected void cleanup(boolean success) { // at the end of the task, set the stale status based on the // cumulative status of all data sources controller.setStale(isDataSourcesTableStale()); } - + @Override void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDbTransaction) throws TskCoreException { final boolean known = f.getKnown() == TskData.FileKnown.KNOWN; - + if (known) { taskDB.removeFile(f.getId(), tr); //remove known files } else { - + try { //supported mimetype => analyzed if (null != f.getMIMEType() && FileTypeUtils.hasDrawableMIMEType(f)) { @@ -947,7 +954,7 @@ public final class ImageGalleryController { } } } - + @Override @NbBundle.Messages({"CopyAnalyzedFiles.populatingDb.status=populating analyzed image/video database",}) ProgressHandle getInitialProgressHandle() { @@ -973,25 +980,25 @@ public final class ImageGalleryController { PrePopulateDataSourceFiles(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { super(dataSourceObjId, controller, taskDB, tskCase); } - + @Override protected void cleanup(boolean success) { } - + @Override void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) { taskDB.insertFile(DrawableFile.create(f, false, false), tr, caseDBTransaction); } - + @Override @NbBundle.Messages({"PrePopulateDataSourceFiles.prepopulatingDb.status=prepopulating image/video database",}) ProgressHandle getInitialProgressHandle() { return ProgressHandle.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this); } } - + private class IngestModuleEventListener implements PropertyChangeListener { - + @Override public void propertyChange(PropertyChangeEvent evt) { if (RuntimeProperties.runningWithGUI() == false) { @@ -1022,7 +1029,7 @@ public final class ImageGalleryController { * getOldValue has fileID getNewValue has * {@link Abstractfile} */ - + AbstractFile file = (AbstractFile) evt.getNewValue(); // only process individual files in realtime on the node that is running the ingest @@ -1053,9 +1060,9 @@ public final class ImageGalleryController { } } } - + private class CaseEventListener implements PropertyChangeListener { - + @Override public void propertyChange(PropertyChangeEvent evt) { if (RuntimeProperties.runningWithGUI() == false) { @@ -1092,7 +1099,7 @@ public final class ImageGalleryController { } } break; - + case CONTENT_TAG_ADDED: final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt; if (getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) { @@ -1113,7 +1120,7 @@ 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. " @@ -1128,15 +1135,15 @@ public final class ImageGalleryController { // 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(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java index c183116e0f..c7c3d707b0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java @@ -34,6 +34,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.services.TagsManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.DhsImageCategory; +import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TagName; @@ -58,7 +59,7 @@ public final class DrawableTagsManager { private final TagName bookmarkTagName; /** - * Used to distribute {@link TagsChangeEvent}s + * Used to distribute TagsChangeEvents */ private final EventBus tagsEventBus = new AsyncEventBus( @@ -70,10 +71,10 @@ public final class DrawableTagsManager { }) .build())); - public DrawableTagsManager(TagsManager autopsyTagsManager) throws TskCoreException { - this.autopsyTagsManager = autopsyTagsManager; - followUpTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.followUp")); - bookmarkTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.bookMark")); + public DrawableTagsManager(ImageGalleryController controller) throws TskCoreException { + this.autopsyTagsManager = controller.getAutopsyCase().getServices().getTagsManager(); + followUpTagName = getTagName(Bundle.DrawableTagsManager_followUp()); + bookmarkTagName = getTagName(Bundle.DrawableTagsManager_bookMark()); } /** @@ -125,6 +126,8 @@ public final class DrawableTagsManager { * * @return All the TagNames that are not categories, in alphabetical order * by displayName. + * + * @throws org.sleuthkit.datamodel.TskCoreException */ public List getNonCategoryTagNames() throws TskCoreException { From a6e5e43d343d6079ce85657acf68163a93735844 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 4 Sep 2018 09:49:56 +0200 Subject: [PATCH 58/84] codacy fixes --- .../autopsy/imagegallery/actions/NextUnseenGroup.java | 1 - .../autopsy/imagegallery/datamodel/DrawableTagsManager.java | 1 - .../autopsy/imagegallery/datamodel/grouping/GroupManager.java | 2 +- .../sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java | 4 +--- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index ae5655c258..acc46f2f26 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.imagegallery.actions; import java.util.Optional; import javafx.application.Platform; import javafx.beans.Observable; -import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.scene.image.Image; import javafx.scene.image.ImageView; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java index 4cbd4bf7d3..aea9b1bfcc 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java @@ -29,7 +29,6 @@ import javafx.scene.Node; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 4425f1e9c6..36d8e49f0a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -145,7 +145,7 @@ public class GroupManager { regroup(dataSource, groupBy, sortBy, sortOrder, true); } - DrawableDB getDB() { + private DrawableDB getDB() { return controller.getDatabase(); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java index e3be8953ab..4ac6fc3996 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2016 Basis Technology Corp. + * Copyright 2016-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,8 +24,6 @@ import java.util.Optional; import java.util.function.Function; import javafx.application.Platform; import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.control.SelectionModel; import javafx.scene.control.Tab; From 8875793dfb88f55eeecd4839668d704819d60c16 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 4 Sep 2018 09:37:38 -0400 Subject: [PATCH 59/84] Cleanup --- .../sleuthkit/autopsy/directorytree/SelectionContext.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java b/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java index bf1d5db9fe..de84a49e85 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/SelectionContext.java @@ -28,8 +28,8 @@ import static org.sleuthkit.autopsy.directorytree.Bundle.*; enum SelectionContext { DATA_SOURCES(SelectionContext_dataSources()), VIEWS(SelectionContext_views()), - OTHER(""), - DATA_SOURCE_FILES(SelectionContext_dataSourceFiles()); // Subnode of another node. + OTHER(""), // Subnode of another node. + DATA_SOURCE_FILES(SelectionContext_dataSourceFiles()); private final String displayName; @@ -68,7 +68,7 @@ enum SelectionContext { } else { // In Group by Data Source mode, the node under root is the data source name, and // under that is Data Source Files, Views, or Results. Before moving up the tree, check - // if one of those applies + // if one of those applies. if (n.getParentNode().getParentNode().getParentNode() == null) { SelectionContext context = SelectionContext.getContextFromName(n.getDisplayName()); if (context != SelectionContext.OTHER) { From 39a3a955ceec452a3c6b3938e8b4dac3f2bf0c0f Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 4 Sep 2018 14:02:12 +0200 Subject: [PATCH 60/84] refactor how ImageGalleryController and ImageGalleryTopComponent are instantiated and interact. --- .../imagegallery/ImageGalleryController.java | 406 ++---------------- .../imagegallery/ImageGalleryModule.java | 246 ++++++++++- .../ImageGalleryOptionsPanel.java | 18 +- .../ImageGalleryTopComponent.java | 288 +++++++++---- .../autopsy/imagegallery/OnStart.java | 19 +- .../autopsy/imagegallery/ThumbnailCache.java | 16 +- .../imagegallery/actions/NextUnseenGroup.java | 15 +- .../imagegallery/actions/OpenAction.java | 53 ++- .../datamodel/CategoryManager.java | 29 +- .../imagegallery/datamodel/DrawableFile.java | 40 +- .../datamodel/HashSetManager.java | 14 +- .../datamodel/grouping/DrawableGroup.java | 18 +- .../datamodel/grouping/GroupManager.java | 225 ++++------ .../autopsy/imagegallery/gui/Toolbar.java | 9 +- .../gui/drawableviews/DrawableTile.java | 6 +- .../gui/drawableviews/DrawableUIBase.java | 2 +- .../gui/drawableviews/GroupPane.java | 49 ++- .../gui/drawableviews/MetaDataPane.java | 10 +- .../imagegallery/gui/navpanel/NavPanel.java | 2 +- 19 files changed, 702 insertions(+), 763 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index e0f253e491..c4ef95317c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.imagegallery; import com.google.common.util.concurrent.ListeningExecutorService; 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; @@ -33,10 +32,10 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.Observable; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.ReadOnlyDoubleProperty; -import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.ReadOnlyIntegerWrapper; import javafx.beans.property.ReadOnlyObjectProperty; @@ -55,24 +54,19 @@ import javafx.scene.layout.CornerRadii; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; +import javax.annotation.Nonnull; import javax.annotation.Nullable; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import org.netbeans.api.progress.ProgressHandle; import org.openide.util.Cancellable; -import org.openide.util.Exceptions; 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; -import org.sleuthkit.autopsy.core.RuntimeProperties; 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; @@ -87,7 +81,6 @@ import org.sleuthkit.autopsy.imagegallery.gui.Toolbar; import org.sleuthkit.autopsy.ingest.IngestManager; 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.SleuthkitCase.CaseDbTransaction; @@ -101,7 +94,6 @@ import org.sleuthkit.datamodel.TskData; public final class ImageGalleryController { private static final Logger logger = Logger.getLogger(ImageGalleryController.class.getName()); - private static ImageGalleryController instance; /** * true if Image Gallery should listen to ingest events, false if it should @@ -113,7 +105,7 @@ public final class ImageGalleryController { private final ReadOnlyBooleanWrapper stale = new ReadOnlyBooleanWrapper(false); private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false); - private final ReadOnlyDoubleWrapper thumbnailSize = new ReadOnlyDoubleWrapper(100); + private final SimpleDoubleProperty thumbnailSize = new SimpleDoubleProperty(100); private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false); private final ReadOnlyIntegerWrapper dbTaskQueueSize = new ReadOnlyIntegerWrapper(0); @@ -121,23 +113,12 @@ public final class ImageGalleryController { private final History historyManager = new History<>(); private final UndoRedoManager undoManager = new UndoRedoManager(); - private final GroupManager groupManager = new GroupManager(this); - private final HashSetManager hashSetManager = new HashSetManager(); - private final CategoryManager categoryManager = new CategoryManager(this); + private final ThumbnailCache thumbnailCache = new ThumbnailCache(this); + private final GroupManager groupManager; + private final HashSetManager hashSetManager; + private final CategoryManager categoryManager; private DrawableTagsManager tagsManager; - private Runnable showTree; - private Toolbar toolbar; - private StackPane fullUIStackPane; - private StackPane centralStackPane; - private Node infoOverlay; - private final Region infoOverLayBackground = new Region() { - { - setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY))); - setOpacity(.4); - } - }; - private ListeningExecutorService dbExecutor; private Case autopsyCase; @@ -148,17 +129,6 @@ public final class ImageGalleryController { private SleuthkitCase sleuthKitCase; private DrawableDB db; - public static synchronized ImageGalleryController getDefault() { - if (instance == null) { - try { - instance = new ImageGalleryController(); - } catch (NoClassDefFoundError error) { - Exceptions.printStackTrace(error); - } - } - return instance; - } - public ReadOnlyBooleanProperty getMetaDataCollapsed() { return metaDataCollapsed.getReadOnlyProperty(); } @@ -167,8 +137,8 @@ public final class ImageGalleryController { this.metaDataCollapsed.set(metaDataCollapsed); } - public ReadOnlyDoubleProperty thumbnailSizeProperty() { - return thumbnailSize.getReadOnlyProperty(); + public DoubleProperty thumbnailSize() { + return thumbnailSize; } public GroupViewState getViewState() { @@ -223,7 +193,23 @@ public final class ImageGalleryController { return stale.get(); } - private ImageGalleryController() { + ImageGalleryController(@Nonnull Case newCase) throws TskCoreException { + + this.autopsyCase = Objects.requireNonNull(newCase); + this.sleuthKitCase = newCase.getSleuthkitCase(); + this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(newCase), this); + + setListeningEnabled(ImageGalleryModule.isEnabledforCase(newCase)); + setStale(ImageGalleryModule.isDrawableDBStale(newCase)); + + groupManager = new GroupManager(this); + tagsManager = new DrawableTagsManager(this); + hashSetManager = new HashSetManager(db); + categoryManager = new CategoryManager(this); + tagsManager.registerListener(groupManager); + tagsManager.registerListener(categoryManager); + shutDownDBExecutor(); + dbExecutor = getNewDBExecutor(); // listener for the boolean property about when IG is listening / enabled listeningEnabled.addListener((observable, wasPreviouslyEnabled, isEnabled) -> { @@ -242,16 +228,12 @@ public final class ImageGalleryController { } }); - groupManager.getAnalyzedGroups().addListener((Observable o) -> checkForGroups()); - viewState().addListener((Observable observable) -> { //when the viewed group changes, clear the selection and the undo/redo history selectionModel.clearSelection(); undoManager.clear(); }); - regroupDisabled.addListener(observable -> checkForGroups()); - IngestManager ingestManager = IngestManager.getInstance(); PropertyChangeListener ingestEventHandler = propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled); @@ -271,10 +253,7 @@ public final class ImageGalleryController { } @ThreadConfined(type = ThreadConfined.ThreadType.ANY) - public void advance(GroupViewState newState, boolean forceShowTree) { - if (forceShowTree && showTree != null) { - showTree.run(); - } + public void advance(GroupViewState newState) { historyManager.advance(newState); } @@ -291,88 +270,6 @@ public final class ImageGalleryController { regroupDisabled.set((dbTaskQueueSize.get() > 0) || IngestManager.getInstance().isIngestRunning()); } - /** - * Check if there are any fully analyzed groups available from the - * GroupManager and remove blocking progress spinners if there are. If there - * aren't, add a blocking progress spinner with appropriate message. - */ - @NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. " - + " No groups will be available until ingest is finished and listening is re-enabled.", - "ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.", - "ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.", - "ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. " - + " No groups will be available until ingest is finished and listening is re-enabled.", - "ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.", - "ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:" - + " the current Group By setting resulted in no groups, " - + "or no groups are fully analyzed but ingest is not running."}) - synchronized private void checkForGroups() { - if (Case.isCaseOpen()) { - if (groupManager.getAnalyzedGroups().isEmpty()) { - if (IngestManager.getInstance().isIngestRunning()) { - if (listeningEnabled.get()) { - replaceNotification(centralStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), - new ProgressIndicator())); - } else { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); - } - - } else if (dbTaskQueueSize.get() > 0) { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), - new ProgressIndicator())); - } else if (db != null) { - try { - if (db.countAllFiles() <= 0) { - // there are no files in db - if (listeningEnabled.get()) { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5())); - } else { - replaceNotification(fullUIStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4())); - } - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error counting files in drawable db.", ex); - } - - } else if (false == groupManager.isRegrouping()) { - replaceNotification(centralStackPane, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6())); - } - - } else { - Platform.runLater(this::clearNotification); - } - } - } - - @ThreadConfined(type = ThreadConfined.ThreadType.JFX) - private void clearNotification() { - //remove the ingest spinner - if (fullUIStackPane != null) { - fullUIStackPane.getChildren().remove(infoOverlay); - } - //remove the ingest spinner - if (centralStackPane != null) { - centralStackPane.getChildren().remove(infoOverlay); - } - } - - private void replaceNotification(StackPane stackPane, Node newNode) { - Platform.runLater(() -> { - clearNotification(); - - infoOverlay = new StackPane(infoOverLayBackground, newNode); - if (stackPane != null) { - stackPane.getChildren().add(infoOverlay); - } - }); - } - /** * configure the controller for a specific case. * @@ -380,36 +277,6 @@ public final class ImageGalleryController { * * @throws org.sleuthkit.datamodel.TskCoreException */ - public synchronized void setCase(Case theNewCase) throws TskCoreException { - if (null == theNewCase) { - reset(); - } else { - this.autopsyCase = theNewCase; - this.sleuthKitCase = theNewCase.getSleuthkitCase(); - this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), this); - - setListeningEnabled(ImageGalleryModule.isEnabledforCase(theNewCase)); - setStale(ImageGalleryModule.isDrawableDBStale(theNewCase)); - - // if we add this line icons are made as files are analyzed rather than on demand. - // db.addUpdatedFileListener(IconCache.getDefault()); - historyManager.clear(); - groupManager.reset(); - hashSetManager.setDb(db); - categoryManager.setDb(db); - - if (tagsManager != null) { - tagsManager.unregisterListener(groupManager); - tagsManager.unregisterListener(categoryManager); - } - tagsManager = new DrawableTagsManager(this); - tagsManager.registerListener(groupManager); - tagsManager.registerListener(categoryManager); - shutDownDBExecutor(); - dbExecutor = getNewDBExecutor(); - } - } - /** * Rebuilds the DrawableDB database. * @@ -417,19 +284,20 @@ public final class ImageGalleryController { public void rebuildDB() { // queue a rebuild task for each stale data source getStaleDataSourceIds().forEach((dataSourceObjId) -> { - queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, instance, db, sleuthKitCase)); + queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, this, db, sleuthKitCase)); }); } /** * reset the state of the controller (eg if the case is closed) */ - public synchronized void reset() { - logger.info("resetting ImageGalleryControler to initial state."); //NON-NLS + public synchronized void shutDown() { + logger.info("Closing ImageGalleryControler for case."); //NON-NLS + autopsyCase = null; selectionModel.clearSelection(); setListeningEnabled(false); - ThumbnailCache.getDefault().clearCache(); + thumbnailCache.clearCache(); historyManager.clear(); groupManager.reset(); @@ -438,14 +306,11 @@ public final class ImageGalleryController { tagsManager = null; shutDownDBExecutor(); - if (toolbar != null) { - toolbar.reset(); - } - if (db != null) { db.closeDBCon(); } db = null; + } /** @@ -454,7 +319,7 @@ public final class ImageGalleryController { * @return true if datasources table is stale */ boolean isDataSourcesTableStale() { - return (getStaleDataSourceIds().isEmpty() == false); + return isNotEmpty(getStaleDataSourceIds()); } /** @@ -554,22 +419,6 @@ public final class ImageGalleryController { return db.getFileFromID(fileID); } - public void setStacks(StackPane fullUIStack, StackPane centralStack) { - fullUIStackPane = fullUIStack; - this.centralStackPane = centralStack; - Platform.runLater(this::checkForGroups); - } - - public synchronized void setToolbar(Toolbar toolbar) { - if (this.toolbar != null) { - throw new IllegalStateException("Can not set the toolbar a second time!"); - } - this.toolbar = toolbar; - thumbnailSize.bind(toolbar.thumbnailSizeProperty()); - - // RAMAN TBD: bind filterByDataSourceId to the data source dropdown in the toolbar. - } - public ReadOnlyDoubleProperty regroupProgress() { return groupManager.regroupProgress(); } @@ -579,12 +428,7 @@ public final class ImageGalleryController { * get setup as early as possible, and do other setup stuff. */ void onStart() { - Platform.setImplicitExit(false); - logger.info("setting up ImageGallery listeners"); //NON-NLS - IngestManager.getInstance().addIngestJobEventListener(new IngestJobEventListener()); - IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener()); - Case.addPropertyChangeListener(new CaseEventListener()); } public HashSetManager getHashSetManager() { @@ -599,10 +443,6 @@ public final class ImageGalleryController { return tagsManager; } - public void setShowTree(Runnable showTree) { - this.showTree = showTree; - } - public UndoRedoManager getUndoManager() { return undoManager; } @@ -616,6 +456,10 @@ public final class ImageGalleryController { } + public ThumbnailCache getThumbsCache() { + return thumbnailCache; + } + /** * Abstract base class for task to be done on {@link DBWorkerThread} */ @@ -703,7 +547,7 @@ public final class ImageGalleryController { /** * task that updates one file in database with results from ingest */ - static private class UpdateFileTask extends FileTask { + static class UpdateFileTask extends FileTask { UpdateFileTask(AbstractFile f, DrawableDB taskDB) { super(f, taskDB); @@ -730,7 +574,7 @@ public final class ImageGalleryController { /** * task that updates one file in database with results from ingest */ - static private class RemoveFileTask extends FileTask { + static class RemoveFileTask extends FileTask { RemoveFileTask(AbstractFile f, DrawableDB taskDB) { super(f, taskDB); @@ -760,7 +604,7 @@ public final class ImageGalleryController { * Base abstract class for various methods of copying image files data, for * a given data source, into the Image gallery DB. */ - abstract static private class BulkTransferTask extends BackgroundTask { + abstract static class BulkTransferTask extends BackgroundTask { static private final String FILE_EXTENSION_CLAUSE = "(extension LIKE '" //NON-NLS @@ -914,7 +758,7 @@ public final class ImageGalleryController { @NbBundle.Messages({"CopyAnalyzedFiles.committingDb.status=committing image/video database", "CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.", "CopyAnalyzedFiles.errPopulating.errMsg=There was an error populating Image Gallery database."}) - private class CopyAnalyzedFiles extends BulkTransferTask { + class CopyAnalyzedFiles extends BulkTransferTask { CopyAnalyzedFiles(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { super(dataSourceObjId, controller, taskDB, tskCase); @@ -971,7 +815,7 @@ public final class ImageGalleryController { * netbeans and ImageGallery progress/status */ @NbBundle.Messages({"PrePopulateDataSourceFiles.committingDb.status=committing image/video database"}) - static private class PrePopulateDataSourceFiles extends BulkTransferTask { + static class PrePopulateDataSourceFiles extends BulkTransferTask { /** * @@ -997,166 +841,4 @@ public final class ImageGalleryController { } } - private class IngestModuleEventListener implements PropertyChangeListener { - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (RuntimeProperties.runningWithGUI() == false) { - /* - * Running in "headless" mode, no need to process any events. - * This cannot be done earlier because the switch to core - * components inactive may not have been made at start up. - */ - IngestManager.getInstance().removeIngestModuleEventListener(this); - return; - } - switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) { - case CONTENT_CHANGED: - //TODO: do we need to do anything here? -jm - case DATA_ADDED: - /* - * we could listen to DATA events and progressivly update - * files, and get data from DataSource ingest modules, but - * given that most modules don't post new artifacts in the - * events and we would have to query for them, without - * knowing which are the new ones, we just ignore these - * events for now. The relevant data should all be captured - * by file done event, anyways -jm - */ - break; - case FILE_DONE: - /** - * getOldValue has fileID getNewValue has - * {@link Abstractfile} - */ - - AbstractFile file = (AbstractFile) evt.getNewValue(); - - // 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) { - 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."); - } - } - } - } - break; - } - } - } - - private class CaseEventListener implements PropertyChangeListener { - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (RuntimeProperties.runningWithGUI() == false) { - /* - * Running in "headless" mode, no need to process any events. - * This cannot be done earlier because the switch to core - * components inactive may not have been made at start up. - */ - Case.removePropertyChangeListener(this); - return; - } - switch (Case.Events.valueOf(evt.getPropertyName())) { - case CURRENT_CASE: - Case newCase = (Case) evt.getNewValue(); - if (newCase == null) { // case is closing - //close window, reset everything - SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent); - reset(); - } else { - try { - // a new case has been opened - setCase(newCase); //connect db, groupmanager, start worker thread - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error changing case in ImageGallery.", ex); - } - } - break; - case DATA_SOURCE_ADDED: - //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.getId(), ImageGalleryController.this, getDatabase(), getSleuthKitCase())); - } - } - break; - - case CONTENT_TAG_ADDED: - final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt; - if (getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) { - getTagsManager().fireTagAddedEvent(tagAddedEvent); - } - break; - case CONTENT_TAG_DELETED: - final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) evt; - if (getDatabase().isInDB(tagDeletedEvent.getDeletedTagInfo().getContentID())) { - getTagsManager().fireTagDeletedEvent(tagDeletedEvent); - } - break; - } - } - } - - /** - * 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 - } - } - }); - } - } - } - } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java index 01b20eed77..046cc5e51d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java @@ -18,15 +18,29 @@ */ package org.sleuthkit.autopsy.imagegallery; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.logging.Level; +import javafx.application.Platform; +import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; import static org.apache.commons.lang3.StringUtils.isNotBlank; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; +import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB; +import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -34,15 +48,45 @@ import org.sleuthkit.datamodel.TskData; @NbBundle.Messages({"ImageGalleryModule.moduleName=Image Gallery"}) public class ImageGalleryModule { - private static final Logger LOGGER = Logger.getLogger(ImageGalleryModule.class.getName()); + private static final Logger logger = Logger.getLogger(ImageGalleryModule.class.getName()); private static final String MODULE_NAME = Bundle.ImageGalleryModule_moduleName(); + private static final Object controllerLock = new Object(); + private static ImageGalleryController controller; + + public static ImageGalleryController getController() throws NoCurrentCaseException { + synchronized (controllerLock) { + if (controller == null) { + try { + controller = new ImageGalleryController(Case.getCurrentCaseThrows()); + } catch (Exception ex) { + throw new NoCurrentCaseException("Error getting ImageGalleryController for the current case.", ex); + } + } + return controller; + } + } + + /** + * + * + * This method is invoked by virtue of the OnStart annotation on the OnStart + * class class + */ + static void onStart() { + Platform.setImplicitExit(false); + logger.info("Setting up ImageGallery listeners"); //NON-NLS + + IngestManager.getInstance().addIngestJobEventListener(new IngestJobEventListener()); + IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener()); + Case.addPropertyChangeListener(new CaseEventListener()); + } + static String getModuleName() { return MODULE_NAME; } - /** * get the Path to the Case's ImageGallery ModuleOutput subfolder; ie * ".../[CaseName]/ModuleOutput/Image Gallery/" @@ -83,17 +127,15 @@ public class ImageGalleryModule { * otherwise */ public static boolean isDrawableDBStale(Case c) { - if (c != null) { - return ImageGalleryController.getDefault().isDataSourcesTableStale(); - } else { - return false; + synchronized (controllerLock) { + if (controller != null) { + return controller.isDataSourcesTableStale(); + } else { + return false; + } } } - - - - /** * Is the given file 'supported' and not 'known'(nsrl hash hit). If so we * should include it in {@link DrawableDB} and UI @@ -106,4 +148,188 @@ public class ImageGalleryModule { public static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws TskCoreException, FileTypeDetector.FileTypeDetectorInitException { return (abstractFile.getKnown() != TskData.FileKnown.KNOWN) && FileTypeUtils.isDrawable(abstractFile); } + + static private class IngestModuleEventListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (RuntimeProperties.runningWithGUI() == false) { + /* + * Running in "headless" mode, no need to process any events. + * This cannot be done earlier because the switch to core + * components inactive may not have been made at start up. + */ + IngestManager.getInstance().removeIngestModuleEventListener(this); + return; + } + switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) { + case CONTENT_CHANGED: + //TODO: do we need to do anything here? -jm + case DATA_ADDED: + /* + * we could listen to DATA events and progressivly update + * files, and get data from DataSource ingest modules, but + * given that most modules don't post new artifacts in the + * events and we would have to query for them, without + * knowing which are the new ones, we just ignore these + * events for now. The relevant data should all be captured + * by file done event, anyways -jm + */ + break; + case FILE_DONE: + /** + * getOldValue has fileID getNewValue has + * {@link Abstractfile} + */ + + AbstractFile file = (AbstractFile) evt.getNewValue(); + + // 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) { + synchronized (controllerLock) { + if (controller != null) { + if (controller.isListeningEnabled()) { + if (file.isFile()) { + try { + + if (ImageGalleryModule.isDrawableAndNotKnown(file)) { + //this file should be included and we don't already know about it from hash sets (NSRL) + controller.queueDBTask(new ImageGalleryController.UpdateFileTask(file, controller.getDatabase())); + } 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 + controller.queueDBTask(new ImageGalleryController.RemoveFileTask(file, controller.getDatabase())); + } + + } catch (TskCoreException | FileTypeDetector.FileTypeDetectorInitException ex) { + 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."); + } + } + } + } + } + } + break; + } + } + } + + static private class CaseEventListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (RuntimeProperties.runningWithGUI() == false) { + /* + * Running in "headless" mode, no need to process any events. + * This cannot be done earlier because the switch to core + * components inactive may not have been made at start up. + */ + Case.removePropertyChangeListener(this); + return; + } + synchronized (controllerLock) { + switch (Case.Events.valueOf(evt.getPropertyName())) { + case CURRENT_CASE: + + // case has changes: close window, reset everything + SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent); + if (controller != null) { + controller.shutDown(); + controller = null; + } + + Case newCase = (Case) evt.getNewValue(); + if (newCase != null) { + // a new case has been opened: connect db, groupmanager, start worker thread + try { + controller = new ImageGalleryController(newCase); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error changing case in ImageGallery.", ex); + } + } + break; + + case DATA_SOURCE_ADDED: + //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 (controller.isListeningEnabled()) { + controller.queueDBTask(new ImageGalleryController.PrePopulateDataSourceFiles(newDataSource.getId(), controller, controller.getDatabase(), controller.getSleuthKitCase())); + } + } + + break; + + case CONTENT_TAG_ADDED: + final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt; + if (controller.getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) { + controller.getTagsManager().fireTagAddedEvent(tagAddedEvent); + } + + break; + case CONTENT_TAG_DELETED: + + final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) evt; + if (controller.getDatabase().isInDB(tagDeletedEvent.getDeletedTagInfo().getContentID())) { + controller.getTagsManager().fireTagDeletedEvent(tagDeletedEvent); + } + + break; + } + } + } + } + + /** + * Listener for Ingest Job events. + */ + static 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 + synchronized (controllerLock) { + if (controller != null) { + controller.setStale(true); + + SwingUtilities.invokeLater(() -> { + synchronized (controllerLock) { + if (controller.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: + controller.rebuildDB(); + break; + case JOptionPane.NO_OPTION: + case JOptionPane.CANCEL_OPTION: + default: + break; //do nothing + } + } + } + }); + } + } + } + } + } + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java index 7fbcaac1f0..5d14ad4b97 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java @@ -19,12 +19,10 @@ package org.sleuthkit.autopsy.imagegallery; import java.awt.event.ActionEvent; -import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.coreutils.Logger; /** * The Image/Video Gallery panel in the NetBeans provided Options Dialogs @@ -45,13 +43,8 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel { enabledForCaseBox.setEnabled(Case.isCaseOpen() && IngestManager.getInstance().isIngestRunning() == false); }); - enabledByDefaultBox.addActionListener((ActionEvent e) -> { - controller.changed(); - }); - - enabledForCaseBox.addActionListener((ActionEvent e) -> { - controller.changed(); - }); + enabledByDefaultBox.addActionListener(actionEvent -> controller.changed()); + enabledForCaseBox.addActionListener(actionEvent -> controller.changed()); } /** @@ -204,19 +197,18 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel { void store() { ImageGalleryPreferences.setEnabledByDefault(enabledByDefaultBox.isSelected()); - ImageGalleryController.getDefault().setListeningEnabled(enabledForCaseBox.isSelected()); + ImageGalleryPreferences.setGroupCategorizationWarningDisabled(groupCategorizationWarningBox.isSelected()); // If a case is open, save the per case setting try { Case openCase = Case.getCurrentCaseThrows(); + ImageGalleryModule.getController().setListeningEnabled(enabledForCaseBox.isSelected()); new PerCaseProperties(openCase).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.ENABLED, Boolean.toString(enabledForCaseBox.isSelected())); } catch (NoCurrentCaseException ex) { // It's not an error if there's no case open } - - - + } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index a1d2e773be..1e1cc7ad11 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -26,29 +26,45 @@ import java.util.Optional; import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; import javafx.embed.swing.JFXPanel; +import javafx.geometry.Insets; +import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.ChoiceDialog; +import javafx.scene.control.ProgressIndicator; import javafx.scene.control.SplitPane; import javafx.scene.control.TabPane; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.BorderPane; +import javafx.scene.layout.CornerRadii; import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; import javafx.stage.Modality; import javax.swing.SwingUtilities; +import org.apache.commons.collections4.CollectionUtils; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; +import org.openide.util.Exceptions; import org.openide.util.Lookup; +import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.openide.windows.Mode; import org.openide.windows.RetainLocation; import org.openide.windows.TopComponent; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager; import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils; +import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog; import org.sleuthkit.autopsy.imagegallery.gui.StatusBar; import org.sleuthkit.autopsy.imagegallery.gui.SummaryTablePane; import org.sleuthkit.autopsy.imagegallery.gui.Toolbar; @@ -56,6 +72,7 @@ import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.GroupPane; import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.MetaDataPane; import org.sleuthkit.autopsy.imagegallery.gui.navpanel.GroupTree; import org.sleuthkit.autopsy.imagegallery.gui.navpanel.HashHitGroupList; +import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.TskCoreException; @@ -87,7 +104,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl private final ExplorerManager em = new ExplorerManager(); private final Lookup lookup = (ExplorerUtils.createLookup(em, getActionMap())); - private final ImageGalleryController controller = ImageGalleryController.getDefault(); + private ImageGalleryController controller; private SplitPane splitPane; private StackPane centralStack; @@ -100,6 +117,14 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl private VBox leftPane; private Scene myScene; + private Node infoOverlay; + private final Region infoOverLayBackground = new Region() { + { + setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY))); + setOpacity(.4); + } + }; + /** * Returns whether the ImageGallery window is open or not. * @@ -129,62 +154,58 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl "ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.all=All", "ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.titleText=Image Gallery",}) public static void openTopComponent() { - //TODO:eventually move to this model, throwing away everything and rebuilding controller groupmanager etc for each case. - // synchronized (OpenTimelineAction.class) { - // if (timeLineController == null) { - // timeLineController = new TimeLineController(); - // LOGGER.log(Level.WARNING, "Failed to get TimeLineController from lookup. Instantiating one directly.S"); - // } - // } - // timeLineController.openTimeLine(); + final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); - if (tc != null) { - topComponentInitialized = true; - if (tc.isOpened()) { - tc.toFront(); - tc.requestActive(); - } else { - List dataSources = Collections.emptyList(); - ImageGalleryController controller = ((ImageGalleryTopComponent) tc).controller; - try { - dataSources = controller.getSleuthKitCase().getDataSources(); - } catch (TskCoreException tskCoreException) { - logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException); - } - if (dataSources.size() > 1 - && controller.getGroupManager().getGroupBy() == DrawableAttribute.PATH) { - Map dataSourceNames = new HashMap<>(); - dataSourceNames.put("All", null); - dataSources.forEach(dataSource -> dataSourceNames.put(dataSource.getName(), dataSource)); - - Platform.runLater(() -> { - ChoiceDialog d = new ChoiceDialog<>(null, dataSourceNames.keySet()); - d.setTitle(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_titleText()); - d.setHeaderText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_headerText()); - d.setContentText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_contentText()); - d.initModality(Modality.APPLICATION_MODAL); - GuiUtils.setDialogIcons(d); - - Optional dataSourceName = d.showAndWait(); - DataSource ds = dataSourceName.map(dataSourceNames::get).orElse(null); - GroupManager groupManager = controller.getGroupManager(); - groupManager.regroup(ds,groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true ); - - SwingUtilities.invokeLater(() -> { - tc.open(); - tc.toFront(); - tc.requestActive(); - }); - }); - } else { - SwingUtilities.invokeLater(() -> { - tc.open(); - tc.toFront(); - tc.requestActive(); - }); - } - } + if (tc == null) { + return; } + topComponentInitialized = true; + if (tc.isOpened()) { + showTopComponent(tc); + return; + } + + List dataSources = Collections.emptyList(); + ImageGalleryController controller = ((ImageGalleryTopComponent) tc).controller; + try { + dataSources = controller.getSleuthKitCase().getDataSources(); + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException); + } + if (dataSources.size() <= 1 + || controller.getGroupManager().getGroupBy() != DrawableAttribute.PATH) { + /* if there is only one datasource or the grouping is already set to + * something other than path , don't both to ask for datasource */ + showTopComponent(tc); + return; + } + + Map dataSourceNames = new HashMap<>(); + dataSourceNames.put("All", null); + dataSources.forEach(dataSource -> dataSourceNames.put(dataSource.getName(), dataSource)); + + Platform.runLater(() -> { + ChoiceDialog d = new ChoiceDialog<>(null, dataSourceNames.keySet()); + d.setTitle(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_titleText()); + d.setHeaderText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_headerText()); + d.setContentText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_contentText()); + d.initModality(Modality.APPLICATION_MODAL); + GuiUtils.setDialogIcons(d); + + Optional dataSourceName = d.showAndWait(); + DataSource ds = dataSourceName.map(dataSourceNames::get).orElse(null); + GroupManager groupManager = controller.getGroupManager(); + groupManager.regroup(ds, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true); + showTopComponent(tc); + }); + } + + public static void showTopComponent(TopComponent tc) { + SwingUtilities.invokeLater(() -> { + tc.open(); + tc.toFront(); + tc.requestActive(); + }); } public static void closeTopComponent() { @@ -200,45 +221,53 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl } } - public ImageGalleryTopComponent() { + public ImageGalleryTopComponent() throws NoCurrentCaseException { setName(Bundle.CTL_ImageGalleryTopComponent()); initComponents(); + setController(ImageGalleryModule.getController()); + } - Platform.runLater(() -> { - //initialize jfx ui - fullUIStack = new StackPane(); //this is passed into controller - myScene = new Scene(fullUIStack); - jfxPanel.setScene(myScene); + synchronized void setController(ImageGalleryController controller) { + if (this.controller != null) { + this.controller.shutDown(); + } + this.controller = controller; + Platform.runLater(() -> this.initJavaFXUI()); + } - groupPane = new GroupPane(controller); - centralStack = new StackPane(groupPane); //this is passed into controller - fullUIStack.getChildren().add(borderPane); - splitPane = new SplitPane(); - borderPane.setCenter(splitPane); - Toolbar toolbar = new Toolbar(controller); - borderPane.setTop(toolbar); - borderPane.setBottom(new StatusBar(controller)); + synchronized private void initJavaFXUI() { + //initialize jfx ui + fullUIStack = new StackPane(); //this is passed into controller + myScene = new Scene(fullUIStack); + jfxPanel.setScene(myScene); - metaDataTable = new MetaDataPane(controller); + groupPane = new GroupPane(controller); + centralStack = new StackPane(groupPane); //this is passed into controller + fullUIStack.getChildren().add(borderPane); + splitPane = new SplitPane(); + borderPane.setCenter(splitPane); + Toolbar toolbar = new Toolbar(controller); + borderPane.setTop(toolbar); + borderPane.setBottom(new StatusBar(controller)); - groupTree = new GroupTree(controller); - hashHitList = new HashHitGroupList(controller); + metaDataTable = new MetaDataPane(controller); + groupTree = new GroupTree(controller); + hashHitList = new HashHitGroupList(controller); - TabPane tabPane = new TabPane(groupTree, hashHitList); - tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE); - tabPane.setMinWidth(TabPane.USE_PREF_SIZE); - VBox.setVgrow(tabPane, Priority.ALWAYS); - leftPane = new VBox(tabPane, new SummaryTablePane(controller)); - SplitPane.setResizableWithParent(leftPane, Boolean.FALSE); - SplitPane.setResizableWithParent(groupPane, Boolean.TRUE); - SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE); - splitPane.getItems().addAll(leftPane, centralStack, metaDataTable); - splitPane.setDividerPositions(0.1, 1.0); + TabPane tabPane = new TabPane(groupTree, hashHitList); + tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE); + tabPane.setMinWidth(TabPane.USE_PREF_SIZE); + VBox.setVgrow(tabPane, Priority.ALWAYS); + leftPane = new VBox(tabPane, new SummaryTablePane(controller)); + SplitPane.setResizableWithParent(leftPane, Boolean.FALSE); + SplitPane.setResizableWithParent(groupPane, Boolean.TRUE); + SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE); + splitPane.getItems().addAll(leftPane, centralStack, metaDataTable); + splitPane.setDividerPositions(0.1, 1.0); - ImageGalleryController.getDefault().setStacks(fullUIStack, centralStack); - ImageGalleryController.getDefault().setToolbar(toolbar); - ImageGalleryController.getDefault().setShowTree(() -> tabPane.getSelectionModel().select(groupTree)); - }); + controller.getGroupManager().getAnalyzedGroups().addListener((Observable o) -> checkForGroups()); + controller.regroupDisabled().addListener((Observable observable) -> checkForGroups()); + checkForGroups(); } /** @@ -281,6 +310,11 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl @Override public void componentOpened() { super.componentOpened(); +// try { +// setController(ImageGalleryModule.getController()); +// } catch (NoCurrentCaseException ex) { +// logger.log(Level.WARNING, "ImageGellery opened with no open case.", ex); +// } WindowManager.getDefault().setTopComponentFloating(this, true); } @@ -293,4 +327,86 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl public Lookup getLookup() { return lookup; } + + /** + * Check if there are any fully analyzed groups available from the + * GroupManager and remove blocking progress spinners if there are. If there + * aren't, add a blocking progress spinner with appropriate message. + */ + @NbBundle.Messages({ + "ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. " + + " No groups will be available until ingest is finished and listening is re-enabled.", + "ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.", + "ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.", + "ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. " + + " No groups will be available until ingest is finished and listening is re-enabled.", + "ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.", + "ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:" + + " the current Group By setting resulted in no groups, " + + "or no groups are fully analyzed but ingest is not running."}) + synchronized private void checkForGroups() { + GroupManager groupManager = controller.getGroupManager(); + if (CollectionUtils.isNotEmpty(groupManager.getAnalyzedGroups())) { + Platform.runLater(this::clearNotification); + return; + } + + if (IngestManager.getInstance().isIngestRunning()) { + if (controller.isListeningEnabled()) { + replaceNotification(centralStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), + new ProgressIndicator())); + } else { + replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); + } + return; + } + if (controller.getDBTasksQueueSizeProperty().get() > 0) { + replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), + new ProgressIndicator())); + return; + } + try { + if (controller.getDatabase().countAllFiles() <= 0) { + // there are no files in db + if (controller.isListeningEnabled()) { + replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5())); + } else { + replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4())); + } + return; + } + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error counting files in the database.", tskCoreException); + } + + if (false == groupManager.isRegrouping()) { + replaceNotification(centralStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6())); + } + + } + + private void replaceNotification(StackPane stackPane, Node newNode) { + Platform.runLater(() -> { + clearNotification(); + + infoOverlay = new StackPane(infoOverLayBackground, newNode); + if (stackPane != null) { + stackPane.getChildren().add(infoOverlay); + } + }); + } + + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) + private void clearNotification() { + //remove the ingest spinner + fullUIStack.getChildren().remove(infoOverlay); + //remove the ingest spinner + centralStack.getChildren().remove(infoOverlay); + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/OnStart.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/OnStart.java index b222b2bc7c..cf928514dd 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/OnStart.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/OnStart.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,26 +18,19 @@ */ package org.sleuthkit.autopsy.imagegallery; -import org.sleuthkit.autopsy.coreutils.Logger; - /** - * - * The {@link org.openide.modules.OnStart} annotation tells NetBeans to invoke - * this class's {@link OnStart#run()} method + * The org.openide.modules.OnStart annotation tells NetBeans to invoke this + * class's run method. */ @org.openide.modules.OnStart public class OnStart implements Runnable { - static private final Logger LOGGER = Logger.getLogger(OnStart.class.getName()); - /** - * - * - * This method is invoked by virtue of the {@link OnStart} annotation on the - * {@link ImageGalleryModule} class + * This method is invoked by virtue of the OnStart annotation on the this + * class */ @Override public void run() { - ImageGalleryController.getDefault().onStart(); + ImageGalleryModule.onStart(); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java index 78b260e731..a3e8fedde3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2013-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -54,9 +54,13 @@ import org.sleuthkit.datamodel.TskCoreException; * TODO: this was only a singleton for convenience, convert this to * non-singleton class -jm? */ -public enum ThumbnailCache { +public class ThumbnailCache { - instance; + private final ImageGalleryController controller; + + public ThumbnailCache(ImageGalleryController controller) { + this.controller = controller; + } private static final int MAX_THUMBNAIL_SIZE = 300; @@ -71,10 +75,6 @@ public enum ThumbnailCache { .softValues() .expireAfterAccess(10, TimeUnit.MINUTES).build(); - public static ThumbnailCache getDefault() { - return instance; - } - /** * currently desired icon size. is bound in {@link Toolbar} */ @@ -109,7 +109,7 @@ public enum ThumbnailCache { @Nullable public Image get(Long fileID) { try { - return get(ImageGalleryController.getDefault().getFileFromId(fileID)); + return get(controller.getFileFromId(fileID)); } catch (TskCoreException ex) { LOGGER.log(Level.WARNING, "Failed to load thumbnail for file: " + fileID, ex.getCause()); //NON-NLS return null; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index 17366a5f36..8306b5137e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -21,14 +21,13 @@ package org.sleuthkit.autopsy.imagegallery.actions; import com.google.common.util.concurrent.MoreExecutors; import java.util.Optional; import javafx.application.Platform; -import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.collections.ObservableList; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import org.apache.commons.collections4.CollectionUtils; import org.controlsfx.control.action.Action; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager; @@ -38,7 +37,8 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; * Marks the currently displayed group as "seen" and advances to the next unseen * group */ -@NbBundle.Messages({"NextUnseenGroup.markGroupSeen=Mark Group Seen", +@NbBundle.Messages({ + "NextUnseenGroup.markGroupSeen=Mark Group Seen", "NextUnseenGroup.nextUnseenGroup=Next Unseen group"}) public class NextUnseenGroup extends Action { @@ -62,10 +62,7 @@ public class NextUnseenGroup extends Action { this.controller = controller; groupManager = controller.getGroupManager(); unSeenGroups = groupManager.getUnSeenGroups(); - unSeenGroups.addListener((Observable observable) -> { - updateButton(); - - }); + unSeenGroups.addListener((Observable observable) -> updateButton()); setEventHandler(event -> { //on fx-thread //if there is a group assigned to the view, mark it as seen @@ -81,8 +78,8 @@ public class NextUnseenGroup extends Action { private void advanceToNextUnseenGroup() { synchronized (groupManager) { - if (unSeenGroups.isEmpty() == false) { - controller.advance(GroupViewState.tile(unSeenGroups.get(0)), false); + if (CollectionUtils.isNotEmpty(unSeenGroups)) { + controller.advance(GroupViewState.tile(unSeenGroups.get(0))); } updateButton(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java index cfd56a0874..80575315b9 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java @@ -62,16 +62,16 @@ import org.sleuthkit.datamodel.TskCoreException; + "Choosing 'yes' will update the database and enable listening to future ingests.", "OpenAction.stale.confDlg.title=Image Gallery"}) public final class OpenAction extends CallableSystemAction { - + private static final Logger logger = Logger.getLogger(OpenAction.class.getName()); private static final String VIEW_IMAGES_VIDEOS = Bundle.CTL_OpenAction(); private static final long FILE_LIMIT = 6_000_000; - + private final PropertyChangeListener pcl; private final JMenuItem menuItem; private final JButton toolbarButton = new JButton(this.getName(), new ImageIcon(getClass().getResource("btn_icon_image_gallery_26.png"))); - + public OpenAction() { super(); toolbarButton.addActionListener(actionEvent -> performAction()); @@ -84,7 +84,7 @@ public final class OpenAction extends CallableSystemAction { Case.addPropertyChangeListener(pcl); this.setEnabled(false); } - + @Override public boolean isEnabled() { Case openCase; @@ -103,10 +103,10 @@ public final class OpenAction extends CallableSystemAction { */ @Override public Component getToolbarPresenter() { - + return toolbarButton; } - + @Override public JMenuItem getMenuPresenter() { return menuItem; @@ -123,7 +123,7 @@ public final class OpenAction extends CallableSystemAction { menuItem.setEnabled(value); toolbarButton.setEnabled(value); } - + @Override @SuppressWarnings("fallthrough") @NbBundle.Messages({"OpenAction.dialogTitle=Image Gallery"}) @@ -137,7 +137,7 @@ public final class OpenAction extends CallableSystemAction { logger.log(Level.SEVERE, "Exception while getting open case.", ex); return; } - + if (tooManyFiles()) { Platform.runLater(OpenAction::showTooManyFiles); setEnabled(false); @@ -147,18 +147,25 @@ public final class OpenAction extends CallableSystemAction { //drawable db is stale, ask what to do int answer = JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), Bundle.OpenAction_stale_confDlg_msg(), Bundle.OpenAction_stale_confDlg_title(), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); - + switch (answer) { case JOptionPane.YES_OPTION: - - // 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(); + /* 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. + */ try { + ImageGalleryController controller = ImageGalleryModule.getController(); + + if (currentCase.getCaseType() == Case.CaseType.SINGLE_USER_CASE) { + controller.setListeningEnabled(true); + } else { + controller.rebuildDB(); + } + } catch (NoCurrentCaseException noCurrentCaseException) { + logger.log(Level.WARNING, "There was no case open when Image Gallery was opened.", noCurrentCaseException); } //fall through @@ -173,7 +180,7 @@ public final class OpenAction extends CallableSystemAction { ImageGalleryTopComponent.openTopComponent(); } } - + private boolean tooManyFiles() { try { return FILE_LIMIT < Case.getCurrentCaseThrows().getSleuthkitCase().countFilesWhere("1 = 1"); @@ -185,7 +192,7 @@ public final class OpenAction extends CallableSystemAction { //if there is any doubt (no case, tskcore error, etc) just disable . return false; } - + @NbBundle.Messages({ "ImageGallery.showTooManyFiles.contentText=" + "There are too many files in the DB to ensure reasonable performance." @@ -200,17 +207,17 @@ public final class OpenAction extends CallableSystemAction { dialog.setHeaderText(Bundle.ImageGallery_showTooManyFiles_headerText()); dialog.showAndWait(); } - + @Override public String getName() { return VIEW_IMAGES_VIDEOS; } - + @Override public HelpCtx getHelpCtx() { return HelpCtx.DEFAULT_HELP; } - + @Override public boolean asynchronous() { return false; // run on edt diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java index 66aa6c578d..9c6ff136da 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java @@ -40,7 +40,6 @@ import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; - /** * Provides a cached view of the number of files per category, and fires * {@link CategoryChangeEvent}s when files are categorized. @@ -80,38 +79,26 @@ public class CategoryManager { * the count related methods go through this cache, which loads initial * values from the database if needed. */ - private final LoadingCache categoryCounts = - CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper)); + private final LoadingCache categoryCounts + = CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper)); /** * cached TagNames corresponding to Categories, looked up from * autopsyTagManager at initial request or if invalidated by case change. */ - private final LoadingCache catTagNameMap = - CacheBuilder.newBuilder().build(CacheLoader.from( - cat -> getController().getTagsManager().getTagName(cat) - )); + private final LoadingCache catTagNameMap + = CacheBuilder.newBuilder().build(CacheLoader.from( + cat -> getController().getTagsManager().getTagName(cat) + )); public CategoryManager(ImageGalleryController controller) { this.controller = controller; + this.db = controller.getDatabase(); } private ImageGalleryController getController() { return controller; } - /** - * assign a new db. the counts cache is invalidated and all subsequent db - * lookups go to the new db. - * - * Also clears the Category TagNames - * - * @param db - */ - synchronized public void setDb(DrawableDB db) { - this.db = db; - invalidateCaches(); - } - synchronized public void invalidateCaches() { categoryCounts.invalidateAll(); catTagNameMap.invalidateAll(); @@ -258,7 +245,7 @@ public class CategoryManager { //remove old category tag(s) if necessary for (ContentTag ct : tagsManager.getContentTags(addedTag.getContent())) { if (ct.getId() != addedTag.getId() - && CategoryManager.isCategoryTagName(ct.getName())) { + && CategoryManager.isCategoryTagName(ct.getName())) { try { tagsManager.deleteContentTag(ct); } catch (TskCoreException tskException) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index 34c9d5a4c5..921bb8f150 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -41,6 +41,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.FileTypeUtils; +import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule; import org.sleuthkit.autopsy.imagegallery.ThumbnailCache; import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; import org.sleuthkit.datamodel.AbstractFile; @@ -67,11 +68,17 @@ public abstract class DrawableFile { /** * Skip the database query if we have already determined the file type. + * + * @param file The underlying AbstractFile. + * @param analyzed Is the file analyzed. + * @param isVideo Is the file a video. + * + * @return */ - public static DrawableFile create(AbstractFile abstractFileById, boolean analyzed, boolean isVideo) { + public static DrawableFile create(AbstractFile file, boolean analyzed, boolean isVideo) { return isVideo - ? new VideoFile(abstractFileById, analyzed) - : new ImageFile(abstractFileById, analyzed); + ? new VideoFile(file, analyzed) + : new ImageFile(file, analyzed); } public static DrawableFile create(Long id, boolean analyzed) throws TskCoreException, NoCurrentCaseException { @@ -254,29 +261,6 @@ public abstract class DrawableFile { return getSleuthkitCase().getContentTagsByContent(file); } - @Deprecated - public Image getThumbnail() { - try { - return getThumbnailTask().get(); - } catch (InterruptedException | ExecutionException ex) { - return null; - } - - } - - public Task getThumbnailTask() { - return ThumbnailCache.getDefault().getThumbnailTask(this); - } - - @Deprecated //use non-blocking getReadFullSizeImageTask instead for most cases - public Image getFullSizeImage() { - try { - return getReadFullSizeImageTask().get(); - } catch (InterruptedException | ExecutionException ex) { - return null; - } - } - public Task getReadFullSizeImageTask() { Image image = (imageRef != null) ? imageRef.get() : null; if (image == null || image.isError()) { @@ -316,14 +300,14 @@ public abstract class DrawableFile { /** * Get the width of the visual content. - * + * * @return The width. */ abstract Double getWidth(); /** * Get the height of the visual content. - * + * * @return The height. */ abstract Double getHeight(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java index 9b3b3218f1..b73e3ef5d0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java @@ -15,6 +15,10 @@ import org.sleuthkit.datamodel.TskCoreException; */ public class HashSetManager { + public HashSetManager(DrawableDB db) { + this.db = db; + } + /** * The db that initial values are loaded from. */ @@ -25,16 +29,6 @@ public class HashSetManager { */ private final LoadingCache> hashSetCache = CacheBuilder.newBuilder().build(CacheLoader.from(this::getHashSetsForFileHelper)); - /** - * assign the given db to back this hashset manager. - * - * @param db - */ - public void setDb(DrawableDB db) { - this.db = db; - hashSetCache.invalidateAll(); - } - /** * helper method to load hashset hits for the given fileID from the db * diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java index 0b4940255f..226cdbc0f9 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java @@ -32,8 +32,11 @@ import javafx.beans.property.ReadOnlyLongWrapper; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; +import org.openide.util.Exceptions; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; +import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; @@ -76,7 +79,7 @@ public class DrawableGroup implements Comparable { } @SuppressWarnings("ReturnOfCollectionOrArrayField") - public synchronized ObservableList getFileIDs() { + public ObservableList getFileIDs() { return unmodifiableFileIDS; } @@ -121,11 +124,11 @@ public class DrawableGroup implements Comparable { if (hashSetHitsCount.get() < 0) { try { hashSetHitsCount.set(fileIDs.stream() - .map(fileID -> ImageGalleryController.getDefault().getHashSetManager().isInAnyHashSet(fileID)) + .map(ImageGalleryModule.getController().getHashSetManager()::isInAnyHashSet) .filter(Boolean::booleanValue) .count()); - } catch (IllegalStateException | NullPointerException ex) { - LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS + } catch (NoCurrentCaseException | IllegalStateException | NullPointerException ex) { + LOGGER.log(Level.WARNING, "Could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS } } return hashSetHitsCount.get(); @@ -139,10 +142,10 @@ public class DrawableGroup implements Comparable { public final synchronized long getUncategorizedCount() { if (uncatCount.get() < 0) { try { - uncatCount.set(ImageGalleryController.getDefault().getDatabase().getUncategorizedCount(fileIDs)); + uncatCount.set(ImageGalleryModule.getController().getDatabase().getUncategorizedCount(fileIDs)); - } catch (IllegalStateException | NullPointerException ex) { - LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS + } catch (NoCurrentCaseException | IllegalStateException | NullPointerException ex) { + LOGGER.log(Level.WARNING, "Could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS } } @@ -174,7 +177,6 @@ public class DrawableGroup implements Comparable { } } - synchronized void addFile(Long f) { if (fileIDs.contains(f) == false) { fileIDs.add(f); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index ecad734eb7..85c32161fa 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.imagegallery.datamodel.grouping; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.eventbus.Subscribe; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; @@ -37,7 +36,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; import java.util.Optional; import java.util.Set; import java.util.TreeSet; @@ -108,6 +106,8 @@ public class GroupManager { private final ImageGalleryController controller; + boolean isRegrouping; + /** list of all analyzed groups */ @GuardedBy("this") private final ObservableList analyzedGroups = FXCollections.observableArrayList(); @@ -137,10 +137,7 @@ public class GroupManager { private final ReadOnlyObjectWrapper dataSourceProp = new ReadOnlyObjectWrapper<>(null);//null indicates all datasources private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper(); - - private synchronized DrawableDB getDB() { - return controller.getDatabase(); - } + private final DrawableDB db; @SuppressWarnings("ReturnOfCollectionOrArrayField") public ObservableList getAnalyzedGroups() { @@ -159,6 +156,7 @@ public class GroupManager { */ public GroupManager(ImageGalleryController controller) { this.controller = controller; + this.db = controller.getDatabase(); } /** @@ -197,13 +195,8 @@ public class GroupManager { */ synchronized public Set> getGroupKeysForFileID(Long fileID) { try { - DrawableDB db = getDB(); - if (nonNull(db)) { - DrawableFile file = db.getFileFromID(fileID); - return getGroupKeysForFile(file); - } else { - Logger.getLogger(GroupManager.class.getName()).log(Level.WARNING, "Failed to load file with id: {0} from database. There is no database assigned.", fileID); //NON-NLS - } + DrawableFile file = db.getFileFromID(fileID); + return getGroupKeysForFile(file); } catch (TskCoreException ex) { Logger.getLogger(GroupManager.class.getName()).log(Level.SEVERE, "failed to load file with id: " + fileID + " from database", ex); //NON-NLS } @@ -224,6 +217,7 @@ public class GroupManager { synchronized public void reset() { if (groupByTask != null) { groupByTask.cancel(true); + isRegrouping = false; } setSortBy(GroupSortBy.GROUP_BY_VALUE); setGroupBy(DrawableAttribute.PATH); @@ -240,21 +234,7 @@ public class GroupManager { } synchronized public boolean isRegrouping() { - if (groupByTask == null) { - return false; - } - - switch (groupByTask.getState()) { - case READY: - case RUNNING: - case SCHEDULED: - return true; - case CANCELLED: - case FAILED: - case SUCCEEDED: - default: - return false; - } + return isRegrouping; } /** @@ -266,22 +246,16 @@ public class GroupManager { * @return A ListenableFuture that encapsulates saving the seen state to the * DB. */ - synchronized public ListenableFuture setGroupSeen(DrawableGroup group, boolean seen) { - DrawableDB db = getDB(); - if (nonNull(db)) { - return exec.submit(() -> { - try { - - db.setGroupSeen(group.getGroupKey(), seen); - group.setSeen(seen); - updateUnSeenGroups(group, seen); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS - } - }); - } - - return Futures.immediateFuture(null); + public ListenableFuture setGroupSeen(DrawableGroup group, boolean seen) { + return exec.submit(() -> { + try { + db.setGroupSeen(group.getGroupKey(), seen); + group.setSeen(seen); + updateUnSeenGroups(group, seen); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS + } + }); } synchronized private void updateUnSeenGroups(DrawableGroup group, boolean seen) { @@ -343,82 +317,73 @@ public class GroupManager { } synchronized public Set getFileIDsInGroup(GroupKey groupKey) throws TskCoreException { - Set fileIDsToReturn = Collections.emptySet(); + switch (groupKey.getAttribute().attrName) { //these cases get special treatment case CATEGORY: - fileIDsToReturn = getFileIDsWithCategory((DhsImageCategory) groupKey.getValue()); - break; + return getFileIDsWithCategory((DhsImageCategory) groupKey.getValue()); case TAGS: - fileIDsToReturn = getFileIDsWithTag((TagName) groupKey.getValue()); - break; + return getFileIDsWithTag((TagName) groupKey.getValue()); case MIME_TYPE: - fileIDsToReturn = getFileIDsWithMimeType((String) groupKey.getValue()); - break; + return getFileIDsWithMimeType((String) groupKey.getValue()); // case HASHSET: //comment out this case to use db functionality for hashsets // return getFileIDsWithHashSetName((String) groupKey.getValue()); default: - DrawableDB db = getDB(); //straight db query - if (nonNull(db)) { - fileIDsToReturn = db.getFileIDsInGroup(groupKey); - } + return db.getFileIDsInGroup(groupKey); } - return fileIDsToReturn; } // @@@ This was kind of slow in the profiler. Maybe we should cache it. // Unless the list of file IDs is necessary, use countFilesWithCategory() to get the counts. synchronized public Set getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException { Set fileIDsToReturn = Collections.emptySet(); - DrawableDB db = getDB(); - if (nonNull(db)) { - try { - final DrawableTagsManager tagsManager = controller.getTagsManager(); - if (category == DhsImageCategory.ZERO) { - List< TagName> tns = Stream.of(DhsImageCategory.ONE, DhsImageCategory.TWO, - DhsImageCategory.THREE, DhsImageCategory.FOUR, DhsImageCategory.FIVE) - .map(tagsManager::getTagName) - .collect(Collectors.toList()); - Set files = new HashSet<>(); - for (TagName tn : tns) { - if (tn != null) { - List contentTags = tagsManager.getContentTagsByTagName(tn); - files.addAll(contentTags.stream() - .filter(ct -> ct.getContent() instanceof AbstractFile) - .filter(ct -> db.isInDB(ct.getContent().getId())) - .map(ct -> ct.getContent().getId()) - .collect(Collectors.toSet())); - } + try { + final DrawableTagsManager tagsManager = controller.getTagsManager(); + if (category == DhsImageCategory.ZERO) { + List< TagName> tns = Stream.of(DhsImageCategory.ONE, DhsImageCategory.TWO, + DhsImageCategory.THREE, DhsImageCategory.FOUR, DhsImageCategory.FIVE) + .map(tagsManager::getTagName) + .collect(Collectors.toList()); + + Set files = new HashSet<>(); + for (TagName tn : tns) { + if (tn != null) { + List contentTags = tagsManager.getContentTagsByTagName(tn); + files.addAll(contentTags.stream() + .filter(ct -> ct.getContent() instanceof AbstractFile) + .filter(ct -> db.isInDB(ct.getContent().getId())) + .map(ct -> ct.getContent().getId()) + .collect(Collectors.toSet())); } - - fileIDsToReturn = db.findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(files, ',') + ")"); //NON-NLS - } else { - - List contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category)); - fileIDsToReturn = contentTags.stream() - .filter(ct -> ct.getContent() instanceof AbstractFile) - .filter(ct -> db.isInDB(ct.getContent().getId())) - .map(ct -> ct.getContent().getId()) - .collect(Collectors.toSet()); } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS - throw ex; + + fileIDsToReturn = db.findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(files, ',') + ")"); //NON-NLS + } else { + + List contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category)); + fileIDsToReturn = contentTags.stream() + .filter(ct -> ct.getContent() instanceof AbstractFile) + .filter(ct -> db.isInDB(ct.getContent().getId())) + .map(ct -> ct.getContent().getId()) + .collect(Collectors.toSet()); } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS + throw ex; } + return fileIDsToReturn; } synchronized public Set getFileIDsWithTag(TagName tagName) throws TskCoreException { Set files = new HashSet<>(); try { - List contentTags = controller.getTagsManager().getContentTagsByTagName(tagName); - DrawableDB db = getDB(); + for (ContentTag ct : contentTags) { - if (ct.getContent() instanceof AbstractFile && nonNull(db) && db.isInDB(ct.getContent().getId())) { + if (ct.getContent() instanceof AbstractFile && db.isInDB(ct.getContent().getId())) { files.add(ct.getContent().getId()); } } @@ -506,7 +471,7 @@ public class GroupManager { if (groupByTask != null) { groupByTask.cancel(true); } - + isRegrouping = true; groupByTask = new ReGroupTask<>(dataSource, groupBy, sortBy, sortOrder); Platform.runLater(() -> regroupProgress.bind(groupByTask.progressProperty())); exec.submit(groupByTask); @@ -625,13 +590,13 @@ public class GroupManager { * was still running) */ if (isNull(task) || task.isCancelled() == false) { - DrawableDB db = getDB(); + /* * For attributes other than path we can't be sure a group is fully * analyzed because we don't know all the files that will be a part * of that group. just show them no matter what. */ - if (nonNull(db) && ((groupKey.getAttribute() != DrawableAttribute.PATH) || db.isGroupAnalyzed(groupKey))) { + if (((groupKey.getAttribute() != DrawableAttribute.PATH) || db.isGroupAnalyzed(groupKey))) { try { Set fileIDs = getFileIDsInGroup(groupKey); if (Objects.nonNull(fileIDs)) { @@ -673,12 +638,12 @@ public class GroupManager { String query = (null == mimeType) ? "SELECT obj_id FROM tsk_files WHERE mime_type IS NULL" //NON-NLS : "SELECT obj_id FROM tsk_files WHERE mime_type = '" + mimeType + "'"; //NON-NLS - DrawableDB db = getDB(); + try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); ResultSet resultSet = executeQuery.getResultSet();) { while (resultSet.next()) { final long fileID = resultSet.getLong("obj_id"); //NON-NLS - if (nonNull(db) && db.isInDB(fileID)) { + if (db.isInDB(fileID)) { hashSet.add(fileID); } } @@ -752,6 +717,7 @@ public class GroupManager { groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); } + isRegrouping = false; } DataSource dataSourceOfCurrentGroup @@ -765,14 +731,15 @@ public class GroupManager { //the current group is for the given datasource, so just keep it in view. } else { //the current group should not be visible so ... if (isNotEmpty(unSeenGroups)) {// show then next unseen group - controller.advance(GroupViewState.tile(unSeenGroups.get(0)), false); + controller.advance(GroupViewState.tile(unSeenGroups.get(0))); } else { // clear the group area. - controller.advance(GroupViewState.tile(null), false); + controller.advance(GroupViewState.tile(null)); } } groupProgress.finish(); updateProgress(1, 1); + return null; } @@ -799,7 +766,7 @@ public class GroupManager { */ public Multimap findValuesForAttribute() { synchronized (GroupManager.this) { - DrawableDB db = getDB(); + Multimap results = HashMultimap.create(); try { switch (groupBy.attrName) { @@ -817,45 +784,43 @@ public class GroupManager { results.putAll(null, Arrays.asList(false, true)); break; case HASHSET: - if (nonNull(db)) { - results.putAll(null, new TreeSet<>(db.getHashSetNames())); - } + + results.putAll(null, new TreeSet<>(db.getHashSetNames())); + break; case MIME_TYPE: - if (nonNull(db)) { - HashSet types = new HashSet<>(); - // Use the group_concat function to get a list of files for each mime type. - // This has different syntax on Postgres vs SQLite - String groupConcatClause; - if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) { - groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; - } else { - groupConcatClause = " group_concat(obj_id) as object_ids"; - } - String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type "; - try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS - ResultSet resultSet = executeQuery.getResultSet();) { - while (resultSet.next()) { - final String mimeType = resultSet.getString("mime_type"); //NON-NLS - String objIds = resultSet.getString("object_ids"); //NON-NLS + HashSet types = new HashSet<>(); - Pattern.compile(",").splitAsStream(objIds) - .map(Long::valueOf) - .filter(db::isInDB) - .findAny().ifPresent(obj_id -> types.add(mimeType)); - } - } catch (SQLException | TskCoreException ex) { - Exceptions.printStackTrace(ex); - } - results.putAll(null, types); + // Use the group_concat function to get a list of files for each mime type. + // This has different syntax on Postgres vs SQLite + String groupConcatClause; + if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) { + groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; + } else { + groupConcatClause = " group_concat(obj_id) as object_ids"; } + String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type "; + try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS + ResultSet resultSet = executeQuery.getResultSet();) { + while (resultSet.next()) { + final String mimeType = resultSet.getString("mime_type"); //NON-NLS + String objIds = resultSet.getString("object_ids"); //NON-NLS + + Pattern.compile(",").splitAsStream(objIds) + .map(Long::valueOf) + .filter(db::isInDB) + .findAny().ifPresent(obj_id -> types.add(mimeType)); + } + } catch (SQLException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + } + results.putAll(null, types); + break; default: //otherwise do straight db query - if (nonNull(db)) { - results.putAll(db.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); - } + results.putAll(db.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); } } catch (TskCoreException | TskDataException ex) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 85c67226a5..10407531b9 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -162,7 +162,7 @@ public class Toolbar extends ToolBar { tagImageViewLabel.setText(Bundle.Toolbar_tagImageViewLabel()); categoryImageViewLabel.setText(Bundle.Toolbar_categoryImageViewLabel()); thumbnailSizeLabel.setText(Bundle.Toolbar_thumbnailSizeLabel()); - + sizeSlider.valueProperty().bindBidirectional(controller.thumbnailSize()); controller.viewState().addListener((observable, oldViewState, newViewState) -> Platform.runLater(() -> syncGroupControlsEnabledState(newViewState)) ); @@ -171,7 +171,7 @@ public class Toolbar extends ToolBar { initDataSourceComboBox(); groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs())); groupByBox.getSelectionModel().select(DrawableAttribute.PATH); - groupByBox.disableProperty().bind(ImageGalleryController.getDefault().regroupDisabled()); + groupByBox.disableProperty().bind(controller.regroupDisabled()); groupByBox.setCellFactory(listView -> new AttributeListCell()); groupByBox.setButtonCell(new AttributeListCell()); groupByBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { @@ -343,10 +343,6 @@ public class Toolbar extends ToolBar { return future; } - public DoubleProperty thumbnailSizeProperty() { - return sizeSlider.valueProperty(); - } - /** * * Static utility to to show a Popover with the given Node as owner. @@ -398,7 +394,6 @@ public class Toolbar extends ToolBar { } public Toolbar(ImageGalleryController controller) { - this.controller = controller; FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java index 29b65a0926..4378cc224e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java @@ -63,8 +63,8 @@ public class DrawableTile extends DrawableTileBase { setCache(true); setCacheHint(CacheHint.SPEED); nameLabel.prefWidthProperty().bind(imageView.fitWidthProperty()); - imageView.fitHeightProperty().bind(getController().thumbnailSizeProperty()); - imageView.fitWidthProperty().bind(getController().thumbnailSizeProperty()); + imageView.fitHeightProperty().bind(getController().thumbnailSize()); + imageView.fitWidthProperty().bind(getController().thumbnailSize()); selectionModel.lastSelectedProperty().addListener(new WeakChangeListener<>(lastSelectionListener)); @@ -106,7 +106,7 @@ public class DrawableTile extends DrawableTileBase { @Override Task newReadImageTask(DrawableFile file) { - return file.getThumbnailTask(); + return getController().getThumbsCache().getThumbnailTask(file); } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java index da8120f632..2cd237842c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java @@ -147,7 +147,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView myTask.setOnFailed(failed -> { Throwable exception = myTask.getException(); if (exception instanceof OutOfMemoryError - && exception.getMessage().contains("Java heap space")) { //NON-NLS + && exception.getMessage().contains("Java heap space")) { //NON-NLS showErrorNode(Bundle.DrawableUIBase_errorLabel_OOMText(), file); } else { showErrorNode(Bundle.DrawableUIBase_errorLabel_text(), file); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 60a0dec228..52117d414b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -429,7 +429,7 @@ public class GroupPane extends BorderPane { flashAnimation.setAutoReverse(true); //configure gridView cell properties - DoubleBinding cellSize = controller.thumbnailSizeProperty().add(75); + DoubleBinding cellSize = controller.thumbnailSize().add(75); gridView.cellHeightProperty().bind(cellSize); gridView.cellWidthProperty().bind(cellSize); gridView.setCellFactory((GridView param) -> new DrawableCell()); @@ -613,9 +613,9 @@ public class GroupPane extends BorderPane { * * @param grouping the new grouping assigned to this group */ - void setViewState(GroupViewState viewState) { + void setViewState(GroupViewState newViewState) { - if (isNull(viewState) || isNull(viewState.getGroup())) { + if (isNull(newViewState) || isNull(newViewState.getGroup().orElse(null))) { if (nonNull(getGroup())) { getGroup().getFileIDs().removeListener(filesSyncListener); } @@ -634,37 +634,35 @@ public class GroupPane extends BorderPane { }); } else { - if (getGroup() != viewState.getGroup().orElse(null)) { + if (getGroup() != newViewState.getGroup().get()) { if (nonNull(getGroup())) { getGroup().getFileIDs().removeListener(filesSyncListener); } - - this.grouping.set(viewState.getGroup().orElse(null)); - - getGroup().getFileIDs().addListener(filesSyncListener); - - final String header = getHeaderString(); - - Platform.runLater(() -> { - gridView.getItems().setAll(getGroup().getFileIDs()); - slideShowToggle.setDisable(gridView.getItems().isEmpty()); - groupLabel.setText(header); - resetScrollBar(); - if (viewState.getMode() == GroupViewMode.TILE) { - activateTileViewer(); - } else { - activateSlideShowViewer(viewState.getSlideShowfileID().orElse(null)); - } - }); } + + this.grouping.set(newViewState.getGroup().get()); + + getGroup().getFileIDs().addListener(filesSyncListener); + + final String header = getHeaderString(); + + Platform.runLater(() -> { + gridView.getItems().setAll(getGroup().getFileIDs()); + slideShowToggle.setDisable(gridView.getItems().isEmpty()); + groupLabel.setText(header); + resetScrollBar(); + if (newViewState.getMode() == GroupViewMode.TILE) { + activateTileViewer(); + } else { + activateSlideShowViewer(newViewState.getSlideShowfileID().orElse(null)); + } + }); } } @ThreadConfined(type = ThreadType.JFX) private void resetScrollBar() { - getScrollBar().ifPresent((scrollBar) -> { - scrollBar.setValue(0); - }); + getScrollBar().ifPresent(scrollBar -> scrollBar.setValue(0)); } @ThreadConfined(type = ThreadType.JFX) @@ -689,6 +687,7 @@ public class GroupPane extends BorderPane { } else { selectionAnchorIndex = null; selectionModel.clearAndSelect(newFileID); + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java index 60f30564b1..4e363761e3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java @@ -67,10 +67,10 @@ import org.sleuthkit.datamodel.TagName; * Shows details of the selected file. */ @NbBundle.Messages({"MetaDataPane.tableView.placeholder=Select a file to show its details here.", - "MetaDataPane.copyMenuItem.text=Copy", - "MetaDataPane.titledPane.displayName=Details", - "MetaDataPane.attributeColumn.headingName=Attribute", - "MetaDataPane.valueColumn.headingName=Value"}) + "MetaDataPane.copyMenuItem.text=Copy", + "MetaDataPane.titledPane.displayName=Details", + "MetaDataPane.attributeColumn.headingName=Attribute", + "MetaDataPane.valueColumn.headingName=Value"}) public class MetaDataPane extends DrawableUIBase { private static final Logger LOGGER = Logger.getLogger(MetaDataPane.class.getName()); @@ -202,7 +202,7 @@ public class MetaDataPane extends DrawableUIBase { @Override Task newReadImageTask(DrawableFile file) { - return file.getThumbnailTask(); + return getController().getThumbsCache().getThumbnailTask(file); } public void updateAttributesTable() { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java index 20af01a9a5..28bd44599d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java @@ -104,7 +104,7 @@ abstract class NavPanel extends Tab { .addListener((observable, oldItem, newSelectedItem) -> { Optional.ofNullable(newSelectedItem) .map(getDataItemMapper()) - .ifPresent(group -> controller.advance(GroupViewState.tile(group), false)); + .ifPresent(group -> controller.advance(GroupViewState.tile(group))); }); } From feb45d62780ad4f9afea612754dd04247db0f43e Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 4 Sep 2018 16:48:50 +0200 Subject: [PATCH 61/84] fix first group display after regrouping --- .../imagegallery/ImageGalleryController.java | 9 ++-- .../ImageGalleryTopComponent.java | 9 +--- .../imagegallery/actions/NextUnseenGroup.java | 1 + .../datamodel/grouping/GroupManager.java | 51 +++++++++++-------- 4 files changed, 38 insertions(+), 32 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index c4ef95317c..7f0cab1d8e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -56,6 +56,7 @@ import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.swing.SortOrder; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import org.netbeans.api.progress.ProgressHandle; import org.openide.util.Cancellable; @@ -69,12 +70,14 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.actions.UndoRedoManager; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; +import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB.DrawableDbBuildStatusEnum; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; import org.sleuthkit.autopsy.imagegallery.datamodel.HashSetManager; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager; +import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog; import org.sleuthkit.autopsy.imagegallery.gui.Toolbar; @@ -294,7 +297,7 @@ public final class ImageGalleryController { public synchronized void shutDown() { logger.info("Closing ImageGalleryControler for case."); //NON-NLS - autopsyCase = null; + selectionModel.clearSelection(); setListeningEnabled(false); thumbnailCache.clearCache(); @@ -303,14 +306,12 @@ public final class ImageGalleryController { tagsManager.unregisterListener(groupManager); tagsManager.unregisterListener(categoryManager); - tagsManager = null; + shutDownDBExecutor(); if (db != null) { db.closeDBCon(); } - db = null; - } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 1e1cc7ad11..1dd2ab6f33 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -227,7 +227,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl setController(ImageGalleryModule.getController()); } - synchronized void setController(ImageGalleryController controller) { + synchronized private void setController(ImageGalleryController controller) { if (this.controller != null) { this.controller.shutDown(); } @@ -310,12 +310,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl @Override public void componentOpened() { super.componentOpened(); -// try { -// setController(ImageGalleryModule.getController()); -// } catch (NoCurrentCaseException ex) { -// logger.log(Level.WARNING, "ImageGellery opened with no open case.", ex); -// } - WindowManager.getDefault().setTopComponentFloating(this, true); + WindowManager.getDefault().setTopComponentFloating(this, true); } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index 8306b5137e..7cae13caa3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -89,6 +89,7 @@ public class NextUnseenGroup extends Action { private void updateButton() { int size = unSeenGroups.size(); Platform.runLater(() -> { + setDisabled(size == 0); if (size <= 1) { setText(MARK_GROUP_SEEN); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 85c32161fa..2c912c18de 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.imagegallery.datamodel.grouping; +import com.google.common.base.MoreObjects; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.eventbus.Subscribe; @@ -65,6 +66,7 @@ import javax.annotation.concurrent.GuardedBy; import javax.swing.SortOrder; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import org.apache.commons.lang3.ObjectUtils; +import static org.apache.commons.lang3.ObjectUtils.notEqual; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.netbeans.api.progress.ProgressHandle; @@ -614,9 +616,7 @@ public class GroupManager { if (analyzedGroups.contains(group) == false) { analyzedGroups.add(group); - if (isNull(task)) { - sortAnalyzedGroups(); - } + sortAnalyzedGroups(); } updateUnSeenGroups(group, groupSeen); @@ -704,7 +704,7 @@ public class GroupManager { // Get the list of group keys final Multimap valsByDataSource = findValuesForAttribute(); - groupProgress.switchToDeterminate(valsByDataSource.size()); + groupProgress.switchToDeterminate(valsByDataSource.entries().size()); int p = 0; // For each key value, partially create the group and add it to the list. for (final Map.Entry val : valsByDataSource.entries()) { @@ -718,25 +718,34 @@ public class GroupManager { popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); } isRegrouping = false; - } - DataSource dataSourceOfCurrentGroup - = Optional.ofNullable(controller.getViewState()) - .flatMap(GroupViewState::getGroup) - .map(DrawableGroup::getGroupKey) - .flatMap(GroupKey::getDataSource) - .orElse(null); - if (getDataSource() == null - || Objects.equals(dataSourceOfCurrentGroup, getDataSource())) { - //the current group is for the given datasource, so just keep it in view. - } else { //the current group should not be visible so ... - if (isNotEmpty(unSeenGroups)) {// show then next unseen group - controller.advance(GroupViewState.tile(unSeenGroups.get(0))); - } else { // clear the group area. - controller.advance(GroupViewState.tile(null)); - } - } + Optional viewedGroup + = Optional.ofNullable(controller.getViewState()) + .flatMap(GroupViewState::getGroup); + Optional> viewedKey = viewedGroup.map(DrawableGroup::getGroupKey); + DataSource dataSourceOfCurrentGroup + = viewedKey.flatMap(GroupKey::getDataSource) + .orElse(null); + DrawableAttribute attributeOfCurrentGroup + = viewedKey.map(GroupKey::getAttribute) + .orElse(null); + /* if no group or if groupbies are different or if data source + * != null and does not equal group */ + if (viewedGroup.isPresent() == false + || (getDataSource() != null && notEqual(dataSourceOfCurrentGroup, getDataSource())) + || getGroupBy() != attributeOfCurrentGroup) { + //the current group should not be visible so ... + if (isNotEmpty(unSeenGroups)) {// show then next unseen group + controller.advance(GroupViewState.tile(unSeenGroups.get(0))); + } else if (isNotEmpty(analyzedGroups)) { + //show the first analyzed group. + controller.advance(GroupViewState.tile(analyzedGroups.get(0))); + } else { //there are no groups, clear the group area. + controller.advance(GroupViewState.tile(null)); + } + } //else, the current group is for the given datasource, so just keep it in view. + } groupProgress.finish(); updateProgress(1, 1); From cea02f4304eb17ef62f50b4c8051618e82ee6c05 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Tue, 4 Sep 2018 17:48:41 +0200 Subject: [PATCH 62/84] fix changing cases --- .../imagegallery/ImageGalleryController.java | 26 +++++++---------- .../imagegallery/ImageGalleryModule.java | 2 +- .../autopsy/imagegallery/ThumbnailCache.java | 2 +- .../imagegallery/actions/AddTagAction.java | 2 +- .../actions/CategorizeAction.java | 2 +- .../actions/CategorizeGroupAction.java | 2 +- .../imagegallery/datamodel/DrawableDB.java | 28 +++++++++---------- .../gui/drawableviews/DrawableTileBase.java | 20 ++++++------- .../gui/drawableviews/DrawableUIBase.java | 22 ++++++--------- .../gui/drawableviews/DrawableView.java | 8 ++++-- .../gui/drawableviews/GroupPane.java | 1 - .../gui/drawableviews/MetaDataPane.java | 5 +++- 12 files changed, 57 insertions(+), 63 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 7f0cab1d8e..ea7e2591b4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -120,17 +120,17 @@ public final class ImageGalleryController { private final GroupManager groupManager; private final HashSetManager hashSetManager; private final CategoryManager categoryManager; - private DrawableTagsManager tagsManager; + private final DrawableTagsManager tagsManager; private ListeningExecutorService dbExecutor; - private Case autopsyCase; + private final Case autopsyCase; public Case getAutopsyCase() { return autopsyCase; } - private SleuthkitCase sleuthKitCase; - private DrawableDB db; + private final SleuthkitCase sleuthKitCase; + private final DrawableDB db; public ReadOnlyBooleanProperty getMetaDataCollapsed() { return metaDataCollapsed.getReadOnlyProperty(); @@ -200,7 +200,7 @@ public final class ImageGalleryController { this.autopsyCase = Objects.requireNonNull(newCase); this.sleuthKitCase = newCase.getSleuthkitCase(); - this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(newCase), this); + this.db = DrawableDB.getDrawableDB(this); setListeningEnabled(ImageGalleryModule.isEnabledforCase(newCase)); setStale(ImageGalleryModule.isDrawableDBStale(newCase)); @@ -297,7 +297,6 @@ public final class ImageGalleryController { public synchronized void shutDown() { logger.info("Closing ImageGalleryControler for case."); //NON-NLS - selectionModel.clearSelection(); setListeningEnabled(false); thumbnailCache.clearCache(); @@ -306,12 +305,12 @@ public final class ImageGalleryController { tagsManager.unregisterListener(groupManager); tagsManager.unregisterListener(categoryManager); - + shutDownDBExecutor(); - if (db != null) { - db.closeDBCon(); - } +// if (db != null) { +// db.closeDBCon(); +// } } /** @@ -411,12 +410,7 @@ public final class ImageGalleryController { Platform.runLater(() -> dbTaskQueueSize.set(dbTaskQueueSize.get() - 1)); } - @Nullable - synchronized public DrawableFile getFileFromId(Long fileID) throws TskCoreException { - if (Objects.isNull(db)) { - logger.log(Level.WARNING, "Could not get file from id, no DB set. The case is probably closed."); //NON-NLS - return null; - } + public DrawableFile getFileFromID(Long fileID) throws TskCoreException { return db.getFileFromID(fileID); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java index 046cc5e51d..1baec7ffbc 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java @@ -96,7 +96,7 @@ public class ImageGalleryModule { * * @return the Path to the ModuleOuput subfolder for Image Gallery */ - static Path getModuleOutputDir(Case theCase) { + public static Path getModuleOutputDir(Case theCase) { return Paths.get(theCase.getModuleDirectory(), getModuleName()); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java index a3e8fedde3..c6fdd893eb 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ThumbnailCache.java @@ -109,7 +109,7 @@ public class ThumbnailCache { @Nullable public Image get(Long fileID) { try { - return get(controller.getFileFromId(fileID)); + return get(controller.getFileFromID(fileID)); } catch (TskCoreException ex) { LOGGER.log(Level.WARNING, "Failed to load thumbnail for file: " + fileID, ex.getCause()); //NON-NLS return null; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java index 522c264a6b..dbb27e81dc 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddTagAction.java @@ -93,7 +93,7 @@ public class AddTagAction extends Action { DrawableTagsManager tagsManager = controller.getTagsManager(); for (Long fileID : selectedFiles) { try { - final DrawableFile file = controller.getFileFromId(fileID); + final DrawableFile file = controller.getFileFromID(fileID); LOGGER.log(Level.INFO, "tagging {0} with {1} and comment {2}", new Object[]{file.getName(), tagName.getDisplayName(), comment}); //NON-NLS List contentTags = tagsManager.getContentTags(file); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java index 14944dff09..c6d540dbb3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java @@ -138,7 +138,7 @@ public class CategorizeAction extends Action { TagName catZeroTagName = categoryManager.getTagName(DhsImageCategory.ZERO); for (long fileID : fileIDs) { try { - DrawableFile file = controller.getFileFromId(fileID); //drawable db access + DrawableFile file = controller.getFileFromID(fileID); //drawable db access if (createUndo) { DhsImageCategory oldCat = file.getCategory(); //drawable db access TagName oldCatTagName = categoryManager.getTagName(oldCat); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java index 2b4946aa8b..b5ea4bc421 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java @@ -65,7 +65,7 @@ public class CategorizeGroupAction extends CategorizeAction { for (Long fileID : fileIDs) { try { - DhsImageCategory category = controller.getFileFromId(fileID).getCategory(); + DhsImageCategory category = controller.getFileFromID(fileID).getCategory(); if (false == DhsImageCategory.ZERO.equals(category) && newCat.equals(category) == false) { catCountMap.merge(category, 1L, Long::sum); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 0a97109091..1c82af7abf 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -214,7 +214,7 @@ public final class DrawableDB { * * @throws SQLException if there is problem creating or configuring the db */ - private DrawableDB(Path dbPath, ImageGalleryController controller) throws SQLException, ExceptionInInitializerError, IOException { + private DrawableDB(Path dbPath, ImageGalleryController controller) throws TskCoreException, SQLException, IOException { this.dbPath = dbPath; this.controller = controller; this.tskCase = controller.getSleuthKitCase(); @@ -267,12 +267,12 @@ public final class DrawableDB { logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); } } - throw new ExceptionInInitializerError(ex); + throw ex; } initializeImageList(); } else { - throw new ExceptionInInitializerError(); + throw new TskCoreException("Failed to initialize Image Gallery db schema"); } } @@ -350,22 +350,22 @@ public final class DrawableDB { /** * public factory method. Creates and opens a connection to a new database * - * at the given path. + * at the given path. * * - * @param dbPath + * @param controller * - * @return + * @return A DrawableDB for the given controller. + * + * @throws org.sleuthkit.datamodel.TskCoreException */ - public static DrawableDB getDrawableDB(Path dbPath, ImageGalleryController controller) { - + public static DrawableDB getDrawableDB(ImageGalleryController controller) throws TskCoreException { + Path dbPath = ImageGalleryModule.getModuleOutputDir(controller.getAutopsyCase()); try { return new DrawableDB(dbPath.resolve("drawable.db"), controller); //NON-NLS } catch (SQLException ex) { - logger.log(Level.SEVERE, "sql error creating database connection", ex); //NON-NLS - return null; - } catch (ExceptionInInitializerError | IOException ex) { - logger.log(Level.SEVERE, "error creating database connection", ex); //NON-NLS - return null; + throw new TskCoreException("sql error creating database connection", ex); //NON-NLS + } catch (IOException ex) { + throw new TskCoreException("Error creating database connection", ex); //NON-NLS } } @@ -1174,7 +1174,7 @@ public final class DrawableDB { areFilesAnalyzed(Collections.singleton(id)), isVideoFile(f)); } catch (IllegalStateException ex) { logger.log(Level.SEVERE, "there is no case open; failed to load file with id: {0}", id); //NON-NLS - return null; + throw new TskCoreException("there is no case open; failed to load file with id: " + id, ex); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java index 0563202f52..e6b9479f50 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java @@ -187,13 +187,12 @@ public abstract class DrawableTileBase extends DrawableUIBase { final ArrayList menuItems = new ArrayList<>(); menuItems.add(CategorizeAction.getCategoriesMenu(getController())); - + try { menuItems.add(AddTagAction.getTagMenu(getController())); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error building tagging context menu.", ex); } - final Collection selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); if (selectedFilesList.size() == 1) { @@ -248,7 +247,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { protected abstract String getTextForLabel(); protected void initialize() { - + followUpToggle.setOnAction( actionEvent -> getFile().ifPresent( file -> { @@ -259,20 +258,21 @@ public abstract class DrawableTileBase extends DrawableUIBase { new DeleteFollowUpTagAction(getController(), file).handle(actionEvent); } }) - ); } protected boolean hasFollowUp() { if (getFileID().isPresent()) { - + TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); - - return DrawableAttribute.TAGS.getValue(getFile().get()).stream() - .anyMatch(followUpTagName::equals); - } else { - return false; + if (getFile().isPresent()) { + return DrawableAttribute.TAGS.getValue(getFile().get()).stream() + .anyMatch(followUpTagName::equals); + } else { + return false; + } } + return false; } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java index 2cd237842c..2fb717628c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java @@ -25,7 +25,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.logging.Level; +import static java.util.logging.Level.SEVERE; import javafx.application.Platform; import javafx.concurrent.Task; import javafx.fxml.FXML; @@ -57,7 +57,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView static final Executor exec = Executors.newSingleThreadExecutor(); - private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName()); + private static final Logger logger = Logger.getLogger(DrawableUIBase.class.getName()); @FXML BorderPane imageBorder; @@ -96,20 +96,18 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView @Override synchronized public Optional getFile() { if (fileIDOpt.isPresent()) { - if (fileOpt.isPresent() && fileOpt.get().getId() == fileIDOpt.get()) { - return fileOpt; - } else { + if (!fileOpt.isPresent() || fileOpt.get().getId() != fileIDOpt.get()) { try { - fileOpt = Optional.ofNullable(getController().getFileFromId(fileIDOpt.get())); + fileOpt = Optional.ofNullable(getController().getFileFromID(fileIDOpt.get())); } catch (TskCoreException ex) { - Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex); //NON-NLS + logger.log(SEVERE, "Error getting file by id.", ex); fileOpt = Optional.empty(); } - return fileOpt; } } else { - return Optional.empty(); + fileOpt = Optional.empty(); } + return fileOpt; } protected abstract void setFileHelper(Long newFileID); @@ -125,10 +123,8 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView } } - synchronized protected void updateContent() { - if (getFile().isPresent()) { - doReadImageTask(getFile().get()); - } + synchronized protected void updateContent() { + getFile().ifPresent(this::doReadImageTask); } synchronized Node doReadImageTask(DrawableFile file) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableView.java index afed2fd832..a9779271a6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableView.java @@ -12,6 +12,7 @@ import javafx.scene.layout.BorderWidths; import javafx.scene.layout.CornerRadii; import javafx.scene.layout.Region; import javafx.scene.paint.Color; +import org.openide.util.Exceptions; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.coreutils.Logger; @@ -20,6 +21,7 @@ import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; +import org.sleuthkit.datamodel.TskCoreException; /** * Interface for classes that are views of a single DrawableFile. Implementation @@ -60,9 +62,8 @@ public interface DrawableView { /** * update the visual representation of the category of the assigned file. - * Implementations of {@link DrawableView} must register themselves with - * {@link CategoryManager#registerListener(java.lang.Object)} to ahve this - * method invoked + * Implementations of DrawableView } must register themselves with + * CategoryManager.registerListener()} to have this method invoked * * @param evt the CategoryChangeEvent to handle */ @@ -95,6 +96,7 @@ public interface DrawableView { Logger.getLogger(DrawableView.class.getName()).log(Level.WARNING, "Error looking up hash set hits"); //NON-NLS return false; } + } static Border getCategoryBorder(DhsImageCategory category) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 52117d414b..ca672baf8f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -710,7 +710,6 @@ public class GroupPane extends BorderPane { } } cellMap.put(newValue, DrawableCell.this); - } }); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java index 4e363761e3..ff7c3f6429 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java @@ -27,6 +27,7 @@ import java.util.Objects; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Optional; +import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; @@ -51,6 +52,7 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; import javafx.scene.text.Text; import javafx.util.Pair; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; @@ -62,6 +64,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.datamodel.TagName; +import org.sleuthkit.datamodel.TskCoreException; /** * Shows details of the selected file. @@ -73,7 +76,7 @@ import org.sleuthkit.datamodel.TagName; "MetaDataPane.valueColumn.headingName=Value"}) public class MetaDataPane extends DrawableUIBase { - private static final Logger LOGGER = Logger.getLogger(MetaDataPane.class.getName()); + private static final Logger logger = Logger.getLogger(MetaDataPane.class.getName()); private static final KeyCodeCombination COPY_KEY_COMBINATION = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_DOWN); From 8d0bbea639ad408ffe7643e720c40c014630e79f Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 5 Sep 2018 11:06:11 +0200 Subject: [PATCH 63/84] fix NPE --- .../datamodel/CategoryManager.java | 20 ++++++++----- .../imagegallery/datamodel/DrawableDB.java | 17 +++++++---- .../datamodel/DrawableTagsManager.java | 25 +++++++++++----- .../datamodel/grouping/GroupManager.java | 30 +++++++------------ .../gui/drawableviews/DrawableUIBase.java | 3 +- 5 files changed, 51 insertions(+), 44 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java index 9c6ff136da..6a4916cb1f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015-16 Basis Technology Corp. + * Copyright 2015-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,7 @@ * limitations under the License. */package org.sleuthkit.autopsy.imagegallery.datamodel; +import com.google.common.base.Function; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -34,19 +35,19 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.datamodel.DhsImageCategory; +import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** * Provides a cached view of the number of files per category, and fires - * {@link CategoryChangeEvent}s when files are categorized. + * CategoryChangeEvents when files are categorized. * * To receive CategoryChangeEvents, a listener must register itself, and - * implement a public method annotated with {@link Subscribe} that accepts one - * argument of type CategoryChangeEvent + * implement a public method annotated with Subscribe that accepts one argument + * of type CategoryChangeEvent * * TODO: currently these two functions (cached counts and events) are separate * although they are related. Can they be integrated more? @@ -86,9 +87,12 @@ public class CategoryManager { * autopsyTagManager at initial request or if invalidated by case change. */ private final LoadingCache catTagNameMap - = CacheBuilder.newBuilder().build(CacheLoader.from( - cat -> getController().getTagsManager().getTagName(cat) - )); + = CacheBuilder.newBuilder().build(new CacheLoader() { + @Override + public TagName load(DhsImageCategory cat) throws Exception { + return getController().getTagsManager().getTagName(cat); + } + }); public CategoryManager(ImageGalleryController controller) { this.controller = controller; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 1c82af7abf..a222148f83 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -43,11 +43,15 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; +import java.util.function.Predicate; import java.util.logging.Level; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.concurrent.GuardedBy; import javax.swing.SortOrder; +import org.apache.commons.lang3.ObjectUtils; +import static org.apache.commons.lang3.ObjectUtils.notEqual; import org.apache.commons.lang3.StringUtils; import org.openide.util.Exceptions; import org.sleuthkit.autopsy.casemodule.Case; @@ -1397,18 +1401,18 @@ public final class DrawableDB { * * @return the number of files in the given set with Cat-0 */ - public long getUncategorizedCount(Collection fileIDs) { + public long getUncategorizedCount(Collection fileIDs) throws TskCoreException { // if the fileset is empty, return count as 0 if (fileIDs.isEmpty()) { return 0; } + // get a comma seperated list of TagName ids for non zero categories DrawableTagsManager tagsManager = controller.getTagsManager(); - // get a comma seperated list of TagName ids for non zero categories - String catTagNameIDs = DhsImageCategory.getNonZeroCategories().stream() - .map(tagsManager::getTagName) + String catTagNameIDs = tagsManager.getCategoryTagNames().stream() + .filter(tagName -> notEqual(tagName.getDisplayName(), DhsImageCategory.ZERO.getDisplayName())) .map(TagName::getId) .map(Object::toString) .collect(Collectors.joining(",", "(", ")")); @@ -1424,9 +1428,10 @@ public final class DrawableDB { while (resultSet.next()) { return resultSet.getLong("obj_count"); //NON-NLS } - } catch (SQLException | TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting category count.", ex); //NON-NLS + } catch (SQLException ex) { + throw new TskCoreException("Error getting category count.", ex); //NON-NLS } + return -1; } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java index c7c3d707b0..e4f52077b1 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java @@ -130,13 +130,27 @@ public final class DrawableTagsManager { * @throws org.sleuthkit.datamodel.TskCoreException */ public List getNonCategoryTagNames() throws TskCoreException { - return autopsyTagsManager.getAllTagNames().stream() .filter(CategoryManager::isNotCategoryTagName) .distinct().sorted() .collect(Collectors.toList()); } + /** + * Get all the TagNames that are categories + * + * @return All the TagNames that are categories, in alphabetical order by + * displayName. + * + * @throws org.sleuthkit.datamodel.TskCoreException + */ + public List getCategoryTagNames() throws TskCoreException { + return autopsyTagsManager.getAllTagNames().stream() + .filter(CategoryManager::isCategoryTagName) + .distinct().sorted() + .collect(Collectors.toList()); + } + /** * Gets content tags by content. * @@ -192,13 +206,8 @@ public final class DrawableTagsManager { } } - public TagName getTagName(DhsImageCategory cat) { - try { - return getTagName(cat.getDisplayName()); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting tag for Category: " + cat.getDisplayName(), ex); - return null; - } + public TagName getTagName(DhsImageCategory cat) throws TskCoreException { + return getTagName(cat.getDisplayName()); } public ContentTag addContentTag(DrawableFile file, TagName tagName, String comment) throws TskCoreException { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 2c912c18de..5cfe47c50a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.imagegallery.datamodel.grouping; -import com.google.common.base.MoreObjects; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.eventbus.Subscribe; @@ -27,6 +26,7 @@ import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -43,6 +43,8 @@ import java.util.TreeSet; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; +import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.logging.Level; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -54,12 +56,6 @@ import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import static javafx.concurrent.Worker.State.CANCELLED; -import static javafx.concurrent.Worker.State.FAILED; -import static javafx.concurrent.Worker.State.READY; -import static javafx.concurrent.Worker.State.RUNNING; -import static javafx.concurrent.Worker.State.SCHEDULED; -import static javafx.concurrent.Worker.State.SUCCEEDED; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -344,24 +340,18 @@ public class GroupManager { try { final DrawableTagsManager tagsManager = controller.getTagsManager(); if (category == DhsImageCategory.ZERO) { - List< TagName> tns = Stream.of(DhsImageCategory.ONE, DhsImageCategory.TWO, - DhsImageCategory.THREE, DhsImageCategory.FOUR, DhsImageCategory.FIVE) - .map(tagsManager::getTagName) - .collect(Collectors.toList()); - - Set files = new HashSet<>(); - for (TagName tn : tns) { - if (tn != null) { - List contentTags = tagsManager.getContentTagsByTagName(tn); - files.addAll(contentTags.stream() + Set fileIDs = new HashSet<>(); + for (TagName catTagName : tagsManager.getCategoryTagNames()) { + if (notEqual(catTagName.getDisplayName(), DhsImageCategory.ZERO.getDisplayName())) { + tagsManager.getContentTagsByTagName(catTagName).stream() .filter(ct -> ct.getContent() instanceof AbstractFile) - .filter(ct -> db.isInDB(ct.getContent().getId())) .map(ct -> ct.getContent().getId()) - .collect(Collectors.toSet())); + .filter(db::isInDB) + .forEach(fileIDs::add); } } - fileIDsToReturn = db.findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(files, ',') + ")"); //NON-NLS + fileIDsToReturn = db.findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(fileIDs, ',') + ")"); //NON-NLS } else { List contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category)); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java index 2fb717628c..8cbcd8840d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java @@ -100,7 +100,6 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView try { fileOpt = Optional.ofNullable(getController().getFileFromID(fileIDOpt.get())); } catch (TskCoreException ex) { - logger.log(SEVERE, "Error getting file by id.", ex); fileOpt = Optional.empty(); } } @@ -123,7 +122,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView } } - synchronized protected void updateContent() { + synchronized protected void updateContent() { getFile().ifPresent(this::doReadImageTask); } From 88a61635660f1830fca58c4e9085f640033117ac Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 5 Sep 2018 12:07:10 +0200 Subject: [PATCH 64/84] more error handling fixes --- .../imagegallery/ImageGalleryController.java | 13 +- .../ImageGalleryTopComponent.java | 9 +- .../imagegallery/actions/OpenAction.java | 85 +++--- .../imagegallery/datamodel/DrawableDB.java | 36 ++- .../datamodel/HashSetManager.java | 20 +- .../datamodel/grouping/DrawableGroup.java | 3 +- .../datamodel/grouping/GroupManager.java | 257 +++++++++--------- .../autopsy/imagegallery/gui/Toolbar.java | 4 - 8 files changed, 224 insertions(+), 203 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index ea7e2591b4..8d6f07f2a1 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -206,11 +206,13 @@ public final class ImageGalleryController { setStale(ImageGalleryModule.isDrawableDBStale(newCase)); groupManager = new GroupManager(this); - tagsManager = new DrawableTagsManager(this); - hashSetManager = new HashSetManager(db); categoryManager = new CategoryManager(this); + tagsManager = new DrawableTagsManager(this); tagsManager.registerListener(groupManager); tagsManager.registerListener(categoryManager); + + hashSetManager = new HashSetManager(db); + shutDownDBExecutor(); dbExecutor = getNewDBExecutor(); @@ -303,14 +305,7 @@ public final class ImageGalleryController { historyManager.clear(); groupManager.reset(); - tagsManager.unregisterListener(groupManager); - tagsManager.unregisterListener(categoryManager); - shutDownDBExecutor(); - -// if (db != null) { -// db.closeDBCon(); -// } } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 1dd2ab6f33..1dc35d2466 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -108,7 +108,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl private SplitPane splitPane; private StackPane centralStack; - private BorderPane borderPane = new BorderPane(); + private final BorderPane borderPane = new BorderPane(); private StackPane fullUIStack; private MetaDataPane metaDataTable; private GroupPane groupPane; @@ -153,7 +153,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl "ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.contentText=Data source:", "ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.all=All", "ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.titleText=Image Gallery",}) - public static void openTopComponent() { + public static void openTopComponent() throws NoCurrentCaseException { final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); if (tc == null) { @@ -166,7 +166,8 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl } List dataSources = Collections.emptyList(); - ImageGalleryController controller = ((ImageGalleryTopComponent) tc).controller; + ImageGalleryController controller = ImageGalleryModule.getController(); + ((ImageGalleryTopComponent) tc).setController(controller); try { dataSources = controller.getSleuthKitCase().getDataSources(); } catch (TskCoreException tskCoreException) { @@ -310,7 +311,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl @Override public void componentOpened() { super.componentOpened(); - WindowManager.getDefault().setTopComponentFloating(this, true); + WindowManager.getDefault().setTopComponentFloating(this, true); } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java index 80575315b9..ebea08be35 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java @@ -34,6 +34,7 @@ import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionReferences; import org.openide.awt.ActionRegistration; +import org.openide.util.Exceptions; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; @@ -62,16 +63,16 @@ import org.sleuthkit.datamodel.TskCoreException; + "Choosing 'yes' will update the database and enable listening to future ingests.", "OpenAction.stale.confDlg.title=Image Gallery"}) public final class OpenAction extends CallableSystemAction { - + private static final Logger logger = Logger.getLogger(OpenAction.class.getName()); private static final String VIEW_IMAGES_VIDEOS = Bundle.CTL_OpenAction(); private static final long FILE_LIMIT = 6_000_000; - + private final PropertyChangeListener pcl; private final JMenuItem menuItem; private final JButton toolbarButton = new JButton(this.getName(), new ImageIcon(getClass().getResource("btn_icon_image_gallery_26.png"))); - + public OpenAction() { super(); toolbarButton.addActionListener(actionEvent -> performAction()); @@ -84,7 +85,7 @@ public final class OpenAction extends CallableSystemAction { Case.addPropertyChangeListener(pcl); this.setEnabled(false); } - + @Override public boolean isEnabled() { Case openCase; @@ -103,10 +104,10 @@ public final class OpenAction extends CallableSystemAction { */ @Override public Component getToolbarPresenter() { - + return toolbarButton; } - + @Override public JMenuItem getMenuPresenter() { return menuItem; @@ -123,7 +124,7 @@ public final class OpenAction extends CallableSystemAction { menuItem.setEnabled(value); toolbarButton.setEnabled(value); } - + @Override @SuppressWarnings("fallthrough") @NbBundle.Messages({"OpenAction.dialogTitle=Image Gallery"}) @@ -137,50 +138,56 @@ public final class OpenAction extends CallableSystemAction { logger.log(Level.SEVERE, "Exception while getting open case.", ex); return; } - + if (tooManyFiles()) { Platform.runLater(OpenAction::showTooManyFiles); setEnabled(false); return; } - if (ImageGalleryModule.isDrawableDBStale(currentCase)) { - //drawable db is stale, ask what to do - int answer = JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), Bundle.OpenAction_stale_confDlg_msg(), - Bundle.OpenAction_stale_confDlg_title(), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); - - switch (answer) { - case JOptionPane.YES_OPTION: - /* 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. - */ try { + try { + if (ImageGalleryModule.isDrawableDBStale(currentCase)) { + //drawable db is stale, ask what to do + int answer = JOptionPane.showConfirmDialog( + WindowManager.getDefault().getMainWindow(), + Bundle.OpenAction_stale_confDlg_msg(), + Bundle.OpenAction_stale_confDlg_title(), + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE); + + switch (answer) { + case JOptionPane.YES_OPTION: + /* 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. + */ ImageGalleryController controller = ImageGalleryModule.getController(); - + if (currentCase.getCaseType() == Case.CaseType.SINGLE_USER_CASE) { controller.setListeningEnabled(true); } else { controller.rebuildDB(); } - } catch (NoCurrentCaseException noCurrentCaseException) { - logger.log(Level.WARNING, "There was no case open when Image Gallery was opened.", noCurrentCaseException); - } - //fall through - case JOptionPane.NO_OPTION: - ImageGalleryTopComponent.openTopComponent(); + //fall through + case JOptionPane.NO_OPTION: { + ImageGalleryTopComponent.openTopComponent(); + } break; - case JOptionPane.CANCEL_OPTION: - break; //do nothing + case JOptionPane.CANCEL_OPTION: + break; //do nothing + } + } else { + //drawable db is not stale, just open it + ImageGalleryTopComponent.openTopComponent(); } - } else { - //drawable db is not stale, just open it - ImageGalleryTopComponent.openTopComponent(); + } catch (NoCurrentCaseException noCurrentCaseException) { + logger.log(Level.WARNING, "There was no case open when Image Gallery was opened.", noCurrentCaseException); } } - + private boolean tooManyFiles() { try { return FILE_LIMIT < Case.getCurrentCaseThrows().getSleuthkitCase().countFilesWhere("1 = 1"); @@ -192,7 +199,7 @@ public final class OpenAction extends CallableSystemAction { //if there is any doubt (no case, tskcore error, etc) just disable . return false; } - + @NbBundle.Messages({ "ImageGallery.showTooManyFiles.contentText=" + "There are too many files in the DB to ensure reasonable performance." @@ -207,17 +214,17 @@ public final class OpenAction extends CallableSystemAction { dialog.setHeaderText(Bundle.ImageGallery_showTooManyFiles_headerText()); dialog.showAndWait(); } - + @Override public String getName() { return VIEW_IMAGES_VIDEOS; } - + @Override public HelpCtx getHelpCtx() { return HelpCtx.DEFAULT_HELP; } - + @Override public boolean asynchronous() { return false; // run on edt diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index a222148f83..4b39e50a3e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -278,7 +278,6 @@ public final class DrawableDB { } else { throw new TskCoreException("Failed to initialize Image Gallery db schema"); } - } /** @@ -1043,17 +1042,22 @@ public final class DrawableDB { } /** + * Get all the values that are in db for the given attribute. * * - * - * @param groupBy - * @param sortBy - * @param sortOrder + * @param The type of values for the given attribute. + * @param groupBy The attribute to get the values for. + * @param sortBy The way to sort the results. Only GROUP_BY_VAL and + * FILE_COUNT are supported. + * @param sortOrder Sort ascending or descending. + * @param dataSource * * @return + * + * @throws org.sleuthkit.datamodel.TskCoreException */ @SuppressWarnings("unchecked") - public > Multimap findValuesForAttribute(DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder, DataSource dataSource) throws TskDataException, TskCoreException { + public > Multimap findValuesForAttribute(DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder, DataSource dataSource) throws TskCoreException { Multimap values = HashMultimap.create(); @@ -1106,16 +1110,26 @@ public final class DrawableDB { ResultSet results = stmt.executeQuery(query.toString())) { while (results.next()) { /* - * I don't like that we have to do this cast here, but - * can't think of a better alternative at the momment - * unless something has gone seriously wrong, we know - * this should be of type A even if JAVA doesn't + * I don't like that we have to do this cast to A here, + * but can't think of a better alternative at the + * momment unless something has gone seriously wrong, we + * know this should be of type A even if JAVA doesn't */ values.put(tskCase.getDataSource(results.getLong("data_source_obj_id")), (A) results.getObject(groupBy.attrName.toString())); } } catch (SQLException ex) { - logger.log(Level.WARNING, "Unable to get values for attribute", ex); //NON-NLS + if (ex.getCause() instanceof java.lang.InterruptedException) { + /* It seems like this originaly comes out of c3p0 when + * its thread is intereupted (cancelled because of + * regroup). It should be safe to just swallow this and + * move on. + */ + } else { + throw new TskCoreException("Unable to get values for attribute", ex); //NON-NLS + } + } catch (TskDataException ex) { + throw new TskCoreException("Unable to get values for attribute", ex); //NON-NLS } finally { dbReadUnlock(); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java index b73e3ef5d0..39e9aa21a8 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java @@ -3,6 +3,7 @@ package org.sleuthkit.autopsy.imagegallery.datamodel; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import java.sql.SQLException; import java.util.Collections; import java.util.Set; import java.util.logging.Level; @@ -15,15 +16,13 @@ import org.sleuthkit.datamodel.TskCoreException; */ public class HashSetManager { + /** The db that initial values are loaded from. */ + private final DrawableDB db; + public HashSetManager(DrawableDB db) { this.db = db; } - /** - * The db that initial values are loaded from. - */ - private DrawableDB db = null; - /** * the internal cache from fileID to a set of hashset names. */ @@ -38,9 +37,14 @@ public class HashSetManager { */ private Set getHashSetsForFileHelper(long fileID) { try { - return db.getHashSetsForFile(fileID); - } catch (TskCoreException ex) { - Logger.getLogger(HashSetManager.class.getName()).log(Level.SEVERE, "Failed to get Hash Sets for file", ex); //NON-NLS + if (db.isClosed()) { + Logger.getLogger(HashSetManager.class.getName()).log(Level.WARNING, "Failed to get Hash Sets for file. The Db connection was already closed."); //NON-NLS + return Collections.emptySet(); + } else { + return db.getHashSetsForFile(fileID); + } + } catch (TskCoreException | SQLException ex) { + Logger.getLogger(HashSetManager.class.getName()).log(Level.SEVERE, "Failed to get Hash Sets for file."); //NON-NLS return Collections.emptySet(); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java index 226cdbc0f9..28094ebae2 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; +import org.sleuthkit.datamodel.TskCoreException; /** * Represents a set of image/video files in a group. The UI listens to changes @@ -144,7 +145,7 @@ public class DrawableGroup implements Comparable { try { uncatCount.set(ImageGalleryModule.getController().getDatabase().getUncategorizedCount(fileIDs)); - } catch (NoCurrentCaseException | IllegalStateException | NullPointerException ex) { + } catch (TskCoreException | NoCurrentCaseException ex) { LOGGER.log(Level.WARNING, "Could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 5cfe47c50a..9ed2b5463a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -95,7 +95,7 @@ import org.sleuthkit.datamodel.TskDataException; * DrawableGroups. */ public class GroupManager { - + private static final Logger logger = Logger.getLogger(GroupManager.class.getName()); /** An executor to submit async UI related background tasks to. */ @@ -103,7 +103,7 @@ public class GroupManager { new BasicThreadFactory.Builder().namingPattern("GUI Task -%d").build())); //NON-NLS private final ImageGalleryController controller; - + boolean isRegrouping; /** list of all analyzed groups */ @@ -121,7 +121,7 @@ public class GroupManager { */ @GuardedBy("this") private final Map, DrawableGroup> groupMap = new HashMap<>(); - + @GuardedBy("this") private ReGroupTask groupByTask; @@ -136,12 +136,12 @@ public class GroupManager { private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper(); private final DrawableDB db; - + @SuppressWarnings("ReturnOfCollectionOrArrayField") public ObservableList getAnalyzedGroups() { return unmodifiableAnalyzedGroups; } - + @SuppressWarnings("ReturnOfCollectionOrArrayField") public ObservableList getUnSeenGroups() { return unmodifiableUnSeenGroups; @@ -211,7 +211,7 @@ public class GroupManager { synchronized public DrawableGroup getGroupForKey(@Nonnull GroupKey groupKey) { return groupMap.get(groupKey); } - + synchronized public void reset() { if (groupByTask != null) { groupByTask.cancel(true); @@ -221,16 +221,16 @@ public class GroupManager { setGroupBy(DrawableAttribute.PATH); setSortOrder(SortOrder.ASCENDING); setDataSource(null); - + unSeenGroups.forEach(controller.getCategoryManager()::unregisterListener); unSeenGroups.clear(); analyzedGroups.forEach(controller.getCategoryManager()::unregisterListener); analyzedGroups.clear(); - + groupMap.values().forEach(controller.getCategoryManager()::unregisterListener); groupMap.clear(); } - + synchronized public boolean isRegrouping() { return isRegrouping; } @@ -255,7 +255,7 @@ public class GroupManager { } }); } - + synchronized private void updateUnSeenGroups(DrawableGroup group, boolean seen) { if (seen) { unSeenGroups.removeAll(group); @@ -294,7 +294,7 @@ public class GroupManager { unSeenGroups.remove(group); sortUnseenGroups(); } - + } } return group; @@ -305,17 +305,21 @@ public class GroupManager { return popuplateIfAnalyzed(groupKey, null); } } - + synchronized private void sortUnseenGroups() { - FXCollections.sort(unSeenGroups, makeGroupComparator(getSortOrder(), getSortBy())); + if (isNotEmpty(unSeenGroups)) { + FXCollections.sort(unSeenGroups, makeGroupComparator(getSortOrder(), getSortBy())); + } } - + synchronized private void sortAnalyzedGroups() { - FXCollections.sort(analyzedGroups, makeGroupComparator(getSortOrder(), getSortBy())); + if (isNotEmpty(analyzedGroups)) { + FXCollections.sort(analyzedGroups, makeGroupComparator(getSortOrder(), getSortBy())); + } } - + synchronized public Set getFileIDsInGroup(GroupKey groupKey) throws TskCoreException { - + switch (groupKey.getAttribute().attrName) { //these cases get special treatment case CATEGORY: @@ -336,7 +340,7 @@ public class GroupManager { // Unless the list of file IDs is necessary, use countFilesWithCategory() to get the counts. synchronized public Set getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException { Set fileIDsToReturn = Collections.emptySet(); - + try { final DrawableTagsManager tagsManager = controller.getTagsManager(); if (category == DhsImageCategory.ZERO) { @@ -350,10 +354,10 @@ public class GroupManager { .forEach(fileIDs::add); } } - + fileIDsToReturn = db.findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(fileIDs, ',') + ")"); //NON-NLS } else { - + List contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category)); fileIDsToReturn = contentTags.stream() .filter(ct -> ct.getContent() instanceof AbstractFile) @@ -365,15 +369,15 @@ public class GroupManager { logger.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS throw ex; } - + return fileIDsToReturn; } - + synchronized public Set getFileIDsWithTag(TagName tagName) throws TskCoreException { Set files = new HashSet<>(); try { List contentTags = controller.getTagsManager().getContentTagsByTagName(tagName); - + for (ContentTag ct : contentTags) { if (ct.getContent() instanceof AbstractFile && db.isInDB(ct.getContent().getId())) { files.add(ct.getContent().getId()); @@ -385,51 +389,51 @@ public class GroupManager { throw ex; } } - + public synchronized GroupSortBy getSortBy() { return sortByProp.get(); } - + synchronized void setSortBy(GroupSortBy sortBy) { sortByProp.set(sortBy); } - + public ReadOnlyObjectProperty< GroupSortBy> getSortByProperty() { return sortByProp.getReadOnlyProperty(); } - + public synchronized DrawableAttribute getGroupBy() { return groupByProp.get(); } - + synchronized void setGroupBy(DrawableAttribute groupBy) { groupByProp.set(groupBy); } - + public ReadOnlyObjectProperty> getGroupByProperty() { return groupByProp.getReadOnlyProperty(); } - + public synchronized SortOrder getSortOrder() { return sortOrderProp.get(); } - + synchronized void setSortOrder(SortOrder sortOrder) { sortOrderProp.set(sortOrder); } - + public ReadOnlyObjectProperty getSortOrderProperty() { return sortOrderProp.getReadOnlyProperty(); } - + public synchronized DataSource getDataSource() { return dataSourceProp.get(); } - + synchronized void setDataSource(DataSource dataSource) { dataSourceProp.set(dataSource); } - + public ReadOnlyObjectProperty getDataSourceProperty() { return dataSourceProp.getReadOnlyProperty(); } @@ -446,7 +450,7 @@ public class GroupManager { * sorting has changed. */ public synchronized > void regroup(DataSource dataSource, DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder, Boolean force) { - + if (!Case.isCaseOpen()) { return; } @@ -455,7 +459,7 @@ public class GroupManager { if (dataSource != getDataSource() || groupBy != getGroupBy() || force) { - + setDataSource(dataSource); setGroupBy(groupBy); setSortBy(sortBy); @@ -471,17 +475,15 @@ public class GroupManager { // resort the list of groups setSortBy(sortBy); setSortOrder(sortOrder); - Platform.runLater(() -> { - FXCollections.sort(analyzedGroups, makeGroupComparator(sortOrder, sortBy)); - FXCollections.sort(unSeenGroups, makeGroupComparator(sortOrder, sortBy)); - }); + sortAnalyzedGroups(); + sortUnseenGroups(); } } - + public ReadOnlyDoubleProperty regroupProgress() { return regroupProgress.getReadOnlyProperty(); } - + @Subscribe synchronized public void handleTagAdded(ContentTagAddedEvent evt) { GroupKey newGroupKey = null; @@ -501,7 +503,7 @@ public class GroupManager { addFileToGroup(g, newGroupKey, fileID); } } - + @SuppressWarnings("AssignmentToMethodParameter") synchronized private void addFileToGroup(DrawableGroup g, final GroupKey groupKey, final long fileID) { if (g == null) { @@ -514,7 +516,7 @@ public class GroupManager { group.addFile(fileID); } } - + @Subscribe synchronized public void handleTagDeleted(ContentTagDeletedEvent evt) { GroupKey groupKey = null; @@ -530,14 +532,14 @@ public class GroupManager { DrawableGroup g = removeFromGroup(groupKey, fileID); } } - + @Subscribe synchronized public void handleFileRemoved(Collection removedFileIDs) { - + for (final long fileId : removedFileIDs) { //get grouping(s) this file would be in Set> groupsForFile = getGroupKeysForFileID(fileId); - + for (GroupKey gk : groupsForFile) { removeFromGroup(gk, fileId); } @@ -557,7 +559,7 @@ public class GroupManager { * groups( if we are grouping by say make or model) -jm */ for (long fileId : updatedFileIDs) { - + controller.getHashSetManager().invalidateHashSetsForFile(fileId); //get grouping(s) this file would be in @@ -571,7 +573,7 @@ public class GroupManager { //we fire this event for all files so that the category counts get updated during initial db population controller.getCategoryManager().fireChange(updatedFileIDs, null); } - + synchronized private DrawableGroup popuplateIfAnalyzed(GroupKey groupKey, ReGroupTask task) { /* * If this method call is part of a ReGroupTask and that task is @@ -603,27 +605,27 @@ public class GroupManager { controller.getCategoryManager().registerListener(group); groupMap.put(groupKey, group); } - + if (analyzedGroups.contains(group) == false) { analyzedGroups.add(group); sortAnalyzedGroups(); } updateUnSeenGroups(group, groupSeen); - + return group; - + } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex); //NON-NLS } } } - + return null; } - + synchronized public Set getFileIDsWithMimeType(String mimeType) throws TskCoreException { - + HashSet hashSet = new HashSet<>(); String query = (null == mimeType) ? "SELECT obj_id FROM tsk_files WHERE mime_type IS NULL" //NON-NLS @@ -638,7 +640,7 @@ public class GroupManager { } } return hashSet; - + } catch (Exception ex) { throw new TskCoreException("Failed to get file ids with mime type " + mimeType, ex); } @@ -657,91 +659,93 @@ public class GroupManager { "# {1} - atribute value", "ReGroupTask.progressUpdate=regrouping files by {0} : {1}"}) private class ReGroupTask> extends LoggedTask { - + private final DataSource dataSource; private final DrawableAttribute groupBy; private final GroupSortBy sortBy; private final SortOrder sortOrder; - + private final ProgressHandle groupProgress; - + ReGroupTask(DataSource dataSource, DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder) { super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), true); this.dataSource = dataSource; this.groupBy = groupBy; this.sortBy = sortBy; this.sortOrder = sortOrder; - + groupProgress = ProgressHandle.createHandle(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), this); } - + @Override public boolean isCancelled() { return super.isCancelled(); } - + @Override protected Void call() throws Exception { - - if (isCancelled()) { - return null; - } - groupProgress.start(); - - synchronized (GroupManager.this) { - analyzedGroups.clear(); - unSeenGroups.clear(); - - // Get the list of group keys - final Multimap valsByDataSource = findValuesForAttribute(); - groupProgress.switchToDeterminate(valsByDataSource.entries().size()); - int p = 0; - // For each key value, partially create the group and add it to the list. - for (final Map.Entry val : valsByDataSource.entries()) { - if (isCancelled()) { - return null; - } - p++; - updateMessage(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val.getValue())); - updateProgress(p, valsByDataSource.size()); - groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); - popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); + try { + if (isCancelled()) { + return null; } - isRegrouping = false; - - Optional viewedGroup - = Optional.ofNullable(controller.getViewState()) - .flatMap(GroupViewState::getGroup); - Optional> viewedKey = viewedGroup.map(DrawableGroup::getGroupKey); - DataSource dataSourceOfCurrentGroup - = viewedKey.flatMap(GroupKey::getDataSource) - .orElse(null); - DrawableAttribute attributeOfCurrentGroup - = viewedKey.map(GroupKey::getAttribute) - .orElse(null); - - /* if no group or if groupbies are different or if data source - * != null and does not equal group */ - if (viewedGroup.isPresent() == false - || (getDataSource() != null && notEqual(dataSourceOfCurrentGroup, getDataSource())) - || getGroupBy() != attributeOfCurrentGroup) { - //the current group should not be visible so ... - if (isNotEmpty(unSeenGroups)) {// show then next unseen group - controller.advance(GroupViewState.tile(unSeenGroups.get(0))); - } else if (isNotEmpty(analyzedGroups)) { - //show the first analyzed group. - controller.advance(GroupViewState.tile(analyzedGroups.get(0))); - } else { //there are no groups, clear the group area. - controller.advance(GroupViewState.tile(null)); + groupProgress.start(); + + synchronized (GroupManager.this) { + analyzedGroups.clear(); + unSeenGroups.clear(); + + Multimap valsByDataSource = null; + // Get the list of group keys + valsByDataSource = findValuesForAttribute(); + groupProgress.switchToDeterminate(valsByDataSource.entries().size()); + int p = 0; + // For each key value, partially create the group and add it to the list. + for (final Map.Entry val : valsByDataSource.entries()) { + if (isCancelled()) { + return null; + } + p++; + updateMessage(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val.getValue())); + updateProgress(p, valsByDataSource.size()); + groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); + popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); } - } //else, the current group is for the given datasource, so just keep it in view. - } - groupProgress.finish(); - updateProgress(1, 1); + isRegrouping = false; + + Optional viewedGroup + = Optional.ofNullable(controller.getViewState()) + .flatMap(GroupViewState::getGroup); + Optional> viewedKey = viewedGroup.map(DrawableGroup::getGroupKey); + DataSource dataSourceOfCurrentGroup + = viewedKey.flatMap(GroupKey::getDataSource) + .orElse(null); + DrawableAttribute attributeOfCurrentGroup + = viewedKey.map(GroupKey::getAttribute) + .orElse(null); + /* if no group or if groupbies are different or if data + * source != null and does not equal group */ + if (viewedGroup.isPresent() == false + || (getDataSource() != null && notEqual(dataSourceOfCurrentGroup, getDataSource())) + || getGroupBy() != attributeOfCurrentGroup) { + //the current group should not be visible so ... + if (isNotEmpty(unSeenGroups)) {// show then next unseen group + controller.advance(GroupViewState.tile(unSeenGroups.get(0))); + } else if (isNotEmpty(analyzedGroups)) { + //show the first analyzed group. + controller.advance(GroupViewState.tile(analyzedGroups.get(0))); + } else { //there are no groups, clear the group area. + controller.advance(GroupViewState.tile(null)); + } + } //else, the current group is for the given datasource, so just keep it in view. + } + } finally { + groupProgress.finish(); + updateProgress(1, 1); + } return null; } - + @Override protected void done() { super.done(); @@ -765,7 +769,7 @@ public class GroupManager { */ public Multimap findValuesForAttribute() { synchronized (GroupManager.this) { - + Multimap results = HashMultimap.create(); try { switch (groupBy.attrName) { @@ -778,17 +782,17 @@ public class GroupManager { .filter(CategoryManager::isNotCategoryTagName) .collect(Collectors.toList())); break; - + case ANALYZED: results.putAll(null, Arrays.asList(false, true)); break; case HASHSET: - + results.putAll(null, new TreeSet<>(db.getHashSetNames())); - + break; case MIME_TYPE: - + HashSet types = new HashSet<>(); // Use the group_concat function to get a list of files for each mime type. @@ -815,21 +819,20 @@ public class GroupManager { Exceptions.printStackTrace(ex); } results.putAll(null, types); - + break; default: //otherwise do straight db query results.putAll(db.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); } - - } catch (TskCoreException | TskDataException ex) { + } catch (TskCoreException ex) { logger.log(Level.SEVERE, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS } return results; } } } - + private static Comparator makeGroupComparator(final SortOrder sortOrder, GroupSortBy comparator) { switch (sortOrder) { case ASCENDING: diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 10407531b9..72b7a73473 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -333,10 +333,6 @@ public class Toolbar extends ToolBar { @Override public void onFailure(Throwable t) { logger.log(Level.SEVERE, "Unable to get datasources for current case.", t); //NON-NLS - Notifications.create().owner(getScene().getRoot()) - .title("Image Gallery Error") - .text(Bundle.Toolbar_getDataSources_errMessage()) - .showError(); } }, Platform::runLater); From 9fe88f29d55357bf0b10f96c14470cd27125c99e Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 5 Sep 2018 13:17:48 +0200 Subject: [PATCH 65/84] downgrade log entry to info when case is closed --- Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java index e856b25418..ec38bb7dbe 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/ImageUtils.java @@ -107,7 +107,7 @@ public class ImageUtils { * NOTE: Must be cleared when the case is changed. */ @Messages({"ImageUtils.ffmpegLoadedError.title=OpenCV FFMpeg", - "ImageUtils.ffmpegLoadedError.msg=OpenCV FFMpeg library failed to load, see log for more details"}) + "ImageUtils.ffmpegLoadedError.msg=OpenCV FFMpeg library failed to load, see log for more details"}) private static final ConcurrentHashMap cacheFileMap = new ConcurrentHashMap<>(); static { @@ -206,7 +206,7 @@ public class ImageUtils { AbstractFile file = (AbstractFile) content; return VideoUtils.isVideoThumbnailSupported(file) - || isImageThumbnailSupported(file); + || isImageThumbnailSupported(file); } /** @@ -388,7 +388,7 @@ public class ImageUtils { String cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory(); return Paths.get(cacheDirectory, "thumbnails", fileID + ".png").toFile(); //NON-NLS } catch (NoCurrentCaseException e) { - LOGGER.log(Level.WARNING, "Could not get cached thumbnail location. No case is open."); //NON-NLS + LOGGER.log(Level.INFO, "Could not get cached thumbnail location. No case is open."); //NON-NLS return null; } }); From 8c33ae8393d8d7c24f824cef83216243037db4b6 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 5 Sep 2018 14:22:56 +0200 Subject: [PATCH 66/84] codacy fixes --- .../imagegallery/ImageGalleryController.java | 65 ++--- .../imagegallery/ImageGalleryModule.java | 157 ++++++------ .../ImageGalleryTopComponent.java | 54 +++-- .../actions/CategorizeGroupAction.java | 3 +- .../imagegallery/actions/OpenAction.java | 1 - .../datamodel/CategoryManager.java | 52 ++-- .../imagegallery/datamodel/DrawableDB.java | 35 +-- .../imagegallery/datamodel/DrawableFile.java | 27 +-- .../datamodel/DrawableTagsManager.java | 33 +-- .../datamodel/HashSetManager.java | 29 ++- .../datamodel/grouping/DrawableGroup.java | 6 +- .../datamodel/grouping/GroupManager.java | 224 +++++++++--------- .../autopsy/imagegallery/gui/StatusBar.java | 2 +- .../imagegallery/gui/SummaryTablePane.java | 1 - .../autopsy/imagegallery/gui/Toolbar.java | 14 +- .../gui/drawableviews/DrawableTile.java | 4 +- .../gui/drawableviews/DrawableUIBase.java | 3 +- .../gui/drawableviews/DrawableView.java | 22 +- .../gui/drawableviews/GroupPane.java | 18 +- .../gui/drawableviews/MetaDataPane.java | 7 +- .../imagegallery/gui/navpanel/NavPanel.java | 2 +- 21 files changed, 360 insertions(+), 399 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 8d6f07f2a1..7c08a7ee42 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -45,18 +45,7 @@ import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.concurrent.Worker; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.ProgressIndicator; -import javafx.scene.layout.Background; -import javafx.scene.layout.BackgroundFill; -import javafx.scene.layout.CornerRadii; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.swing.SortOrder; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import org.netbeans.api.progress.ProgressHandle; import org.openide.util.Cancellable; @@ -70,17 +59,13 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.actions.UndoRedoManager; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; -import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB.DrawableDbBuildStatusEnum; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; import org.sleuthkit.autopsy.imagegallery.datamodel.HashSetManager; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager; -import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy; 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.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; @@ -108,7 +93,7 @@ public final class ImageGalleryController { private final ReadOnlyBooleanWrapper stale = new ReadOnlyBooleanWrapper(false); private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false); - private final SimpleDoubleProperty thumbnailSize = new SimpleDoubleProperty(100); + private final SimpleDoubleProperty thumbnailSizeProp = new SimpleDoubleProperty(100); private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false); private final ReadOnlyIntegerWrapper dbTaskQueueSize = new ReadOnlyIntegerWrapper(0); @@ -130,9 +115,9 @@ public final class ImageGalleryController { return autopsyCase; } private final SleuthkitCase sleuthKitCase; - private final DrawableDB db; + private final DrawableDB drawableDB; - public ReadOnlyBooleanProperty getMetaDataCollapsed() { + public ReadOnlyBooleanProperty metaDataCollapsedProperty() { return metaDataCollapsed.getReadOnlyProperty(); } @@ -140,19 +125,19 @@ public final class ImageGalleryController { this.metaDataCollapsed.set(metaDataCollapsed); } - public DoubleProperty thumbnailSize() { - return thumbnailSize; + public DoubleProperty thumbnailSizeProperty() { + return thumbnailSizeProp; } public GroupViewState getViewState() { return historyManager.getCurrentState(); } - public ReadOnlyBooleanProperty regroupDisabled() { + public ReadOnlyBooleanProperty regroupDisabledProperty() { return regroupDisabled.getReadOnlyProperty(); } - public ReadOnlyObjectProperty viewState() { + public ReadOnlyObjectProperty viewStateProperty() { return historyManager.currentState(); } @@ -165,7 +150,7 @@ public final class ImageGalleryController { } synchronized public DrawableDB getDatabase() { - return db; + return drawableDB; } public void setListeningEnabled(boolean enabled) { @@ -187,7 +172,7 @@ public final class ImageGalleryController { }); } - public ReadOnlyBooleanProperty stale() { + public ReadOnlyBooleanProperty staleProperty() { return stale.getReadOnlyProperty(); } @@ -200,7 +185,7 @@ public final class ImageGalleryController { this.autopsyCase = Objects.requireNonNull(newCase); this.sleuthKitCase = newCase.getSleuthkitCase(); - this.db = DrawableDB.getDrawableDB(this); + this.drawableDB = DrawableDB.getDrawableDB(this); setListeningEnabled(ImageGalleryModule.isEnabledforCase(newCase)); setStale(ImageGalleryModule.isDrawableDBStale(newCase)); @@ -211,7 +196,7 @@ public final class ImageGalleryController { tagsManager.registerListener(groupManager); tagsManager.registerListener(categoryManager); - hashSetManager = new HashSetManager(db); + hashSetManager = new HashSetManager(drawableDB); shutDownDBExecutor(); dbExecutor = getNewDBExecutor(); @@ -233,7 +218,7 @@ public final class ImageGalleryController { } }); - viewState().addListener((Observable observable) -> { + viewStateProperty().addListener((Observable observable) -> { //when the viewed group changes, clear the selection and the undo/redo history selectionModel.clearSelection(); undoManager.clear(); @@ -288,9 +273,9 @@ public final class ImageGalleryController { */ public void rebuildDB() { // queue a rebuild task for each stale data source - getStaleDataSourceIds().forEach((dataSourceObjId) -> { - queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, this, db, sleuthKitCase)); - }); + getStaleDataSourceIds().forEach((dataSourceObjId) + -> queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, this)) + ); } /** @@ -406,7 +391,7 @@ public final class ImageGalleryController { } public DrawableFile getFileFromID(Long fileID) throws TskCoreException { - return db.getFileFromID(fileID); + return drawableDB.getFileFromID(fileID); } public ReadOnlyDoubleProperty regroupProgress() { @@ -617,10 +602,10 @@ public final class ImageGalleryController { ProgressHandle progressHandle; private boolean taskCompletionStatus; - BulkTransferTask(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { + BulkTransferTask(long dataSourceObjId, ImageGalleryController controller) { this.controller = controller; - this.taskDB = taskDB; - this.tskCase = tskCase; + this.taskDB = controller.getDatabase(); + this.tskCase = controller.getSleuthKitCase(); this.dataSourceObjId = dataSourceObjId; DATASOURCE_CLAUSE = " (data_source_obj_id = " + dataSourceObjId + ") "; @@ -748,17 +733,17 @@ public final class ImageGalleryController { @NbBundle.Messages({"CopyAnalyzedFiles.committingDb.status=committing image/video database", "CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.", "CopyAnalyzedFiles.errPopulating.errMsg=There was an error populating Image Gallery database."}) - class CopyAnalyzedFiles extends BulkTransferTask { + static class CopyAnalyzedFiles extends BulkTransferTask { - CopyAnalyzedFiles(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { - super(dataSourceObjId, controller, taskDB, tskCase); + CopyAnalyzedFiles(long dataSourceObjId, ImageGalleryController controller) { + super(dataSourceObjId, controller); } @Override protected void cleanup(boolean success) { // at the end of the task, set the stale status based on the // cumulative status of all data sources - controller.setStale(isDataSourcesTableStale()); + controller.setStale(controller.isDataSourcesTableStale()); } @Override @@ -811,8 +796,8 @@ public final class ImageGalleryController { * * @param dataSourceId Data source object ID */ - PrePopulateDataSourceFiles(long dataSourceObjId, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { - super(dataSourceObjId, controller, taskDB, tskCase); + PrePopulateDataSourceFiles(long dataSourceObjId, ImageGalleryController controller) { + super(dataSourceObjId, controller); } @Override diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java index 1baec7ffbc..5b15a5eacf 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2013-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,6 +38,8 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB; import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.IngestManager.IngestJobEvent; +import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.FILE_DONE; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; @@ -60,7 +62,7 @@ public class ImageGalleryModule { if (controller == null) { try { controller = new ImageGalleryController(Case.getCurrentCaseThrows()); - } catch (Exception ex) { + } catch (NoCurrentCaseException | TskCoreException ex) { throw new NoCurrentCaseException("Error getting ImageGalleryController for the current case.", ex); } } @@ -149,6 +151,9 @@ public class ImageGalleryModule { return (abstractFile.getKnown() != TskData.FileKnown.KNOWN) && FileTypeUtils.isDrawable(abstractFile); } + /** + * Listener for IngestModuleEvents + */ static private class IngestModuleEventListener implements PropertyChangeListener { @Override @@ -162,61 +167,49 @@ public class ImageGalleryModule { IngestManager.getInstance().removeIngestModuleEventListener(this); return; } - switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) { - case CONTENT_CHANGED: - //TODO: do we need to do anything here? -jm - case DATA_ADDED: - /* - * we could listen to DATA events and progressivly update - * files, and get data from DataSource ingest modules, but - * given that most modules don't post new artifacts in the - * events and we would have to query for them, without - * knowing which are the new ones, we just ignore these - * events for now. The relevant data should all be captured - * by file done event, anyways -jm - */ - break; - case FILE_DONE: - /** - * getOldValue has fileID getNewValue has - * {@link Abstractfile} - */ - AbstractFile file = (AbstractFile) evt.getNewValue(); + if (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName()) != FILE_DONE) { + return; + } + // getOldValue has fileID getNewValue has Abstractfile + AbstractFile file = (AbstractFile) evt.getNewValue(); + if (false == file.isFile()) { + return; + } + /* 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) { + return; + } - // 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) { - synchronized (controllerLock) { - if (controller != null) { - if (controller.isListeningEnabled()) { - if (file.isFile()) { - try { - - if (ImageGalleryModule.isDrawableAndNotKnown(file)) { - //this file should be included and we don't already know about it from hash sets (NSRL) - controller.queueDBTask(new ImageGalleryController.UpdateFileTask(file, controller.getDatabase())); - } 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 - controller.queueDBTask(new ImageGalleryController.RemoveFileTask(file, controller.getDatabase())); - } - - } catch (TskCoreException | FileTypeDetector.FileTypeDetectorInitException ex) { - 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."); - } - } - } - } + synchronized (controllerLock) { + if (controller != null && controller.isListeningEnabled()) { + try { + if (isDrawableAndNotKnown(file)) { + //this file should be included and we don't already know about it from hash sets (NSRL) + controller.queueDBTask(new ImageGalleryController.UpdateFileTask(file, controller.getDatabase())); + } 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 */ + controller.queueDBTask(new ImageGalleryController.RemoveFileTask(file, controller.getDatabase())); } + + } catch (TskCoreException | FileTypeDetector.FileTypeDetectorInitException ex) { + 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."); } - break; + } } } } + /** + * Listener for case events. + */ static private class CaseEventListener implements PropertyChangeListener { @Override @@ -233,7 +226,6 @@ public class ImageGalleryModule { synchronized (controllerLock) { switch (Case.Events.valueOf(evt.getPropertyName())) { case CURRENT_CASE: - // case has changes: close window, reset everything SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent); if (controller != null) { @@ -251,32 +243,26 @@ public class ImageGalleryModule { } } break; - case DATA_SOURCE_ADDED: //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 (controller.isListeningEnabled()) { - controller.queueDBTask(new ImageGalleryController.PrePopulateDataSourceFiles(newDataSource.getId(), controller, controller.getDatabase(), controller.getSleuthKitCase())); + controller.queueDBTask(new ImageGalleryController.PrePopulateDataSourceFiles(newDataSource.getId(), controller)); } } - break; - case CONTENT_TAG_ADDED: final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt; if (controller.getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) { controller.getTagsManager().fireTagAddedEvent(tagAddedEvent); } - break; case CONTENT_TAG_DELETED: - final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) evt; if (controller.getDatabase().isInDB(tagDeletedEvent.getDeletedTagInfo().getContentID())) { controller.getTagsManager().fireTagDeletedEvent(tagDeletedEvent); } - break; } } @@ -296,37 +282,34 @@ public class ImageGalleryModule { }) @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 - synchronized (controllerLock) { - if (controller != null) { - controller.setStale(true); + IngestJobEvent eventType = IngestJobEvent.valueOf(evt.getPropertyName()); + if (eventType != IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED + || ((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.REMOTE) { + return; + } + // 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 + synchronized (controllerLock) { + if (controller != null) { + controller.setStale(true); + if (controller.isListeningEnabled() && ImageGalleryTopComponent.isImageGalleryOpen()) { + ImageGalleryController con = controller; + SwingUtilities.invokeLater(() -> { + int showAnswer = JOptionPane.showConfirmDialog(ImageGalleryTopComponent.getTopComponent(), + Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_msg(), + Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_title(), + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); - SwingUtilities.invokeLater(() -> { - synchronized (controllerLock) { - if (controller.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: - controller.rebuildDB(); - break; - case JOptionPane.NO_OPTION: - case JOptionPane.CANCEL_OPTION: - default: - break; //do nothing - } - } - } - }); - } + switch (showAnswer) { + case JOptionPane.YES_OPTION: + con.rebuildDB(); + break; + case JOptionPane.NO_OPTION: + case JOptionPane.CANCEL_OPTION: + default: + break; //do nothing + } + }); } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 1dc35d2466..8b88fd151f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -27,7 +27,6 @@ import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.InvalidationListener; -import javafx.beans.Observable; import javafx.embed.swing.JFXPanel; import javafx.geometry.Insets; import javafx.scene.Node; @@ -50,7 +49,6 @@ import javax.swing.SwingUtilities; import org.apache.commons.collections4.CollectionUtils; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; -import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; @@ -118,12 +116,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl private Scene myScene; private Node infoOverlay; - private final Region infoOverLayBackground = new Region() { - { - setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY))); - setOpacity(.4); - } - }; + private final Region infoOverLayBackground = new TranslucentRegion(); /** * Returns whether the ImageGallery window is open or not. @@ -155,19 +148,19 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl "ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.titleText=Image Gallery",}) public static void openTopComponent() throws NoCurrentCaseException { - final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); - if (tc == null) { + final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID); + if (topComponent == null) { return; } topComponentInitialized = true; - if (tc.isOpened()) { - showTopComponent(tc); + if (topComponent.isOpened()) { + showTopComponent(topComponent); return; } List dataSources = Collections.emptyList(); ImageGalleryController controller = ImageGalleryModule.getController(); - ((ImageGalleryTopComponent) tc).setController(controller); + ((ImageGalleryTopComponent) topComponent).setController(controller); try { dataSources = controller.getSleuthKitCase().getDataSources(); } catch (TskCoreException tskCoreException) { @@ -177,7 +170,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl || controller.getGroupManager().getGroupBy() != DrawableAttribute.PATH) { /* if there is only one datasource or the grouping is already set to * something other than path , don't both to ask for datasource */ - showTopComponent(tc); + showTopComponent(topComponent); return; } @@ -186,18 +179,18 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl dataSources.forEach(dataSource -> dataSourceNames.put(dataSource.getName(), dataSource)); Platform.runLater(() -> { - ChoiceDialog d = new ChoiceDialog<>(null, dataSourceNames.keySet()); - d.setTitle(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_titleText()); - d.setHeaderText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_headerText()); - d.setContentText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_contentText()); - d.initModality(Modality.APPLICATION_MODAL); - GuiUtils.setDialogIcons(d); + ChoiceDialog datasourceDialog = new ChoiceDialog<>(null, dataSourceNames.keySet()); + datasourceDialog.setTitle(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_titleText()); + datasourceDialog.setHeaderText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_headerText()); + datasourceDialog.setContentText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_contentText()); + datasourceDialog.initModality(Modality.APPLICATION_MODAL); + GuiUtils.setDialogIcons(datasourceDialog); - Optional dataSourceName = d.showAndWait(); - DataSource ds = dataSourceName.map(dataSourceNames::get).orElse(null); + Optional dataSourceName = datasourceDialog.showAndWait(); + DataSource dataSource = dataSourceName.map(dataSourceNames::get).orElse(null); GroupManager groupManager = controller.getGroupManager(); - groupManager.regroup(ds, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true); - showTopComponent(tc); + groupManager.regroup(dataSource, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true); + showTopComponent(topComponent); }); } @@ -266,8 +259,9 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl splitPane.getItems().addAll(leftPane, centralStack, metaDataTable); splitPane.setDividerPositions(0.1, 1.0); - controller.getGroupManager().getAnalyzedGroups().addListener((Observable o) -> checkForGroups()); - controller.regroupDisabled().addListener((Observable observable) -> checkForGroups()); + InvalidationListener checkGroupsListener = observable -> checkForGroups(); + controller.getGroupManager().getAnalyzedGroups().addListener(checkGroupsListener); + controller.regroupDisabledProperty().addListener(checkGroupsListener); checkForGroups(); } @@ -405,4 +399,12 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl //remove the ingest spinner centralStack.getChildren().remove(infoOverlay); } + + static final private class TranslucentRegion extends Region { + + TranslucentRegion() { + setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY))); + setOpacity(.4); + } + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java index b5ea4bc421..b49e1ca30c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeGroupAction.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015-16 Basis Technology Corp. + * Copyright 2015-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,7 +34,6 @@ import javafx.scene.control.Label; import javafx.scene.control.Separator; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import org.apache.commons.lang.ObjectUtils; import static org.apache.commons.lang.ObjectUtils.notEqual; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java index ebea08be35..c3948e1b02 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java @@ -34,7 +34,6 @@ import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionReferences; import org.openide.awt.ActionRegistration; -import org.openide.util.Exceptions; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java index 6a4916cb1f..8d287c7881 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java @@ -17,7 +17,6 @@ * limitations under the License. */package org.sleuthkit.autopsy.imagegallery.datamodel; -import com.google.common.base.Function; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; @@ -64,14 +63,23 @@ public class CategoryManager { * initialized from this, and the counting of CAT-0 is always delegated to * this db. */ - private DrawableDB db; + private final DrawableDB drawableDb; + + public CategoryManager(ImageGalleryController controller) { + this.controller = controller; + this.drawableDb = controller.getDatabase(); + } + + private ImageGalleryController getController() { + return controller; + } /** - * Used to distribute {@link CategoryChangeEvent}s + * Used to distribute CategoryChangeEvents */ private final EventBus categoryEventBus = new AsyncEventBus(Executors.newSingleThreadExecutor( - new BasicThreadFactory.Builder().namingPattern("Category Event Bus").uncaughtExceptionHandler((Thread t, Throwable e) -> { //NON-NLS - LOGGER.log(Level.SEVERE, "Uncaught exception in category event bus handler", e); //NON-NLS + new BasicThreadFactory.Builder().namingPattern("Category Event Bus").uncaughtExceptionHandler((Thread thread, Throwable throwable) -> { //NON-NLS + LOGGER.log(Level.SEVERE, "Uncaught exception in category event bus handler", throwable); //NON-NLS }).build() )); @@ -89,20 +97,11 @@ public class CategoryManager { private final LoadingCache catTagNameMap = CacheBuilder.newBuilder().build(new CacheLoader() { @Override - public TagName load(DhsImageCategory cat) throws Exception { + public TagName load(DhsImageCategory cat) throws TskCoreException { return getController().getTagsManager().getTagName(cat); } }); - public CategoryManager(ImageGalleryController controller) { - this.controller = controller; - this.db = controller.getDatabase(); - } - - private ImageGalleryController getController() { - return controller; - } - synchronized public void invalidateCaches() { categoryCounts.invalidateAll(); catTagNameMap.invalidateAll(); @@ -122,7 +121,7 @@ public class CategoryManager { // is going on, so always use the list of file IDs we already have along with the // other category counts instead of trying to track it separately. long allOtherCatCount = getCategoryCount(DhsImageCategory.ONE) + getCategoryCount(DhsImageCategory.TWO) + getCategoryCount(DhsImageCategory.THREE) + getCategoryCount(DhsImageCategory.FOUR) + getCategoryCount(DhsImageCategory.FIVE); - return db.getNumberOfImageFilesInList() - allOtherCatCount; + return drawableDb.getNumberOfImageFilesInList() - allOtherCatCount; } else { return categoryCounts.getUnchecked(cat).sum(); } @@ -142,7 +141,7 @@ public class CategoryManager { /** * decrement the cached value for the number of files with the given - * {@link DhsImageCategory} + * DhsImageCategory * * @param cat the Category to decrement */ @@ -166,7 +165,7 @@ public class CategoryManager { LongAdder longAdder = new LongAdder(); longAdder.decrement(); try { - longAdder.add(db.getCategoryCount(cat)); + longAdder.add(drawableDb.getCategoryCount(cat)); longAdder.increment(); } catch (IllegalStateException ex) { LOGGER.log(Level.WARNING, "Case closed while getting files"); //NON-NLS @@ -203,15 +202,14 @@ public class CategoryManager { try { categoryEventBus.unregister(listener); } catch (IllegalArgumentException e) { - if (e.getMessage().contains("missing event subscriber for an annotated method. Is " + listener + " registered?")) { //NON-NLS - /* - * We don't fully understand why we are getting this exception - * when the groups should all be registered. To avoid cluttering - * the logs we have disabled recording this exception. This - * documented in issues 738 and 802. - */ - //LOGGER.log(Level.WARNING, "Attempted to unregister {0} for category change events, but it was not registered.", listener.toString()); //NON-NLS - } else { + /* + * We don't fully understand why we are getting this exception when + * the groups should all be registered. To avoid cluttering the logs + * we have disabled recording this exception. This documented in + * issues 738 and 802. + */ + + if (!e.getMessage().contains("missing event subscriber for an annotated method. Is " + listener + " registered?")) { //NON-NLS throw e; } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 4b39e50a3e..97e42dfa76 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -42,18 +42,13 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.function.Function; -import java.util.function.Predicate; import java.util.logging.Level; import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.concurrent.GuardedBy; import javax.swing.SortOrder; -import org.apache.commons.lang3.ObjectUtils; import static org.apache.commons.lang3.ObjectUtils.notEqual; 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.datamodel.DhsImageCategory; @@ -80,12 +75,9 @@ import org.sleuthkit.datamodel.TskDataException; import org.sqlite.SQLiteJDBCLoader; /** - * This class is the public interface to the Image / Video Analyzer SQLite - * database. This class borrows a lot of ideas and techniques (for good or ill) - * from {@link SleuthkitCase}. - * - * TODO: Creating an abstract base class for sqlite databases may make sense in - * the future. see also {@link EventsDB} in the timeline viewer. + * This class is the public interface to the Image Gallery SQLite database. This + * class borrows a lot of ideas and techniques (for good or ill) from + * SleuthkitCase */ public final class DrawableDB { @@ -664,14 +656,23 @@ public final class DrawableDB { return false; } - public void setGroupSeen(GroupKey gk, boolean seen) throws TskCoreException { + /** + * Record in the DB that the group with the given key has the given seen + * state. + * + * @param groupKey + * @param seen + * + * @throws TskCoreException + */ + public void setGroupSeen(GroupKey groupKey, boolean seen) throws TskCoreException { String updateSQL; - if (gk.getAttribute() == DrawableAttribute.PATH) { + if (groupKey.getAttribute() == DrawableAttribute.PATH) { updateSQL = String.format("SET seen = %d WHERE VALUE = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = %d", - seen ? 1 : 0, gk.getValueDisplayName(), gk.getAttribute().attrName.toString(), gk.getDataSourceObjId()); + seen ? 1 : 0, groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString(), groupKey.getDataSourceObjId()); } else { updateSQL = String.format("SET seen = %d WHERE VALUE = \'%s\' AND attribute = \'%s\' AND data_source_obj_id = 0", - seen ? 1 : 0, gk.getValueDisplayName(), gk.getAttribute().attrName.toString()); + seen ? 1 : 0, groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString()); } tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL); } @@ -1120,10 +1121,14 @@ public final class DrawableDB { } } catch (SQLException ex) { if (ex.getCause() instanceof java.lang.InterruptedException) { + /* It seems like this originaly comes out of c3p0 when * its thread is intereupted (cancelled because of * regroup). It should be safe to just swallow this and * move on. + * + * see + * https://sourceforge.net/p/c3p0/mailman/c3p0-users/thread/EBB32BB8-6487-43AF-B291-9464C9051869@mchange.com/ */ } else { throw new TskCoreException("Unable to get values for attribute", ex); //NON-NLS diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index 921bb8f150..e22151f934 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.imagegallery.datamodel; -import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import java.lang.ref.SoftReference; import java.text.MessageFormat; import java.util.ArrayList; @@ -32,6 +31,7 @@ import java.util.stream.Collectors; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.concurrent.Task; +import javafx.concurrent.Worker; import javafx.scene.image.Image; import javafx.util.Pair; import javax.annotation.Nonnull; @@ -40,9 +40,8 @@ import org.apache.commons.lang3.text.WordUtils; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.imagegallery.FileTypeUtils; -import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule; -import org.sleuthkit.autopsy.imagegallery.ThumbnailCache; import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -81,8 +80,8 @@ public abstract class DrawableFile { : new ImageFile(file, analyzed); } - public static DrawableFile create(Long id, boolean analyzed) throws TskCoreException, NoCurrentCaseException { - return create(Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(id), analyzed); + public static DrawableFile create(Long fileID, boolean analyzed) throws TskCoreException, NoCurrentCaseException { + return create(Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(fileID), analyzed); } private SoftReference imageRef; @@ -156,8 +155,8 @@ public abstract class DrawableFile { return file.getSleuthkitCase(); } - private Pair, Collection> makeAttributeValuePair(DrawableAttribute t) { - return new Pair<>(t, t.getValue(DrawableFile.this)); + private Pair, Collection> makeAttributeValuePair(DrawableAttribute attribute) { + return new Pair<>(attribute, attribute.getValue(this)); } public String getModel() { @@ -266,14 +265,12 @@ public abstract class DrawableFile { if (image == null || image.isError()) { Task readImageTask = getReadFullSizeImageTaskHelper(); readImageTask.stateProperty().addListener(stateProperty -> { - switch (readImageTask.getState()) { - case SUCCEEDED: - try { - imageRef = new SoftReference<>(readImageTask.get()); - } catch (InterruptedException | ExecutionException exception) { - LOGGER.log(Level.WARNING, getMessageTemplate(exception), getContentPathSafe()); - } - break; + if (readImageTask.getState() == Worker.State.SUCCEEDED) { + try { + imageRef = new SoftReference<>(readImageTask.get()); + } catch (InterruptedException | ExecutionException exception) { + LOGGER.log(Level.WARNING, getMessageTemplate(exception), getContentPathSafe()); + } } }); return readImageTask; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java index e4f52077b1..90b7b5c398 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableTagsManager.java @@ -66,9 +66,8 @@ public final class DrawableTagsManager { Executors.newSingleThreadExecutor( new BasicThreadFactory.Builder() .namingPattern("Tags Event Bus")//NON-NLS - .uncaughtExceptionHandler((Thread t, Throwable e) -> { - logger.log(Level.SEVERE, "Uncaught exception in DrawableTagsManager event bus handler.", e); //NON-NLS - }) + .uncaughtExceptionHandler((Thread thread, Throwable throwable) + -> logger.log(Level.SEVERE, "Uncaught exception in DrawableTagsManager event bus handler.", throwable)) //NON-NLS .build())); public DrawableTagsManager(ImageGalleryController controller) throws TskCoreException { @@ -180,29 +179,19 @@ public final class DrawableTagsManager { } public TagName getTagName(String displayName) throws TskCoreException { + + TagName returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName); + if (returnTagName != null) { + return returnTagName; + } try { - TagName returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName); + return autopsyTagsManager.addTagName(displayName); + } catch (TagsManager.TagNameAlreadyExistsException ex) { + returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName); if (returnTagName != null) { return returnTagName; } - try { - return autopsyTagsManager.addTagName(displayName); - } catch (TagsManager.TagNameAlreadyExistsException ex) { - returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName); - if (returnTagName != null) { - return returnTagName; - } - - throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex); - - } catch (NullPointerException | IllegalStateException ex) { - logger.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS - throw new TskCoreException("Case was closed out from underneath", ex); - - } - } catch (NullPointerException | IllegalStateException ex) { - logger.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS - throw new TskCoreException("Case was closed out from underneath", ex); + throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java index 39e9aa21a8..19eab6792a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java @@ -1,4 +1,21 @@ -package org.sleuthkit.autopsy.imagegallery.datamodel; +/* + * Autopsy Forensic Browser + * + * Copyright 2013-18 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */package org.sleuthkit.autopsy.imagegallery.datamodel; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -17,10 +34,10 @@ import org.sleuthkit.datamodel.TskCoreException; public class HashSetManager { /** The db that initial values are loaded from. */ - private final DrawableDB db; + private final DrawableDB drawableDB; - public HashSetManager(DrawableDB db) { - this.db = db; + public HashSetManager(DrawableDB drawableDB) { + this.drawableDB = drawableDB; } /** @@ -37,11 +54,11 @@ public class HashSetManager { */ private Set getHashSetsForFileHelper(long fileID) { try { - if (db.isClosed()) { + if (drawableDB.isClosed()) { Logger.getLogger(HashSetManager.class.getName()).log(Level.WARNING, "Failed to get Hash Sets for file. The Db connection was already closed."); //NON-NLS return Collections.emptySet(); } else { - return db.getHashSetsForFile(fileID); + return drawableDB.getHashSetsForFile(fileID); } } catch (TskCoreException | SQLException ex) { Logger.getLogger(HashSetManager.class.getName()).log(Level.SEVERE, "Failed to get Hash Sets for file."); //NON-NLS diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java index 28094ebae2..64a73d02b8 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2013-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,10 +32,8 @@ import javafx.beans.property.ReadOnlyLongWrapper; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; -import org.openide.util.Exceptions; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; @@ -128,7 +126,7 @@ public class DrawableGroup implements Comparable { .map(ImageGalleryModule.getController().getHashSetManager()::isInAnyHashSet) .filter(Boolean::booleanValue) .count()); - } catch (NoCurrentCaseException | IllegalStateException | NullPointerException ex) { + } catch (NoCurrentCaseException ex) { LOGGER.log(Level.WARNING, "Could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 9ed2b5463a..fd40a45325 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -26,7 +26,6 @@ import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -43,12 +42,9 @@ import java.util.TreeSet; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; -import java.util.function.Consumer; -import java.util.function.Predicate; import java.util.logging.Level; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.util.stream.Stream; import javafx.application.Platform; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleWrapper; @@ -87,7 +83,6 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData.DbType; -import org.sleuthkit.datamodel.TskDataException; /** * Provides an abstraction layer on top of DrawableDB ( and to some extent @@ -95,7 +90,7 @@ import org.sleuthkit.datamodel.TskDataException; * DrawableGroups. */ public class GroupManager { - + private static final Logger logger = Logger.getLogger(GroupManager.class.getName()); /** An executor to submit async UI related background tasks to. */ @@ -103,45 +98,46 @@ public class GroupManager { new BasicThreadFactory.Builder().namingPattern("GUI Task -%d").build())); //NON-NLS private final ImageGalleryController controller; - - boolean isRegrouping; + private final DrawableDB drawableDB; + + @GuardedBy("this") //NOPMD + boolean regrouping; /** list of all analyzed groups */ - @GuardedBy("this") + @GuardedBy("this") //NOPMD private final ObservableList analyzedGroups = FXCollections.observableArrayList(); private final ObservableList unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups); /** list of unseen groups */ - @GuardedBy("this") + @GuardedBy("this") //NOPMD private final ObservableList unSeenGroups = FXCollections.observableArrayList(); private final ObservableList unmodifiableUnSeenGroups = FXCollections.unmodifiableObservableList(unSeenGroups); /** * map from GroupKey} to DrawableGroupSs. All groups (even not fully * analyzed or not visible groups could be in this map */ - @GuardedBy("this") + @GuardedBy("this") //NOPMD private final Map, DrawableGroup> groupMap = new HashMap<>(); - - @GuardedBy("this") + + @GuardedBy("this") //NOPMD private ReGroupTask groupByTask; /* * --- current grouping/sorting attributes --- */ - @GuardedBy("this") + @GuardedBy("this") //NOPMD private final ReadOnlyObjectWrapper< GroupSortBy> sortByProp = new ReadOnlyObjectWrapper<>(GroupSortBy.PRIORITY); private final ReadOnlyObjectWrapper< DrawableAttribute> groupByProp = new ReadOnlyObjectWrapper<>(DrawableAttribute.PATH); private final ReadOnlyObjectWrapper sortOrderProp = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING); private final ReadOnlyObjectWrapper dataSourceProp = new ReadOnlyObjectWrapper<>(null);//null indicates all datasources private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper(); - private final DrawableDB db; - + @SuppressWarnings("ReturnOfCollectionOrArrayField") public ObservableList getAnalyzedGroups() { return unmodifiableAnalyzedGroups; } - + @SuppressWarnings("ReturnOfCollectionOrArrayField") public ObservableList getUnSeenGroups() { return unmodifiableUnSeenGroups; @@ -154,7 +150,7 @@ public class GroupManager { */ public GroupManager(ImageGalleryController controller) { this.controller = controller; - this.db = controller.getDatabase(); + this.drawableDB = controller.getDatabase(); } /** @@ -193,7 +189,7 @@ public class GroupManager { */ synchronized public Set> getGroupKeysForFileID(Long fileID) { try { - DrawableFile file = db.getFileFromID(fileID); + DrawableFile file = drawableDB.getFileFromID(fileID); return getGroupKeysForFile(file); } catch (TskCoreException ex) { Logger.getLogger(GroupManager.class.getName()).log(Level.SEVERE, "failed to load file with id: " + fileID + " from database", ex); //NON-NLS @@ -211,28 +207,28 @@ public class GroupManager { synchronized public DrawableGroup getGroupForKey(@Nonnull GroupKey groupKey) { return groupMap.get(groupKey); } - + synchronized public void reset() { if (groupByTask != null) { groupByTask.cancel(true); - isRegrouping = false; + regrouping = false; } setSortBy(GroupSortBy.GROUP_BY_VALUE); setGroupBy(DrawableAttribute.PATH); setSortOrder(SortOrder.ASCENDING); setDataSource(null); - + unSeenGroups.forEach(controller.getCategoryManager()::unregisterListener); unSeenGroups.clear(); analyzedGroups.forEach(controller.getCategoryManager()::unregisterListener); analyzedGroups.clear(); - + groupMap.values().forEach(controller.getCategoryManager()::unregisterListener); groupMap.clear(); } - + synchronized public boolean isRegrouping() { - return isRegrouping; + return regrouping; } /** @@ -247,7 +243,7 @@ public class GroupManager { public ListenableFuture setGroupSeen(DrawableGroup group, boolean seen) { return exec.submit(() -> { try { - db.setGroupSeen(group.getGroupKey(), seen); + drawableDB.setGroupSeen(group.getGroupKey(), seen); group.setSeen(seen); updateUnSeenGroups(group, seen); } catch (TskCoreException ex) { @@ -255,7 +251,7 @@ public class GroupManager { } }); } - + synchronized private void updateUnSeenGroups(DrawableGroup group, boolean seen) { if (seen) { unSeenGroups.removeAll(group); @@ -284,18 +280,17 @@ public class GroupManager { group.removeFile(fileID); // If we're grouping by category, we don't want to remove empty groups. - if (groupKey.getAttribute() != DrawableAttribute.CATEGORY) { - if (group.getFileIDs().isEmpty()) { - if (analyzedGroups.contains(group)) { - analyzedGroups.remove(group); - sortAnalyzedGroups(); - } - if (unSeenGroups.contains(group)) { - unSeenGroups.remove(group); - sortUnseenGroups(); - } - + if (groupKey.getAttribute() != DrawableAttribute.CATEGORY + && group.getFileIDs().isEmpty()) { + if (analyzedGroups.contains(group)) { + analyzedGroups.remove(group); + sortAnalyzedGroups(); } + if (unSeenGroups.contains(group)) { + unSeenGroups.remove(group); + sortUnseenGroups(); + } + } return group; } @@ -305,21 +300,21 @@ public class GroupManager { return popuplateIfAnalyzed(groupKey, null); } } - + synchronized private void sortUnseenGroups() { if (isNotEmpty(unSeenGroups)) { FXCollections.sort(unSeenGroups, makeGroupComparator(getSortOrder(), getSortBy())); } } - + synchronized private void sortAnalyzedGroups() { if (isNotEmpty(analyzedGroups)) { FXCollections.sort(analyzedGroups, makeGroupComparator(getSortOrder(), getSortBy())); } } - + synchronized public Set getFileIDsInGroup(GroupKey groupKey) throws TskCoreException { - + switch (groupKey.getAttribute().attrName) { //these cases get special treatment case CATEGORY: @@ -332,7 +327,7 @@ public class GroupManager { // return getFileIDsWithHashSetName((String) groupKey.getValue()); default: //straight db query - return db.getFileIDsInGroup(groupKey); + return drawableDB.getFileIDsInGroup(groupKey); } } @@ -340,7 +335,7 @@ public class GroupManager { // Unless the list of file IDs is necessary, use countFilesWithCategory() to get the counts. synchronized public Set getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException { Set fileIDsToReturn = Collections.emptySet(); - + try { final DrawableTagsManager tagsManager = controller.getTagsManager(); if (category == DhsImageCategory.ZERO) { @@ -350,18 +345,18 @@ public class GroupManager { tagsManager.getContentTagsByTagName(catTagName).stream() .filter(ct -> ct.getContent() instanceof AbstractFile) .map(ct -> ct.getContent().getId()) - .filter(db::isInDB) + .filter(drawableDB::isInDB) .forEach(fileIDs::add); } } - - fileIDsToReturn = db.findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(fileIDs, ',') + ")"); //NON-NLS + + fileIDsToReturn = drawableDB.findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(fileIDs, ',') + ")"); //NON-NLS } else { - + List contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category)); fileIDsToReturn = contentTags.stream() .filter(ct -> ct.getContent() instanceof AbstractFile) - .filter(ct -> db.isInDB(ct.getContent().getId())) + .filter(ct -> drawableDB.isInDB(ct.getContent().getId())) .map(ct -> ct.getContent().getId()) .collect(Collectors.toSet()); } @@ -369,17 +364,17 @@ public class GroupManager { logger.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS throw ex; } - + return fileIDsToReturn; } - + synchronized public Set getFileIDsWithTag(TagName tagName) throws TskCoreException { Set files = new HashSet<>(); try { List contentTags = controller.getTagsManager().getContentTagsByTagName(tagName); - + for (ContentTag ct : contentTags) { - if (ct.getContent() instanceof AbstractFile && db.isInDB(ct.getContent().getId())) { + if (ct.getContent() instanceof AbstractFile && drawableDB.isInDB(ct.getContent().getId())) { files.add(ct.getContent().getId()); } } @@ -389,51 +384,51 @@ public class GroupManager { throw ex; } } - + public synchronized GroupSortBy getSortBy() { return sortByProp.get(); } - + synchronized void setSortBy(GroupSortBy sortBy) { sortByProp.set(sortBy); } - + public ReadOnlyObjectProperty< GroupSortBy> getSortByProperty() { return sortByProp.getReadOnlyProperty(); } - + public synchronized DrawableAttribute getGroupBy() { return groupByProp.get(); } - + synchronized void setGroupBy(DrawableAttribute groupBy) { groupByProp.set(groupBy); } - + public ReadOnlyObjectProperty> getGroupByProperty() { return groupByProp.getReadOnlyProperty(); } - + public synchronized SortOrder getSortOrder() { return sortOrderProp.get(); } - + synchronized void setSortOrder(SortOrder sortOrder) { sortOrderProp.set(sortOrder); } - + public ReadOnlyObjectProperty getSortOrderProperty() { return sortOrderProp.getReadOnlyProperty(); } - + public synchronized DataSource getDataSource() { return dataSourceProp.get(); } - + synchronized void setDataSource(DataSource dataSource) { dataSourceProp.set(dataSource); } - + public ReadOnlyObjectProperty getDataSourceProperty() { return dataSourceProp.getReadOnlyProperty(); } @@ -450,7 +445,7 @@ public class GroupManager { * sorting has changed. */ public synchronized > void regroup(DataSource dataSource, DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder, Boolean force) { - + if (!Case.isCaseOpen()) { return; } @@ -459,7 +454,7 @@ public class GroupManager { if (dataSource != getDataSource() || groupBy != getGroupBy() || force) { - + setDataSource(dataSource); setGroupBy(groupBy); setSortBy(sortBy); @@ -467,7 +462,7 @@ public class GroupManager { if (groupByTask != null) { groupByTask.cancel(true); } - isRegrouping = true; + regrouping = true; groupByTask = new ReGroupTask<>(dataSource, groupBy, sortBy, sortOrder); Platform.runLater(() -> regroupProgress.bind(groupByTask.progressProperty())); exec.submit(groupByTask); @@ -479,11 +474,11 @@ public class GroupManager { sortUnseenGroups(); } } - + public ReadOnlyDoubleProperty regroupProgress() { return regroupProgress.getReadOnlyProperty(); } - + @Subscribe synchronized public void handleTagAdded(ContentTagAddedEvent evt) { GroupKey newGroupKey = null; @@ -503,20 +498,19 @@ public class GroupManager { addFileToGroup(g, newGroupKey, fileID); } } - + @SuppressWarnings("AssignmentToMethodParameter") - synchronized private void addFileToGroup(DrawableGroup g, final GroupKey groupKey, final long fileID) { - if (g == null) { + synchronized private void addFileToGroup(DrawableGroup group, final GroupKey groupKey, final long fileID) { + if (group == null) { //if there wasn't already a group check if there should be one now - g = popuplateIfAnalyzed(groupKey, null); + group = popuplateIfAnalyzed(groupKey, null); } - DrawableGroup group = g; if (group != null) { //if there is aleady a group that was previously deemed fully analyzed, then add this newly analyzed file to it. group.addFile(fileID); } } - + @Subscribe synchronized public void handleTagDeleted(ContentTagDeletedEvent evt) { GroupKey groupKey = null; @@ -532,14 +526,14 @@ public class GroupManager { DrawableGroup g = removeFromGroup(groupKey, fileID); } } - + @Subscribe synchronized public void handleFileRemoved(Collection removedFileIDs) { - + for (final long fileId : removedFileIDs) { //get grouping(s) this file would be in Set> groupsForFile = getGroupKeysForFileID(fileId); - + for (GroupKey gk : groupsForFile) { removeFromGroup(gk, fileId); } @@ -559,7 +553,7 @@ public class GroupManager { * groups( if we are grouping by say make or model) -jm */ for (long fileId : updatedFileIDs) { - + controller.getHashSetManager().invalidateHashSetsForFile(fileId); //get grouping(s) this file would be in @@ -573,7 +567,7 @@ public class GroupManager { //we fire this event for all files so that the category counts get updated during initial db population controller.getCategoryManager().fireChange(updatedFileIDs, null); } - + synchronized private DrawableGroup popuplateIfAnalyzed(GroupKey groupKey, ReGroupTask task) { /* * If this method call is part of a ReGroupTask and that task is @@ -590,12 +584,13 @@ public class GroupManager { * analyzed because we don't know all the files that will be a part * of that group. just show them no matter what. */ - if (((groupKey.getAttribute() != DrawableAttribute.PATH) || db.isGroupAnalyzed(groupKey))) { + if (groupKey.getAttribute() != DrawableAttribute.PATH + || drawableDB.isGroupAnalyzed(groupKey)) { try { Set fileIDs = getFileIDsInGroup(groupKey); if (Objects.nonNull(fileIDs)) { DrawableGroup group; - final boolean groupSeen = db.isGroupSeen(groupKey); + final boolean groupSeen = drawableDB.isGroupSeen(groupKey); if (groupMap.containsKey(groupKey)) { group = groupMap.get(groupKey); group.setFiles(ObjectUtils.defaultIfNull(fileIDs, Collections.emptySet())); @@ -605,27 +600,27 @@ public class GroupManager { controller.getCategoryManager().registerListener(group); groupMap.put(groupKey, group); } - + if (analyzedGroups.contains(group) == false) { analyzedGroups.add(group); sortAnalyzedGroups(); } updateUnSeenGroups(group, groupSeen); - + return group; - + } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex); //NON-NLS } } } - + return null; } - + synchronized public Set getFileIDsWithMimeType(String mimeType) throws TskCoreException { - + HashSet hashSet = new HashSet<>(); String query = (null == mimeType) ? "SELECT obj_id FROM tsk_files WHERE mime_type IS NULL" //NON-NLS @@ -635,12 +630,12 @@ public class GroupManager { ResultSet resultSet = executeQuery.getResultSet();) { while (resultSet.next()) { final long fileID = resultSet.getLong("obj_id"); //NON-NLS - if (db.isInDB(fileID)) { + if (drawableDB.isInDB(fileID)) { hashSet.add(fileID); } } return hashSet; - + } catch (Exception ex) { throw new TskCoreException("Failed to get file ids with mime type " + mimeType, ex); } @@ -659,29 +654,24 @@ public class GroupManager { "# {1} - atribute value", "ReGroupTask.progressUpdate=regrouping files by {0} : {1}"}) private class ReGroupTask> extends LoggedTask { - + private final DataSource dataSource; private final DrawableAttribute groupBy; private final GroupSortBy sortBy; private final SortOrder sortOrder; - + private final ProgressHandle groupProgress; - + ReGroupTask(DataSource dataSource, DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder) { super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), true); this.dataSource = dataSource; this.groupBy = groupBy; this.sortBy = sortBy; this.sortOrder = sortOrder; - + groupProgress = ProgressHandle.createHandle(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), this); } - - @Override - public boolean isCancelled() { - return super.isCancelled(); - } - + @Override protected Void call() throws Exception { try { @@ -689,11 +679,11 @@ public class GroupManager { return null; } groupProgress.start(); - + synchronized (GroupManager.this) { analyzedGroups.clear(); unSeenGroups.clear(); - + Multimap valsByDataSource = null; // Get the list of group keys valsByDataSource = findValuesForAttribute(); @@ -710,8 +700,8 @@ public class GroupManager { groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); } - isRegrouping = false; - + regrouping = false; + Optional viewedGroup = Optional.ofNullable(controller.getViewState()) .flatMap(GroupViewState::getGroup); @@ -745,13 +735,13 @@ public class GroupManager { } return null; } - + @Override protected void done() { super.done(); try { get(); - } catch (CancellationException cancelEx) { + } catch (CancellationException cancelEx) { //NOPMD //cancellation is normal } catch (InterruptedException | ExecutionException ex) { logger.log(Level.SEVERE, "Error while regrouping.", ex); @@ -769,7 +759,7 @@ public class GroupManager { */ public Multimap findValuesForAttribute() { synchronized (GroupManager.this) { - + Multimap results = HashMultimap.create(); try { switch (groupBy.attrName) { @@ -782,17 +772,17 @@ public class GroupManager { .filter(CategoryManager::isNotCategoryTagName) .collect(Collectors.toList())); break; - + case ANALYZED: results.putAll(null, Arrays.asList(false, true)); break; case HASHSET: - - results.putAll(null, new TreeSet<>(db.getHashSetNames())); - + + results.putAll(null, new TreeSet<>(drawableDB.getHashSetNames())); + break; case MIME_TYPE: - + HashSet types = new HashSet<>(); // Use the group_concat function to get a list of files for each mime type. @@ -812,18 +802,18 @@ public class GroupManager { Pattern.compile(",").splitAsStream(objIds) .map(Long::valueOf) - .filter(db::isInDB) + .filter(drawableDB::isInDB) .findAny().ifPresent(obj_id -> types.add(mimeType)); } } catch (SQLException | TskCoreException ex) { Exceptions.printStackTrace(ex); } results.putAll(null, types); - + break; default: //otherwise do straight db query - results.putAll(db.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); + results.putAll(drawableDB.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS @@ -832,7 +822,7 @@ public class GroupManager { } } } - + private static Comparator makeGroupComparator(final SortOrder sortOrder, GroupSortBy comparator) { switch (sortOrder) { case ASCENDING: diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/StatusBar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/StatusBar.java index 143ef142fd..2355d6d2a0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/StatusBar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/StatusBar.java @@ -79,7 +79,7 @@ public class StatusBar extends AnchorPane { }); Platform.runLater(() -> staleLabel.setTooltip(new Tooltip(Bundle.StatuBar_toolTip()))); - staleLabel.visibleProperty().bind(controller.stale()); + staleLabel.visibleProperty().bind(controller.staleProperty()); } public StatusBar(ImageGalleryController controller) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java index 264c7c9016..a9c4da2907 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java @@ -26,7 +26,6 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TableColumn; -import javafx.scene.control.TableColumn.CellDataFeatures; import javafx.scene.control.TableView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Priority; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 72b7a73473..47acdeca53 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -24,19 +24,14 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; -import javafx.beans.property.DoubleProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -61,7 +56,6 @@ import javafx.scene.text.Text; import javafx.stage.Modality; import javafx.util.StringConverter; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; -import org.controlsfx.control.Notifications; import org.controlsfx.control.PopOver; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; @@ -162,16 +156,16 @@ public class Toolbar extends ToolBar { tagImageViewLabel.setText(Bundle.Toolbar_tagImageViewLabel()); categoryImageViewLabel.setText(Bundle.Toolbar_categoryImageViewLabel()); thumbnailSizeLabel.setText(Bundle.Toolbar_thumbnailSizeLabel()); - sizeSlider.valueProperty().bindBidirectional(controller.thumbnailSize()); - controller.viewState().addListener((observable, oldViewState, newViewState) + sizeSlider.valueProperty().bindBidirectional(controller.thumbnailSizeProperty()); + controller.viewStateProperty().addListener((observable, oldViewState, newViewState) -> Platform.runLater(() -> syncGroupControlsEnabledState(newViewState)) ); - syncGroupControlsEnabledState(controller.viewState().get()); + syncGroupControlsEnabledState(controller.viewStateProperty().get()); initDataSourceComboBox(); groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs())); groupByBox.getSelectionModel().select(DrawableAttribute.PATH); - groupByBox.disableProperty().bind(controller.regroupDisabled()); + groupByBox.disableProperty().bind(controller.regroupDisabledProperty()); groupByBox.setCellFactory(listView -> new AttributeListCell()); groupByBox.setButtonCell(new AttributeListCell()); groupByBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java index 4378cc224e..b7fa5a2d65 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java @@ -63,8 +63,8 @@ public class DrawableTile extends DrawableTileBase { setCache(true); setCacheHint(CacheHint.SPEED); nameLabel.prefWidthProperty().bind(imageView.fitWidthProperty()); - imageView.fitHeightProperty().bind(getController().thumbnailSize()); - imageView.fitWidthProperty().bind(getController().thumbnailSize()); + imageView.fitHeightProperty().bind(getController().thumbnailSizeProperty()); + imageView.fitWidthProperty().bind(getController().thumbnailSizeProperty()); selectionModel.lastSelectedProperty().addListener(new WeakChangeListener<>(lastSelectionListener)); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java index 8cbcd8840d..dd26ecd4c7 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableUIBase.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2015-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,6 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import static java.util.logging.Level.SEVERE; import javafx.application.Platform; import javafx.concurrent.Task; import javafx.fxml.FXML; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableView.java index a9779271a6..caca379014 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableView.java @@ -1,3 +1,21 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015-18 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; import com.google.common.eventbus.Subscribe; @@ -12,16 +30,14 @@ import javafx.scene.layout.BorderWidths; import javafx.scene.layout.CornerRadii; import javafx.scene.layout.Region; import javafx.scene.paint.Color; -import org.openide.util.Exceptions; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; -import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.datamodel.DhsImageCategory; +import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; -import org.sleuthkit.datamodel.TskCoreException; /** * Interface for classes that are views of a single DrawableFile. Implementation diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index ca672baf8f..afef0b556f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2013-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,10 +35,8 @@ import java.util.Map; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Optional; -import java.util.function.Function; import java.util.logging.Level; import java.util.stream.IntStream; -import java.util.stream.Stream; import javafx.animation.Interpolator; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; @@ -96,14 +94,12 @@ import javafx.scene.layout.CornerRadii; import javafx.scene.layout.HBox; import javafx.scene.paint.Color; import javafx.util.Duration; -import javax.swing.Action; import javax.swing.SwingUtilities; import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.GridCell; import org.controlsfx.control.GridView; import org.controlsfx.control.SegmentedButton; import org.controlsfx.control.action.ActionUtils; -import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.actions.Presenter; @@ -429,7 +425,7 @@ public class GroupPane extends BorderPane { flashAnimation.setAutoReverse(true); //configure gridView cell properties - DoubleBinding cellSize = controller.thumbnailSize().add(75); + DoubleBinding cellSize = controller.thumbnailSizeProperty().add(75); gridView.cellHeightProperty().bind(cellSize); gridView.cellWidthProperty().bind(cellSize); gridView.setCellFactory((GridView param) -> new DrawableCell()); @@ -503,7 +499,7 @@ public class GroupPane extends BorderPane { slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get())); tileToggle.setOnAction(onAction -> activateTileViewer()); - controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState)); + controller.viewStateProperty().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState)); addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler); gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler()); @@ -544,7 +540,7 @@ public class GroupPane extends BorderPane { } }); - setViewState(controller.viewState().get()); + setViewState(controller.viewStateProperty().get()); } //TODO: make sure we are testing complete visability not just bounds intersection @@ -634,10 +630,8 @@ public class GroupPane extends BorderPane { }); } else { - if (getGroup() != newViewState.getGroup().get()) { - if (nonNull(getGroup())) { - getGroup().getFileIDs().removeListener(filesSyncListener); - } + if (nonNull(getGroup()) && getGroup() != newViewState.getGroup().get()) { + getGroup().getFileIDs().removeListener(filesSyncListener); } this.grouping.set(newViewState.getGroup().get()); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java index ff7c3f6429..645c67baba 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/MetaDataPane.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2013-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,6 @@ import java.util.Objects; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Optional; -import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; @@ -52,19 +51,17 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; import javafx.scene.text.Text; import javafx.util.Pair; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; -import org.sleuthkit.autopsy.datamodel.DhsImageCategory; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.datamodel.TagName; -import org.sleuthkit.datamodel.TskCoreException; /** * Shows details of the selected file. diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java index 28bd44599d..d67e84ca17 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java @@ -91,7 +91,7 @@ abstract class NavPanel extends Tab { toolBar.getItems().add(sortChooser); //keep selection in sync with controller - controller.viewState().addListener(observable -> { + controller.viewStateProperty().addListener(observable -> { Platform.runLater(() -> { Optional.ofNullable(controller.getViewState()) .flatMap(GroupViewState::getGroup) From d8f46f88a8ce3be00214f4eb46dd61d9075f10b2 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 5 Sep 2018 15:12:04 +0200 Subject: [PATCH 67/84] make sure a grouping is applied the first time Image Gallery is opened. --- .../imagegallery/ImageGalleryController.java | 2 +- .../imagegallery/ImageGalleryModule.java | 2 +- .../ImageGalleryTopComponent.java | 77 ++++++++++--------- .../datamodel/grouping/GroupManager.java | 35 +++++---- 4 files changed, 63 insertions(+), 53 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 7c08a7ee42..f111f7b3c5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -185,12 +185,12 @@ public final class ImageGalleryController { this.autopsyCase = Objects.requireNonNull(newCase); this.sleuthKitCase = newCase.getSleuthkitCase(); - this.drawableDB = DrawableDB.getDrawableDB(this); setListeningEnabled(ImageGalleryModule.isEnabledforCase(newCase)); setStale(ImageGalleryModule.isDrawableDBStale(newCase)); groupManager = new GroupManager(this); + this.drawableDB = DrawableDB.getDrawableDB(this); categoryManager = new CategoryManager(this); tagsManager = new DrawableTagsManager(this); tagsManager.registerListener(groupManager); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java index 5b15a5eacf..f5064c035f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java @@ -46,7 +46,7 @@ import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; -/** static definitions and utilities for the ImageGallery module */ +/** static definitions, utilities, and listeners for the ImageGallery module */ @NbBundle.Messages({"ImageGalleryModule.moduleName=Image Gallery"}) public class ImageGalleryModule { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 8b88fd151f..bb144f6b5a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -47,6 +47,7 @@ import javafx.scene.paint.Color; import javafx.stage.Modality; import javax.swing.SwingUtilities; import org.apache.commons.collections4.CollectionUtils; +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; import org.openide.util.Lookup; @@ -166,10 +167,13 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl } catch (TskCoreException tskCoreException) { logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException); } + GroupManager groupManager = controller.getGroupManager(); if (dataSources.size() <= 1 - || controller.getGroupManager().getGroupBy() != DrawableAttribute.PATH) { + || groupManager.getGroupBy() != DrawableAttribute.PATH) { /* if there is only one datasource or the grouping is already set to * something other than path , don't both to ask for datasource */ + groupManager.regroup(null, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true); + showTopComponent(topComponent); return; } @@ -188,7 +192,6 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl Optional dataSourceName = datasourceDialog.showAndWait(); DataSource dataSource = dataSourceName.map(dataSourceNames::get).orElse(null); - GroupManager groupManager = controller.getGroupManager(); groupManager.regroup(dataSource, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true); showTopComponent(topComponent); }); @@ -261,6 +264,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl InvalidationListener checkGroupsListener = observable -> checkForGroups(); controller.getGroupManager().getAnalyzedGroups().addListener(checkGroupsListener); + controller.getGroupManager().getUnSeenGroups().addListener(checkGroupsListener); controller.regroupDisabledProperty().addListener(checkGroupsListener); checkForGroups(); } @@ -336,49 +340,50 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl + "or no groups are fully analyzed but ingest is not running."}) synchronized private void checkForGroups() { GroupManager groupManager = controller.getGroupManager(); - if (CollectionUtils.isNotEmpty(groupManager.getAnalyzedGroups())) { - Platform.runLater(this::clearNotification); - return; - } - - if (IngestManager.getInstance().isIngestRunning()) { - if (controller.isListeningEnabled()) { - replaceNotification(centralStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), - new ProgressIndicator())); - } else { - replaceNotification(fullUIStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); + synchronized (groupManager) { + if (isNotEmpty(groupManager.getAnalyzedGroups())) { + Platform.runLater(this::clearNotification); + return; } - return; - } - if (controller.getDBTasksQueueSizeProperty().get() > 0) { - replaceNotification(fullUIStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), - new ProgressIndicator())); - return; - } - try { - if (controller.getDatabase().countAllFiles() <= 0) { - // there are no files in db + + if (IngestManager.getInstance().isIngestRunning()) { if (controller.isListeningEnabled()) { - replaceNotification(fullUIStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5())); + replaceNotification(centralStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), + new ProgressIndicator())); } else { replaceNotification(fullUIStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4())); + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); } return; } - } catch (TskCoreException tskCoreException) { - logger.log(Level.SEVERE, "Error counting files in the database.", tskCoreException); - } + if (controller.getDBTasksQueueSizeProperty().get() > 0) { + replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), + new ProgressIndicator())); + return; + } + try { + if (controller.getDatabase().countAllFiles() <= 0) { + // there are no files in db + if (controller.isListeningEnabled()) { + replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5())); + } else { + replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4())); + } + return; + } + } catch (TskCoreException tskCoreException) { + logger.log(Level.SEVERE, "Error counting files in the database.", tskCoreException); + } - if (false == groupManager.isRegrouping()) { - replaceNotification(centralStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6())); + if (false == groupManager.isRegrouping()) { + replaceNotification(centralStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6())); + } } - } private void replaceNotification(StackPane stackPane, Node newNode) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index fd40a45325..883a93517e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -98,7 +98,6 @@ public class GroupManager { new BasicThreadFactory.Builder().namingPattern("GUI Task -%d").build())); //NON-NLS private final ImageGalleryController controller; - private final DrawableDB drawableDB; @GuardedBy("this") //NOPMD boolean regrouping; @@ -150,7 +149,6 @@ public class GroupManager { */ public GroupManager(ImageGalleryController controller) { this.controller = controller; - this.drawableDB = controller.getDatabase(); } /** @@ -189,7 +187,7 @@ public class GroupManager { */ synchronized public Set> getGroupKeysForFileID(Long fileID) { try { - DrawableFile file = drawableDB.getFileFromID(fileID); + DrawableFile file = getDrawableDB().getFileFromID(fileID); return getGroupKeysForFile(file); } catch (TskCoreException ex) { Logger.getLogger(GroupManager.class.getName()).log(Level.SEVERE, "failed to load file with id: " + fileID + " from database", ex); //NON-NLS @@ -243,7 +241,7 @@ public class GroupManager { public ListenableFuture setGroupSeen(DrawableGroup group, boolean seen) { return exec.submit(() -> { try { - drawableDB.setGroupSeen(group.getGroupKey(), seen); + getDrawableDB().setGroupSeen(group.getGroupKey(), seen); group.setSeen(seen); updateUnSeenGroups(group, seen); } catch (TskCoreException ex) { @@ -327,7 +325,7 @@ public class GroupManager { // return getFileIDsWithHashSetName((String) groupKey.getValue()); default: //straight db query - return drawableDB.getFileIDsInGroup(groupKey); + return getDrawableDB().getFileIDsInGroup(groupKey); } } @@ -345,18 +343,18 @@ public class GroupManager { tagsManager.getContentTagsByTagName(catTagName).stream() .filter(ct -> ct.getContent() instanceof AbstractFile) .map(ct -> ct.getContent().getId()) - .filter(drawableDB::isInDB) + .filter(getDrawableDB()::isInDB) .forEach(fileIDs::add); } } - fileIDsToReturn = drawableDB.findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(fileIDs, ',') + ")"); //NON-NLS + fileIDsToReturn = getDrawableDB().findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(fileIDs, ',') + ")"); //NON-NLS } else { List contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category)); fileIDsToReturn = contentTags.stream() .filter(ct -> ct.getContent() instanceof AbstractFile) - .filter(ct -> drawableDB.isInDB(ct.getContent().getId())) + .filter(ct -> getDrawableDB().isInDB(ct.getContent().getId())) .map(ct -> ct.getContent().getId()) .collect(Collectors.toSet()); } @@ -374,7 +372,7 @@ public class GroupManager { List contentTags = controller.getTagsManager().getContentTagsByTagName(tagName); for (ContentTag ct : contentTags) { - if (ct.getContent() instanceof AbstractFile && drawableDB.isInDB(ct.getContent().getId())) { + if (ct.getContent() instanceof AbstractFile && getDrawableDB().isInDB(ct.getContent().getId())) { files.add(ct.getContent().getId()); } } @@ -585,12 +583,12 @@ public class GroupManager { * of that group. just show them no matter what. */ if (groupKey.getAttribute() != DrawableAttribute.PATH - || drawableDB.isGroupAnalyzed(groupKey)) { + || getDrawableDB().isGroupAnalyzed(groupKey)) { try { Set fileIDs = getFileIDsInGroup(groupKey); if (Objects.nonNull(fileIDs)) { DrawableGroup group; - final boolean groupSeen = drawableDB.isGroupSeen(groupKey); + final boolean groupSeen = getDrawableDB().isGroupSeen(groupKey); if (groupMap.containsKey(groupKey)) { group = groupMap.get(groupKey); group.setFiles(ObjectUtils.defaultIfNull(fileIDs, Collections.emptySet())); @@ -630,7 +628,7 @@ public class GroupManager { ResultSet resultSet = executeQuery.getResultSet();) { while (resultSet.next()) { final long fileID = resultSet.getLong("obj_id"); //NON-NLS - if (drawableDB.isInDB(fileID)) { + if (getDrawableDB().isInDB(fileID)) { hashSet.add(fileID); } } @@ -778,7 +776,7 @@ public class GroupManager { break; case HASHSET: - results.putAll(null, new TreeSet<>(drawableDB.getHashSetNames())); + results.putAll(null, new TreeSet<>(getDrawableDB().getHashSetNames())); break; case MIME_TYPE: @@ -802,7 +800,7 @@ public class GroupManager { Pattern.compile(",").splitAsStream(objIds) .map(Long::valueOf) - .filter(drawableDB::isInDB) + .filter(getDrawableDB()::isInDB) .findAny().ifPresent(obj_id -> types.add(mimeType)); } } catch (SQLException | TskCoreException ex) { @@ -813,7 +811,7 @@ public class GroupManager { break; default: //otherwise do straight db query - results.putAll(drawableDB.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); + results.putAll(getDrawableDB().findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS @@ -834,4 +832,11 @@ public class GroupManager { return new GroupSortBy.AllEqualComparator<>(); } } + + /** + * @return the drawableDB + */ + private DrawableDB getDrawableDB() { + return controller.getDatabase(); + } } From c97e64172b9c7b8163f7973078808693333bcee1 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 5 Sep 2018 15:38:58 +0200 Subject: [PATCH 68/84] more codacy fixes --- .../imagegallery/ImageGalleryController.java | 8 ------- .../imagegallery/ImageGalleryModule.java | 3 +++ .../ImageGalleryOptionsPanel.java | 1 - .../ImageGalleryTopComponent.java | 12 +++++++---- .../datamodel/grouping/GroupManager.java | 21 +++++++------------ .../autopsy/imagegallery/gui/Toolbar.java | 4 ++-- .../gui/drawableviews/GroupPane.java | 4 ++-- .../gui/navpanel/GroupTreeItem.java | 11 ++++------ 8 files changed, 26 insertions(+), 38 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index f111f7b3c5..354ab62914 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -398,14 +398,6 @@ public final class ImageGalleryController { return groupManager.regroupProgress(); } - /** - * invoked by {@link OnStart} to make sure that the ImageGallery listeners - * get setup as early as possible, and do other setup stuff. - */ - void onStart() { - - } - public HashSetManager getHashSetManager() { return hashSetManager; } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java index f5064c035f..f0f733bd36 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java @@ -264,6 +264,9 @@ public class ImageGalleryModule { controller.getTagsManager().fireTagDeletedEvent(tagDeletedEvent); } break; + default: + //we don't need to do anything for other events. + break; } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java index 5d14ad4b97..80c35be23b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryOptionsPanel.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.imagegallery; -import java.awt.event.ActionEvent; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index bb144f6b5a..4a36fe9b62 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -197,11 +197,11 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl }); } - public static void showTopComponent(TopComponent tc) { + public static void showTopComponent(TopComponent topComponent) { SwingUtilities.invokeLater(() -> { - tc.open(); - tc.toFront(); - tc.requestActive(); + topComponent.open(); + topComponent.toFront(); + topComponent.requestActive(); }); } @@ -405,6 +405,10 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl centralStack.getChildren().remove(infoOverlay); } + /** + * Region with partialy opacity used to block out parts of the UI behind a + * pseudo dialog. + */ static final private class TranslucentRegion extends Region { TranslucentRegion() { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 883a93517e..d7ad049fdd 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -77,6 +77,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.SleuthkitCase; @@ -367,20 +368,12 @@ public class GroupManager { } synchronized public Set getFileIDsWithTag(TagName tagName) throws TskCoreException { - Set files = new HashSet<>(); - try { - List contentTags = controller.getTagsManager().getContentTagsByTagName(tagName); - - for (ContentTag ct : contentTags) { - if (ct.getContent() instanceof AbstractFile && getDrawableDB().isInDB(ct.getContent().getId())) { - files.add(ct.getContent().getId()); - } - } - return files; - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "TSK error getting files with Tag:" + tagName.getDisplayName(), ex); //NON-NLS - throw ex; - } + return controller.getTagsManager().getContentTagsByTagName(tagName).stream() + .map(ContentTag::getContent) + .filter(AbstractFile.class::isInstance) + .map(Content::getId) + .filter(getDrawableDB()::isInDB) + .collect(Collectors.toSet()); } public synchronized GroupSortBy getSortBy() { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 47acdeca53..546703da59 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -275,7 +275,7 @@ public class Toolbar extends ToolBar { } @Override - public void onFailure(Throwable t) { + public void onFailure(Throwable throwable) { /* * The problem appears to be a timing issue where a case is * closed before this initialization is completed, which It @@ -285,7 +285,7 @@ public class Toolbar extends ToolBar { * TODO (JIRA-3010): SEVERE error logged by image Gallery UI */ if (Case.isCaseOpen()) { - logger.log(Level.WARNING, "Could not create Follow Up tag menu item", t); //NON-NLS + logger.log(Level.WARNING, "Could not create Follow Up tag menu item", throwable); //NON-NLS } else { // don't add stack trace to log because it makes looking for real errors harder logger.log(Level.INFO, "Unable to get tag name. Case is closed."); //NON-NLS diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index afef0b556f..d730f8346a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -452,8 +452,8 @@ public class GroupPane extends BorderPane { } @Override - public void onFailure(Throwable t) { - logger.log(Level.SEVERE, "Error getting tag names.", t); + public void onFailure(Throwable throwable) { + logger.log(Level.SEVERE, "Error getting tag names.", throwable); } }, Platform::runLater); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeItem.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeItem.java index e5b67222dd..1dbeeac945 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeItem.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeItem.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2013-18 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,6 @@ */ package org.sleuthkit.autopsy.imagegallery.gui.navpanel; -import java.util.Collections; -import static java.util.Collections.singleton; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -36,8 +34,8 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup; /** * A node in the nav/hash tree. Manages inserts and removals. Has parents and * children. Does not have graphical properties these are configured in - * {@link GroupTreeCell}. Each GroupTreeItem has a TreeNode which has a path - * segment and may or may not have a group + * GroupTreeCell. Each GroupTreeItem has a TreeNode which has a path segment and + * may or may not have a group */ class GroupTreeItem extends TreeItem { @@ -132,7 +130,6 @@ class GroupTreeItem extends TreeItem { } synchronized GroupTreeItem getTreeItemForPath(List path) { - if (path.isEmpty()) { // end of recursion return this; @@ -155,7 +152,7 @@ class GroupTreeItem extends TreeItem { if (parent != null) { parent.childMap.remove(getValue().getPath()); - Platform.runLater(() -> parent.getChildren().removeAll(singleton(GroupTreeItem.this))); + Platform.runLater(() -> parent.getChildren().remove(this)); if (parent.childMap.isEmpty()) { parent.removeFromParent(); From af9642d171b297ac5624446cecedaa26379f9e71 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 6 Sep 2018 13:56:11 +0200 Subject: [PATCH 69/84] cleanup --- .../autopsy/imagegallery/ImageGalleryPreferences.java | 3 +-- .../sleuthkit/autopsy/imagegallery/actions/OpenAction.java | 4 ++-- .../sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java | 1 - .../autopsy/imagegallery/datamodel/grouping/GroupManager.java | 4 ++-- .../autopsy/imagegallery/gui/navpanel/GroupTreeItem.java | 4 +--- .../sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java | 2 +- 6 files changed, 7 insertions(+), 11 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryPreferences.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryPreferences.java index 93329c3e62..37be779969 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryPreferences.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryPreferences.java @@ -46,8 +46,7 @@ public class ImageGalleryPreferences { * @return true if new cases should have image analyzer enabled. */ public static boolean isEnabledByDefault() { - final boolean aBoolean = preferences.getBoolean(ENABLED_BY_DEFAULT, true); - return aBoolean; + return preferences.getBoolean(ENABLED_BY_DEFAULT, true); } public static void setEnabledByDefault(boolean b) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java index c3948e1b02..42ea6ef139 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java @@ -125,7 +125,6 @@ public final class OpenAction extends CallableSystemAction { } @Override - @SuppressWarnings("fallthrough") @NbBundle.Messages({"OpenAction.dialogTitle=Image Gallery"}) public void performAction() { @@ -169,8 +168,9 @@ public final class OpenAction extends CallableSystemAction { } else { controller.rebuildDB(); } + ImageGalleryTopComponent.openTopComponent(); + break; - //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 97e42dfa76..d64977e0d4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -932,7 +932,6 @@ public final class DrawableDB { dbReadLock(); try { Set fileIDsInGroup = getFileIDsInGroup(gk); - try { // In testing, this method appears to be a lot faster than doing one large select statement for (Long fileID : fileIDsInGroup) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index d7ad049fdd..f8b5c9e808 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -675,9 +675,9 @@ public class GroupManager { analyzedGroups.clear(); unSeenGroups.clear(); - Multimap valsByDataSource = null; // Get the list of group keys - valsByDataSource = findValuesForAttribute(); + Multimap valsByDataSource = findValuesForAttribute(); + groupProgress.switchToDeterminate(valsByDataSource.entries().size()); int p = 0; // For each key value, partially create the group and add it to the list. diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeItem.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeItem.java index 1dbeeac945..234e834149 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeItem.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTreeItem.java @@ -169,8 +169,6 @@ class GroupTreeItem extends TreeItem { synchronized void resortChildren(Comparator newComp) { this.comp = newComp; getChildren().sort(Comparator.comparing(treeItem -> treeItem.getValue().getGroup(), Comparator.nullsLast(comp))); - for (GroupTreeItem ti : childMap.values()) { - ti.resortChildren(comp); - } + childMap.values().forEach(treeItem -> treeItem.resortChildren(comp)); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java index d67e84ca17..f8544056e5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java @@ -115,7 +115,7 @@ abstract class NavPanel extends Tab { @Subscribe public void handleCategoryChange(CategoryManager.CategoryChangeEvent event) { - sortGroups(); + Platform.runLater(this::sortGroups); } /** From c96304106e29b5a8d56ba6befd50197af60ca14d Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 6 Sep 2018 13:57:00 +0200 Subject: [PATCH 70/84] remove synchronization on ImageGalleryController by passing it as argument to initJavaFXUIi --- .../ImageGalleryTopComponent.java | 154 +++++++++--------- 1 file changed, 80 insertions(+), 74 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 4a36fe9b62..8e39ab6244 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -27,6 +27,7 @@ import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.InvalidationListener; +import javafx.beans.Observable; import javafx.embed.swing.JFXPanel; import javafx.geometry.Insets; import javafx.scene.Node; @@ -46,7 +47,6 @@ import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.stage.Modality; import javax.swing.SwingUtilities; -import org.apache.commons.collections4.CollectionUtils; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; @@ -168,14 +168,17 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException); } GroupManager groupManager = controller.getGroupManager(); - if (dataSources.size() <= 1 - || groupManager.getGroupBy() != DrawableAttribute.PATH) { - /* if there is only one datasource or the grouping is already set to - * something other than path , don't both to ask for datasource */ - groupManager.regroup(null, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true); + synchronized (groupManager) { + if (dataSources.size() <= 1 + || groupManager.getGroupBy() != DrawableAttribute.PATH) { + /* if there is only one datasource or the grouping is already + * set to something other than path , don't both to ask for + * datasource */ + groupManager.regroup(null, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true); - showTopComponent(topComponent); - return; + showTopComponent(topComponent); + return; + } } Map dataSourceNames = new HashMap<>(); @@ -192,17 +195,18 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl Optional dataSourceName = datasourceDialog.showAndWait(); DataSource dataSource = dataSourceName.map(dataSourceNames::get).orElse(null); - groupManager.regroup(dataSource, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true); - showTopComponent(topComponent); + synchronized (groupManager) { + groupManager.regroup(dataSource, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true); + } + SwingUtilities.invokeLater(() -> showTopComponent(topComponent)); }); } + @ThreadConfined(type = ThreadConfined.ThreadType.AWT) public static void showTopComponent(TopComponent topComponent) { - SwingUtilities.invokeLater(() -> { - topComponent.open(); - topComponent.toFront(); - topComponent.requestActive(); - }); + topComponent.open(); + topComponent.toFront(); + topComponent.requestActive(); } public static void closeTopComponent() { @@ -229,44 +233,41 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl this.controller.shutDown(); } this.controller = controller; - Platform.runLater(() -> this.initJavaFXUI()); - } - - synchronized private void initJavaFXUI() { - //initialize jfx ui - fullUIStack = new StackPane(); //this is passed into controller - myScene = new Scene(fullUIStack); - jfxPanel.setScene(myScene); - - groupPane = new GroupPane(controller); - centralStack = new StackPane(groupPane); //this is passed into controller - fullUIStack.getChildren().add(borderPane); - splitPane = new SplitPane(); - borderPane.setCenter(splitPane); - Toolbar toolbar = new Toolbar(controller); - borderPane.setTop(toolbar); - borderPane.setBottom(new StatusBar(controller)); - - metaDataTable = new MetaDataPane(controller); - groupTree = new GroupTree(controller); - hashHitList = new HashHitGroupList(controller); - - TabPane tabPane = new TabPane(groupTree, hashHitList); - tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE); - tabPane.setMinWidth(TabPane.USE_PREF_SIZE); - VBox.setVgrow(tabPane, Priority.ALWAYS); - leftPane = new VBox(tabPane, new SummaryTablePane(controller)); - SplitPane.setResizableWithParent(leftPane, Boolean.FALSE); - SplitPane.setResizableWithParent(groupPane, Boolean.TRUE); - SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE); - splitPane.getItems().addAll(leftPane, centralStack, metaDataTable); - splitPane.setDividerPositions(0.1, 1.0); - - InvalidationListener checkGroupsListener = observable -> checkForGroups(); - controller.getGroupManager().getAnalyzedGroups().addListener(checkGroupsListener); - controller.getGroupManager().getUnSeenGroups().addListener(checkGroupsListener); - controller.regroupDisabledProperty().addListener(checkGroupsListener); - checkForGroups(); + Platform.runLater(new Runnable() { + @Override + public void run() { + //initialize jfx ui + fullUIStack = new StackPane(); //this is passed into controller + myScene = new Scene(fullUIStack); + jfxPanel.setScene(myScene); + groupPane = new GroupPane(controller); + centralStack = new StackPane(groupPane); //this is passed into controller + fullUIStack.getChildren().add(borderPane); + splitPane = new SplitPane(); + borderPane.setCenter(splitPane); + Toolbar toolbar = new Toolbar(controller); + borderPane.setTop(toolbar); + borderPane.setBottom(new StatusBar(controller)); + metaDataTable = new MetaDataPane(controller); + groupTree = new GroupTree(controller); + hashHitList = new HashHitGroupList(controller); + TabPane tabPane = new TabPane(groupTree, hashHitList); + tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE); + tabPane.setMinWidth(TabPane.USE_PREF_SIZE); + VBox.setVgrow(tabPane, Priority.ALWAYS); + leftPane = new VBox(tabPane, new SummaryTablePane(controller)); + SplitPane.setResizableWithParent(leftPane, Boolean.FALSE); + SplitPane.setResizableWithParent(groupPane, Boolean.TRUE); + SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE); + splitPane.getItems().addAll(leftPane, centralStack, metaDataTable); + splitPane.setDividerPositions(0.1, 1.0); + InvalidationListener checkGroupsListener = (Observable observable) -> checkForGroups(); + controller.getGroupManager().getAnalyzedGroups().addListener(checkGroupsListener); + controller.getGroupManager().getUnSeenGroups().addListener(checkGroupsListener); + controller.regroupDisabledProperty().addListener(checkGroupsListener); + checkForGroups(); + } + }); } /** @@ -348,30 +349,35 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl if (IngestManager.getInstance().isIngestRunning()) { if (controller.isListeningEnabled()) { - replaceNotification(centralStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), - new ProgressIndicator())); + Platform.runLater(() + -> replaceNotification(centralStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), + new ProgressIndicator()))); } else { - replaceNotification(fullUIStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); + Platform.runLater(() + -> replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1()))); } return; } if (controller.getDBTasksQueueSizeProperty().get() > 0) { - replaceNotification(fullUIStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), - new ProgressIndicator())); + Platform.runLater(() + -> replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), + new ProgressIndicator()))); return; } try { if (controller.getDatabase().countAllFiles() <= 0) { // there are no files in db if (controller.isListeningEnabled()) { - replaceNotification(fullUIStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5())); + Platform.runLater(() + -> replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5()))); } else { - replaceNotification(fullUIStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4())); + Platform.runLater(() + -> replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4()))); } return; } @@ -380,21 +386,21 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl } if (false == groupManager.isRegrouping()) { - replaceNotification(centralStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6())); + Platform.runLater(() + -> replaceNotification(centralStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6()))); } } } + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private void replaceNotification(StackPane stackPane, Node newNode) { - Platform.runLater(() -> { - clearNotification(); + clearNotification(); + infoOverlay = new StackPane(infoOverLayBackground, newNode); + if (stackPane != null) { + stackPane.getChildren().add(infoOverlay); + } - infoOverlay = new StackPane(infoOverLayBackground, newNode); - if (stackPane != null) { - stackPane.getChildren().add(infoOverlay); - } - }); } @ThreadConfined(type = ThreadConfined.ThreadType.JFX) From 3cd00b0aa03ab0f53d8253286a18e66d74a73f11 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 6 Sep 2018 17:40:09 +0200 Subject: [PATCH 71/84] make tree select viewed group --- .../imagegallery/datamodel/grouping/GroupManager.java | 4 ++-- .../autopsy/imagegallery/gui/navpanel/GroupTree.java | 8 +++++++- .../autopsy/imagegallery/gui/navpanel/NavPanel.java | 9 ++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index f8b5c9e808..b726fb7ba0 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -703,12 +703,12 @@ public class GroupManager { DrawableAttribute attributeOfCurrentGroup = viewedKey.map(GroupKey::getAttribute) .orElse(null); - /* if no group or if groupbies are different or if data * source != null and does not equal group */ if (viewedGroup.isPresent() == false || (getDataSource() != null && notEqual(dataSourceOfCurrentGroup, getDataSource())) || getGroupBy() != attributeOfCurrentGroup) { + //the current group should not be visible so ... if (isNotEmpty(unSeenGroups)) {// show then next unseen group controller.advance(GroupViewState.tile(unSeenGroups.get(0))); @@ -718,7 +718,7 @@ public class GroupManager { } else { //there are no groups, clear the group area. controller.advance(GroupViewState.tile(null)); } - } //else, the current group is for the given datasource, so just keep it in view. + } } } finally { groupProgress.finish(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java index 33cd84916f..66736ab2f9 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java @@ -38,6 +38,7 @@ import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup; +import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; /** * Shows path based groups as a tree and others kinds of groups as a flat list ( @@ -82,7 +83,12 @@ final public class GroupTree extends NavPanel> { change.getAddedSubList().stream().forEach(this::insertGroup); change.getRemoved().stream().forEach(this::removeFromTree); } - Platform.runLater(this::sortGroups); + Platform.runLater(() -> { + GroupTree.this.sortGroups(); + Optional.ofNullable(getController().getViewState()) + .flatMap(GroupViewState::getGroup) + .ifPresent(this::setFocusedGroup); + }); }); getGroupManager().getAnalyzedGroups().forEach(this::insertGroup); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java index f8544056e5..aa3af58b78 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java @@ -92,11 +92,10 @@ abstract class NavPanel extends Tab { //keep selection in sync with controller controller.viewStateProperty().addListener(observable -> { - Platform.runLater(() -> { - Optional.ofNullable(controller.getViewState()) - .flatMap(GroupViewState::getGroup) - .ifPresent(this::setFocusedGroup); - }); + Platform.runLater(() + -> Optional.ofNullable(controller.getViewState()) + .flatMap(GroupViewState::getGroup) + .ifPresent(this::setFocusedGroup)); }); // notify controller about group selection in this view From 9d46669937b0bf609fcd00ff015dbf41c42868e8 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 12 Sep 2018 12:49:23 +0200 Subject: [PATCH 72/84] use a service instead of creating a new task each time. --- .../ImageGalleryTopComponent.java | 51 ++++++------ .../datamodel/grouping/GroupManager.java | 78 ++++++++++++------- 2 files changed, 78 insertions(+), 51 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 8e39ab6244..c7f174a1e4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -261,11 +261,16 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE); splitPane.getItems().addAll(leftPane, centralStack, metaDataTable); splitPane.setDividerPositions(0.1, 1.0); + InvalidationListener checkGroupsListener = (Observable observable) -> checkForGroups(); - controller.getGroupManager().getAnalyzedGroups().addListener(checkGroupsListener); - controller.getGroupManager().getUnSeenGroups().addListener(checkGroupsListener); + controller.getGroupManager().reGroupingState().addListener(checkGroupsListener); controller.regroupDisabledProperty().addListener(checkGroupsListener); - checkForGroups(); + + InvalidationListener checkGroupsListenerFX = (Observable observable) -> Platform.runLater(() -> checkForGroups()); + controller.getGroupManager().getAnalyzedGroups().addListener(checkGroupsListenerFX); + controller.getGroupManager().getUnSeenGroups().addListener(checkGroupsListenerFX); + + Platform.runLater(() -> checkForGroups()); } }); } @@ -328,6 +333,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl * GroupManager and remove blocking progress spinners if there are. If there * aren't, add a blocking progress spinner with appropriate message. */ + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) @NbBundle.Messages({ "ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. " + " No groups will be available until ingest is finished and listening is re-enabled.", @@ -339,45 +345,40 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl "ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:" + " the current Group By setting resulted in no groups, " + "or no groups are fully analyzed but ingest is not running."}) - synchronized private void checkForGroups() { + private void checkForGroups() { GroupManager groupManager = controller.getGroupManager(); synchronized (groupManager) { if (isNotEmpty(groupManager.getAnalyzedGroups())) { - Platform.runLater(this::clearNotification); + clearNotification(); return; } if (IngestManager.getInstance().isIngestRunning()) { if (controller.isListeningEnabled()) { - Platform.runLater(() - -> replaceNotification(centralStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), - new ProgressIndicator()))); + replaceNotification(centralStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(), + new ProgressIndicator())); } else { - Platform.runLater(() - -> replaceNotification(fullUIStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1()))); + replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1())); } return; } if (controller.getDBTasksQueueSizeProperty().get() > 0) { - Platform.runLater(() - -> replaceNotification(fullUIStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), - new ProgressIndicator()))); + replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), + new ProgressIndicator())); return; } try { if (controller.getDatabase().countAllFiles() <= 0) { // there are no files in db if (controller.isListeningEnabled()) { - Platform.runLater(() - -> replaceNotification(fullUIStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5()))); + replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5())); } else { - Platform.runLater(() - -> replaceNotification(fullUIStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4()))); + replaceNotification(fullUIStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4())); } return; } @@ -386,9 +387,8 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl } if (false == groupManager.isRegrouping()) { - Platform.runLater(() - -> replaceNotification(centralStack, - new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6()))); + replaceNotification(centralStack, + new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6())); } } } @@ -409,6 +409,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl fullUIStack.getChildren().remove(infoOverlay); //remove the ingest spinner centralStack.getChildren().remove(infoOverlay); + } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index b726fb7ba0..597ef51c40 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -52,6 +52,9 @@ import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.concurrent.Service; +import javafx.concurrent.Task; +import javafx.concurrent.Worker; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -100,9 +103,6 @@ public class GroupManager { private final ImageGalleryController controller; - @GuardedBy("this") //NOPMD - boolean regrouping; - /** list of all analyzed groups */ @GuardedBy("this") //NOPMD private final ObservableList analyzedGroups = FXCollections.observableArrayList(); @@ -119,9 +119,6 @@ public class GroupManager { @GuardedBy("this") //NOPMD private final Map, DrawableGroup> groupMap = new HashMap<>(); - @GuardedBy("this") //NOPMD - private ReGroupTask groupByTask; - /* * --- current grouping/sorting attributes --- */ @@ -131,7 +128,7 @@ public class GroupManager { private final ReadOnlyObjectWrapper sortOrderProp = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING); private final ReadOnlyObjectWrapper dataSourceProp = new ReadOnlyObjectWrapper<>(null);//null indicates all datasources - private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper(); + private final GroupingService regrouper; @SuppressWarnings("ReturnOfCollectionOrArrayField") public ObservableList getAnalyzedGroups() { @@ -150,6 +147,8 @@ public class GroupManager { */ public GroupManager(ImageGalleryController controller) { this.controller = controller; + this.regrouper = new GroupingService(); + regrouper.setExecutor(exec); } /** @@ -208,10 +207,8 @@ public class GroupManager { } synchronized public void reset() { - if (groupByTask != null) { - groupByTask.cancel(true); - regrouping = false; - } + regrouper.cancel(); + setSortBy(GroupSortBy.GROUP_BY_VALUE); setGroupBy(DrawableAttribute.PATH); setSortOrder(SortOrder.ASCENDING); @@ -226,8 +223,14 @@ public class GroupManager { groupMap.clear(); } - synchronized public boolean isRegrouping() { - return regrouping; + public boolean isRegrouping() { + Worker.State state = regrouper.getState(); + return Arrays.asList(Worker.State.READY, Worker.State.RUNNING, Worker.State.SCHEDULED) + .contains(state); + } + + public ReadOnlyObjectProperty reGroupingState() { + return regrouper.stateProperty(); } /** @@ -450,13 +453,8 @@ public class GroupManager { setGroupBy(groupBy); setSortBy(sortBy); setSortOrder(sortOrder); - if (groupByTask != null) { - groupByTask.cancel(true); - } - regrouping = true; - groupByTask = new ReGroupTask<>(dataSource, groupBy, sortBy, sortOrder); - Platform.runLater(() -> regroupProgress.bind(groupByTask.progressProperty())); - exec.submit(groupByTask); + + regrouper.restart(); } else { // resort the list of groups setSortBy(sortBy); @@ -467,7 +465,7 @@ public class GroupManager { } public ReadOnlyDoubleProperty regroupProgress() { - return regroupProgress.getReadOnlyProperty(); + return regrouper.progressProperty(); } @Subscribe @@ -644,7 +642,7 @@ public class GroupManager { "# {0} - groupBy attribute Name", "# {1} - atribute value", "ReGroupTask.progressUpdate=regrouping files by {0} : {1}"}) - private class ReGroupTask> extends LoggedTask { + class ReGroupTask> extends LoggedTask { private final DataSource dataSource; private final DrawableAttribute groupBy; @@ -691,7 +689,6 @@ public class GroupManager { groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); } - regrouping = false; Optional viewedGroup = Optional.ofNullable(controller.getViewState()) @@ -705,9 +702,7 @@ public class GroupManager { .orElse(null); /* if no group or if groupbies are different or if data * source != null and does not equal group */ - if (viewedGroup.isPresent() == false - || (getDataSource() != null && notEqual(dataSourceOfCurrentGroup, getDataSource())) - || getGroupBy() != attributeOfCurrentGroup) { + if (viewedGroup.isPresent() == false) { //the current group should not be visible so ... if (isNotEmpty(unSeenGroups)) {// show then next unseen group @@ -718,6 +713,27 @@ public class GroupManager { } else { //there are no groups, clear the group area. controller.advance(GroupViewState.tile(null)); } + } else if ((getDataSource() != null && notEqual(dataSourceOfCurrentGroup, getDataSource()))) { + + //the current group should not be visible so ... + if (isNotEmpty(unSeenGroups)) {// show then next unseen group + controller.advance(GroupViewState.tile(unSeenGroups.get(0))); + } else if (isNotEmpty(analyzedGroups)) { + //show the first analyzed group. + controller.advance(GroupViewState.tile(analyzedGroups.get(0))); + } else { //there are no groups, clear the group area. + controller.advance(GroupViewState.tile(null)); + } + } else if (getGroupBy() != attributeOfCurrentGroup) { + //the current group should not be visible so ... + if (isNotEmpty(unSeenGroups)) {// show then next unseen group + controller.advance(GroupViewState.tile(unSeenGroups.get(0))); + } else if (isNotEmpty(analyzedGroups)) { + //show the first analyzed group. + controller.advance(GroupViewState.tile(analyzedGroups.get(0))); + } else { //there are no groups, clear the group area. + controller.advance(GroupViewState.tile(null)); + } } } } finally { @@ -832,4 +848,14 @@ public class GroupManager { private DrawableDB getDrawableDB() { return controller.getDatabase(); } + + class GroupingService extends Service< Void> { + + @Override + protected Task createTask() { + synchronized (GroupManager.this) { + return new ReGroupTask<>(getDataSource(), getGroupBy(), getSortBy(), getSortOrder()); + } + } + } } From 22775f31c244fe644a273adbe6bfda2a3c169203 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 12 Sep 2018 13:39:45 +0200 Subject: [PATCH 73/84] reduce unecessary synchronization, listeners, and sorting. more reliable preservation of selection when the datasource filter changes. --- .../ImageGalleryTopComponent.java | 9 +- .../datamodel/grouping/GroupManager.java | 217 +++++++++--------- .../imagegallery/gui/navpanel/GroupTree.java | 5 +- .../imagegallery/gui/navpanel/NavPanel.java | 17 +- 4 files changed, 124 insertions(+), 124 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index c7f174a1e4..344bbdfdf2 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -262,13 +262,8 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl splitPane.getItems().addAll(leftPane, centralStack, metaDataTable); splitPane.setDividerPositions(0.1, 1.0); - InvalidationListener checkGroupsListener = (Observable observable) -> checkForGroups(); - controller.getGroupManager().reGroupingState().addListener(checkGroupsListener); - controller.regroupDisabledProperty().addListener(checkGroupsListener); - - InvalidationListener checkGroupsListenerFX = (Observable observable) -> Platform.runLater(() -> checkForGroups()); - controller.getGroupManager().getAnalyzedGroups().addListener(checkGroupsListenerFX); - controller.getGroupManager().getUnSeenGroups().addListener(checkGroupsListenerFX); + controller.regroupDisabledProperty().addListener((Observable observable) -> checkForGroups()); + controller.getGroupManager().getAnalyzedGroups().addListener((Observable observable) -> Platform.runLater(() -> checkForGroups())); Platform.runLater(() -> checkForGroups()); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 597ef51c40..0a23460778 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -207,7 +207,7 @@ public class GroupManager { } synchronized public void reset() { - regrouper.cancel(); + Platform.runLater(regrouper::cancel); setSortBy(GroupSortBy.GROUP_BY_VALUE); setGroupBy(DrawableAttribute.PATH); @@ -669,71 +669,69 @@ public class GroupManager { } groupProgress.start(); - synchronized (GroupManager.this) { - analyzedGroups.clear(); - unSeenGroups.clear(); + analyzedGroups.clear(); + unSeenGroups.clear(); - // Get the list of group keys - Multimap valsByDataSource = findValuesForAttribute(); + // Get the list of group keys + Multimap valsByDataSource = findValuesForAttribute(); - groupProgress.switchToDeterminate(valsByDataSource.entries().size()); - int p = 0; - // For each key value, partially create the group and add it to the list. - for (final Map.Entry val : valsByDataSource.entries()) { - if (isCancelled()) { - return null; - } - p++; - updateMessage(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val.getValue())); - updateProgress(p, valsByDataSource.size()); - groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); - popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); + groupProgress.switchToDeterminate(valsByDataSource.entries().size()); + int p = 0; + // For each key value, partially create the group and add it to the list. + for (final Map.Entry val : valsByDataSource.entries()) { + if (isCancelled()) { + return null; } + p++; + updateMessage(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val.getValue())); + updateProgress(p, valsByDataSource.size()); + groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); + popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); + } - Optional viewedGroup - = Optional.ofNullable(controller.getViewState()) - .flatMap(GroupViewState::getGroup); - Optional> viewedKey = viewedGroup.map(DrawableGroup::getGroupKey); - DataSource dataSourceOfCurrentGroup - = viewedKey.flatMap(GroupKey::getDataSource) - .orElse(null); - DrawableAttribute attributeOfCurrentGroup - = viewedKey.map(GroupKey::getAttribute) - .orElse(null); + Optional viewedGroup + = Optional.ofNullable(controller.getViewState()) + .flatMap(GroupViewState::getGroup); + Optional> viewedKey = viewedGroup.map(DrawableGroup::getGroupKey); + DataSource dataSourceOfCurrentGroup + = viewedKey.flatMap(GroupKey::getDataSource) + .orElse(null); + DrawableAttribute attributeOfCurrentGroup + = viewedKey.map(GroupKey::getAttribute) + .orElse(null); /* if no group or if groupbies are different or if data * source != null and does not equal group */ - if (viewedGroup.isPresent() == false) { + if (viewedGroup.isPresent() == false) { - //the current group should not be visible so ... - if (isNotEmpty(unSeenGroups)) {// show then next unseen group - controller.advance(GroupViewState.tile(unSeenGroups.get(0))); - } else if (isNotEmpty(analyzedGroups)) { - //show the first analyzed group. - controller.advance(GroupViewState.tile(analyzedGroups.get(0))); - } else { //there are no groups, clear the group area. - controller.advance(GroupViewState.tile(null)); - } - } else if ((getDataSource() != null && notEqual(dataSourceOfCurrentGroup, getDataSource()))) { + //the current group should not be visible so ... + if (isNotEmpty(unSeenGroups)) {// show then next unseen group + controller.advance(GroupViewState.tile(unSeenGroups.get(0))); + } else if (isNotEmpty(analyzedGroups)) { + //show the first analyzed group. + controller.advance(GroupViewState.tile(analyzedGroups.get(0))); + } else { //there are no groups, clear the group area. + controller.advance(GroupViewState.tile(null)); + } + } else if ((getDataSource() != null && notEqual(dataSourceOfCurrentGroup, getDataSource()))) { - //the current group should not be visible so ... - if (isNotEmpty(unSeenGroups)) {// show then next unseen group - controller.advance(GroupViewState.tile(unSeenGroups.get(0))); - } else if (isNotEmpty(analyzedGroups)) { - //show the first analyzed group. - controller.advance(GroupViewState.tile(analyzedGroups.get(0))); - } else { //there are no groups, clear the group area. - controller.advance(GroupViewState.tile(null)); - } - } else if (getGroupBy() != attributeOfCurrentGroup) { - //the current group should not be visible so ... - if (isNotEmpty(unSeenGroups)) {// show then next unseen group - controller.advance(GroupViewState.tile(unSeenGroups.get(0))); - } else if (isNotEmpty(analyzedGroups)) { - //show the first analyzed group. - controller.advance(GroupViewState.tile(analyzedGroups.get(0))); - } else { //there are no groups, clear the group area. - controller.advance(GroupViewState.tile(null)); - } + //the current group should not be visible so ... + if (isNotEmpty(unSeenGroups)) {// show then next unseen group + controller.advance(GroupViewState.tile(unSeenGroups.get(0))); + } else if (isNotEmpty(analyzedGroups)) { + //show the first analyzed group. + controller.advance(GroupViewState.tile(analyzedGroups.get(0))); + } else { //there are no groups, clear the group area. + controller.advance(GroupViewState.tile(null)); + } + } else if (getGroupBy() != attributeOfCurrentGroup) { + //the current group should not be visible so ... + if (isNotEmpty(unSeenGroups)) {// show then next unseen group + controller.advance(GroupViewState.tile(unSeenGroups.get(0))); + } else if (isNotEmpty(analyzedGroups)) { + //show the first analyzed group. + controller.advance(GroupViewState.tile(analyzedGroups.get(0))); + } else { //there are no groups, clear the group area. + controller.advance(GroupViewState.tile(null)); } } } finally { @@ -765,68 +763,67 @@ public class GroupManager { * @return */ public Multimap findValuesForAttribute() { - synchronized (GroupManager.this) { - Multimap results = HashMultimap.create(); - try { - switch (groupBy.attrName) { - //these cases get special treatment - case CATEGORY: - results.putAll(null, Arrays.asList(DhsImageCategory.values())); - break; - case TAGS: - results.putAll(null, controller.getTagsManager().getTagNamesInUse().stream() - .filter(CategoryManager::isNotCategoryTagName) - .collect(Collectors.toList())); - break; + Multimap results = HashMultimap.create(); + try { + switch (groupBy.attrName) { + //these cases get special treatment + case CATEGORY: + results.putAll(null, Arrays.asList(DhsImageCategory.values())); + break; + case TAGS: + results.putAll(null, controller.getTagsManager().getTagNamesInUse().stream() + .filter(CategoryManager::isNotCategoryTagName) + .collect(Collectors.toList())); + break; - case ANALYZED: - results.putAll(null, Arrays.asList(false, true)); - break; - case HASHSET: + case ANALYZED: + results.putAll(null, Arrays.asList(false, true)); + break; + case HASHSET: - results.putAll(null, new TreeSet<>(getDrawableDB().getHashSetNames())); + results.putAll(null, new TreeSet<>(getDrawableDB().getHashSetNames())); - break; - case MIME_TYPE: + break; + case MIME_TYPE: - HashSet types = new HashSet<>(); + HashSet types = new HashSet<>(); - // Use the group_concat function to get a list of files for each mime type. - // This has different syntax on Postgres vs SQLite - String groupConcatClause; - if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) { - groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; - } else { - groupConcatClause = " group_concat(obj_id) as object_ids"; + // Use the group_concat function to get a list of files for each mime type. + // This has different syntax on Postgres vs SQLite + String groupConcatClause; + if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) { + groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; + } else { + groupConcatClause = " group_concat(obj_id) as object_ids"; + } + String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type "; + try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS + ResultSet resultSet = executeQuery.getResultSet();) { + while (resultSet.next()) { + final String mimeType = resultSet.getString("mime_type"); //NON-NLS + String objIds = resultSet.getString("object_ids"); //NON-NLS + + Pattern.compile(",").splitAsStream(objIds) + .map(Long::valueOf) + .filter(getDrawableDB()::isInDB) + .findAny().ifPresent(obj_id -> types.add(mimeType)); } - String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type "; - try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS - ResultSet resultSet = executeQuery.getResultSet();) { - while (resultSet.next()) { - final String mimeType = resultSet.getString("mime_type"); //NON-NLS - String objIds = resultSet.getString("object_ids"); //NON-NLS + } catch (SQLException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + } + results.putAll(null, types); - Pattern.compile(",").splitAsStream(objIds) - .map(Long::valueOf) - .filter(getDrawableDB()::isInDB) - .findAny().ifPresent(obj_id -> types.add(mimeType)); - } - } catch (SQLException | TskCoreException ex) { - Exceptions.printStackTrace(ex); - } - results.putAll(null, types); - - break; - default: - //otherwise do straight db query - results.putAll(getDrawableDB().findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS + break; + default: + //otherwise do straight db query + results.putAll(getDrawableDB().findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); } - return results; + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS } + return results; + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java index 66736ab2f9..b06153ee83 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupTree.java @@ -78,14 +78,15 @@ final public class GroupTree extends NavPanel> { groupTree.setShowRoot(false); getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change change) -> { + GroupViewState oldState = getController().getViewState(); while (change.next()) { change.getAddedSubList().stream().forEach(this::insertGroup); change.getRemoved().stream().forEach(this::removeFromTree); } Platform.runLater(() -> { - GroupTree.this.sortGroups(); - Optional.ofNullable(getController().getViewState()) + GroupTree.this.sortGroups(false); + Optional.ofNullable(oldState) .flatMap(GroupViewState::getGroup) .ifPresent(this::setFocusedGroup); }); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java index aa3af58b78..e8c97c1379 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/NavPanel.java @@ -75,9 +75,9 @@ abstract class NavPanel extends Tab { sortChooser = new SortChooser<>(GroupComparators.getValues()); sortChooser.setComparator(getDefaultComparator()); - sortChooser.sortOrderProperty().addListener(order -> sortGroups()); + sortChooser.sortOrderProperty().addListener(order -> NavPanel.this.sortGroups()); sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> { - sortGroups(); + NavPanel.this.sortGroups(); //only need to listen to changes in category if we are sorting by/ showing the uncategorized count if (newComparator == GroupComparators.UNCATEGORIZED_COUNT) { categoryManager.registerListener(NavPanel.this); @@ -135,11 +135,18 @@ abstract class NavPanel extends Tab { */ @ThreadConfined(type = ThreadConfined.ThreadType.JFX) void sortGroups() { + sortGroups(true); + } + + public void sortGroups(boolean preserveSelection) { + X selectedItem = getSelectionModel().getSelectedItem(); applyGroupComparator(); - Optional.ofNullable(selectedItem) - .map(getDataItemMapper()) - .ifPresent(this::setFocusedGroup); + if (preserveSelection) { + Optional.ofNullable(selectedItem) + .map(getDataItemMapper()) + .ifPresent(this::setFocusedGroup); + } } /** From cd6086b9c58be5d44edbd2f61206a80efcf10145 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 12 Sep 2018 13:39:45 +0200 Subject: [PATCH 74/84] make sure service.restart is called on JavaFX thread. --- .../autopsy/imagegallery/datamodel/grouping/GroupManager.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 0a23460778..5202f40a7f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -453,8 +453,7 @@ public class GroupManager { setGroupBy(groupBy); setSortBy(sortBy); setSortOrder(sortOrder); - - regrouper.restart(); + Platform.runLater(regrouper::restart); } else { // resort the list of groups setSortBy(sortBy); From c4d1376f15937e304ac2be4982390b0cc9c90f76 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Wed, 12 Sep 2018 14:18:34 +0200 Subject: [PATCH 75/84] remove unneeded synchronization og ImageGalleryController.getDatabase() --- .../sleuthkit/autopsy/imagegallery/ImageGalleryController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 354ab62914..8b0d40aeb6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -149,7 +149,7 @@ public final class ImageGalleryController { return groupManager; } - synchronized public DrawableDB getDatabase() { + public DrawableDB getDatabase() { return drawableDB; } From 722af489aa9ca30fc3b247c56849db240a8f73fd Mon Sep 17 00:00:00 2001 From: Raman Date: Wed, 12 Sep 2018 13:34:02 -0400 Subject: [PATCH 76/84] 1012: Allow user to view groups that are either new to them or new to everyone - Added image_gallery_groups_seen table to track which groups have been seen by which examiner. --- .../imagegallery/datamodel/DrawableDB.java | 124 +++++++++++++++--- .../datamodel/grouping/GroupManager.java | 25 ++-- 2 files changed, 125 insertions(+), 24 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 99b5e2bd63..584ad2b8c1 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -89,6 +89,7 @@ public final class DrawableDB { private static final String HASH_SET_NAME = "hash_set_name"; //NON-NLS private static final String GROUPS_TABLENAME = "image_gallery_groups"; //NON-NLS + private static final String GROUPS_SEEN_TABLENAME = "image_gallery_groups_seen"; //NON-NLS private final PreparedStatement insertHashSetStmt; @@ -124,7 +125,7 @@ public final class DrawableDB { private final PreparedStatement hashSetGroupStmt; /** - * map from {@link DrawableAttribute} to the {@link PreparedStatement} thet + * map from {@link DrawableAttribute} to the {@link PreparedStatement} that * is used to select groups for that attribute */ private final Map, PreparedStatement> groupStatementMap = new HashMap<>(); @@ -407,9 +408,11 @@ public final class DrawableDB { return false; } - // The ig_groups table is created in the Case Database + String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "BIGSERIAL" : "INTEGER" ; + + // The image_gallery_groups table is created in the Case Database try { - String autogenKeyType = (DbType.POSTGRESQL == tskCase.getDatabaseType()) ? "SERIAL" : "INTEGER" ; + String tableSchema = "( group_id " + autogenKeyType + " PRIMARY KEY, " //NON-NLS + " value VARCHAR(255) not null, " //NON-NLS @@ -423,7 +426,27 @@ public final class DrawableDB { LOGGER.log(Level.SEVERE, "problem creating groups table", ex); //NON-NLS return false; } - + + // The image_gallery_groups_seen table is created in the Case Database + try { + + String tableSchema = + "( id " + autogenKeyType + " PRIMARY KEY, " //NON-NLS + + " group_id integer not null, " //NON-NLS + + " examiner_id integer not null, " //NON-NLS + + " seen integer DEFAULT 0, " //NON-NLS + + " UNIQUE(group_id, examiner_id)," + + " FOREIGN KEY(group_id) REFERENCES " + GROUPS_TABLENAME +"(group_id)," + + " FOREIGN KEY(examiner_id) REFERENCES tsk_examiners(examiner_id)" + + " )"; //NON-NLS + + tskCase.getCaseDbAccessManager().createTable(GROUPS_SEEN_TABLENAME, tableSchema); + } + catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "problem creating groups_seen table", ex); //NON-NLS + return false; + } + try (Statement stmt = con.createStatement()) { String sql = "CREATE TABLE if not exists hash_sets " //NON-NLS + "( hash_set_id INTEGER primary key," //NON-NLS @@ -568,8 +591,16 @@ public final class DrawableDB { return names; } - public boolean isGroupSeen(GroupKey groupKey) { - + /** + * Returns true if the specified group has been seen by the specified examiner + * + * @param groupKey - key to identify the group + * @param examinerId + * + * @return true if the examine has this group, false otherwise + */ + public boolean isGroupSeenByExaminer(GroupKey groupKey, long examinerId) { + // Callback to process result of seen query class GroupSeenQueryResultProcessor implements CaseDbAccessQueryCallback { private boolean seen = false; @@ -592,9 +623,64 @@ public final class DrawableDB { } } } + try { + + // query to find the group id from attribute/value + String groupIdQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME + + " WHERE attribute = \'%s\' AND value = \'%s\' )", groupKey.getAttribute().attrName.toString(), groupKey.getValueDisplayName() ); + + String groupSeenQueryStmt = String.format("seen FROM " + GROUPS_SEEN_TABLENAME + " WHERE examiner_id = %d AND group_id in ( %s )", examinerId, groupIdQuery); + GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor(); + + tskCase.getCaseDbAccessManager().select(groupSeenQueryStmt, queryResultProcessor); + return queryResultProcessor.getGroupSeen(); + } + catch (TskCoreException ex) { + String msg = String.format("Failed to get is group seen for group key %s", groupKey.getValueDisplayName()); //NON-NLS + LOGGER.log(Level.WARNING, msg, ex); + } + + return false; + } + + /** + * Returns true if the specified group has been any examiner + * + * @param groupKey + * @return + */ + public boolean isGroupSeen(GroupKey groupKey) { + + // Callback to process result of seen query + class GroupSeenQueryResultProcessor implements CaseDbAccessQueryCallback { + private boolean seen = false; + + boolean getGroupSeen() { + return seen; + } + + @Override + public void process(ResultSet resultSet) { + try { + if (resultSet != null) { + while (resultSet.next()) { + int count = resultSet.getInt("count"); + seen = count > 0; + return; + } + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "failed to get group seen", ex); //NON-NLS + } + } + } try { - String groupSeenQueryStmt = String.format("seen FROM " + GROUPS_TABLENAME + " WHERE value = \'%s\' AND attribute = \'%s\'", groupKey.getValueDisplayName(), groupKey.getAttribute().attrName.toString() ); + // query to find the group id from attribute/value + String groupIdQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME + + " WHERE attribute = \'%s\' AND value = \'%s\' )", groupKey.getAttribute().attrName.toString(), groupKey.getValueDisplayName() ); + + String groupSeenQueryStmt = String.format("COUNT((*) as count FROM " + GROUPS_SEEN_TABLENAME + " WHERE seen = 1 AND group_id in ( %s )", groupIdQuery); GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor(); tskCase.getCaseDbAccessManager().select(groupSeenQueryStmt, queryResultProcessor); @@ -608,11 +694,19 @@ public final class DrawableDB { return false; } - public void markGroupSeen(GroupKey gk, boolean seen) { + public void markGroupSeen(GroupKey gk, boolean seen, long examiner_id) { try { - String updateSQL = String.format("set seen = %d where value = \'%s\' and attribute = \'%s\'", seen ? 1 : 0, - gk.getValueDisplayName(), gk.getAttribute().attrName.toString() ); - tskCase.getCaseDbAccessManager().update(GROUPS_TABLENAME, updateSQL); + // query to find the group id from attribute/value + String innerQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME + + " WHERE attribute = \'%s\' AND value = \'%s\' )", gk.getAttribute().attrName.toString(), gk.getValueDisplayName() ); + + String insertSQL = String.format(" (group_id, examiner_id, seen) VALUES (%s, %d, %d)", innerQuery, examiner_id, seen ? 1: 0); + + if (DbType.POSTGRESQL == tskCase.getDatabaseType()) { + insertSQL += String.format(" ON CONFLICT (group_id, examiner_id) DO UPDATE SET seen = %d", seen ? 1: 0); + } + + tskCase.getCaseDbAccessManager().insertOrUpdate(GROUPS_SEEN_TABLENAME, insertSQL); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS } @@ -1088,18 +1182,16 @@ public final class DrawableDB { private void insertGroup(final String value, DrawableAttribute groupBy, CaseDbTransaction caseDbTransaction) { String insertSQL = ""; try { - insertSQL = String.format(" (value, attribute) VALUES (\'%s\', \'%s\')", value, groupBy.attrName.toString());; - + insertSQL = String.format(" (value, attribute) VALUES (\'%s\', \'%s\')", value, groupBy.attrName.toString()); if (DbType.POSTGRESQL == tskCase.getDatabaseType()) { - insertSQL += String.format(" ON CONFLICT (value, attribute) DO UPDATE SET value = \'%s\', attribute=\'%s\'", value, groupBy.attrName.toString()); + insertSQL += String.format(" ON CONFLICT DO NOTHING "); } - tskCase.getCaseDbAccessManager().insertOrUpdate(GROUPS_TABLENAME, insertSQL, caseDbTransaction); + tskCase.getCaseDbAccessManager().insert(GROUPS_TABLENAME, insertSQL, caseDbTransaction ); } catch (TskCoreException ex) { // Don't need to report it if the case was closed if (Case.isCaseOpen()) { LOGGER.log(Level.SEVERE, "Unable to insert group", ex); //NON-NLS - } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index fb381160f7..f17c0b8a63 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -79,6 +79,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.ContentTag; +import org.sleuthkit.datamodel.Examiner; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; @@ -267,14 +268,20 @@ public class GroupManager { @ThreadConfined(type = ThreadType.JFX) public void markGroupSeen(DrawableGroup group, boolean seen) { if (nonNull(db)) { - db.markGroupSeen(group.getGroupKey(), seen); - group.setSeen(seen); - if (seen) { - unSeenGroups.removeAll(group); - } else if (unSeenGroups.contains(group) == false) { - unSeenGroups.add(group); + try { + Examiner examiner = controller.getSleuthKitCase().getCurrentExaminer(); + + db.markGroupSeen(group.getGroupKey(), seen, examiner.getId()); + group.setSeen(seen); + if (seen) { + unSeenGroups.removeAll(group); + } else if (unSeenGroups.contains(group) == false) { + unSeenGroups.add(group); + } + FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy)); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to set seen status for group", ex); //NON-NLS } - FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy)); } } @@ -679,7 +686,9 @@ public class GroupManager { Set fileIDs = getFileIDsInGroup(groupKey); if (Objects.nonNull(fileIDs)) { DrawableGroup group; - final boolean groupSeen = db.isGroupSeen(groupKey); + Examiner examiner = controller.getSleuthKitCase().getCurrentExaminer(); + + final boolean groupSeen = db.isGroupSeenByExaminer(groupKey, examiner.getId()); synchronized (groupMap) { if (groupMap.containsKey(groupKey)) { group = groupMap.get(groupKey); From ec9ab46aca50713b7503381f09a918b17fdb8662 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 13 Sep 2018 13:30:58 +0200 Subject: [PATCH 77/84] fix initialization of listeningEnabled and stale properties. --- .../imagegallery/ImageGalleryController.java | 20 +-- .../imagegallery/ImageGalleryModule.java | 137 +++++++++--------- .../ImageGalleryTopComponent.java | 2 +- .../imagegallery/actions/OpenAction.java | 5 +- 4 files changed, 84 insertions(+), 80 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 8b0d40aeb6..a30a633759 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -187,7 +187,6 @@ public final class ImageGalleryController { this.sleuthKitCase = newCase.getSleuthkitCase(); setListeningEnabled(ImageGalleryModule.isEnabledforCase(newCase)); - setStale(ImageGalleryModule.isDrawableDBStale(newCase)); groupManager = new GroupManager(this); this.drawableDB = DrawableDB.getDrawableDB(this); @@ -197,8 +196,8 @@ public final class ImageGalleryController { tagsManager.registerListener(categoryManager); hashSetManager = new HashSetManager(drawableDB); + setStale(isDataSourcesTableStale()); - shutDownDBExecutor(); dbExecutor = getNewDBExecutor(); // listener for the boolean property about when IG is listening / enabled @@ -207,7 +206,7 @@ public final class ImageGalleryController { // 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 (isEnabled && !wasPreviouslyEnabled - && ImageGalleryModule.isDrawableDBStale(Case.getCurrentCaseThrows()) + && isDataSourcesTableStale() && (Case.getCurrentCaseThrows().getCaseType() == CaseType.SINGLE_USER_CASE)) { //populate the db this.rebuildDB(); @@ -232,6 +231,7 @@ public final class ImageGalleryController { ingestManager.addIngestJobEventListener(ingestEventHandler); dbTaskQueueSize.addListener(obs -> this.updateRegroupDisabled()); + } public ReadOnlyBooleanProperty getCanAdvance() { @@ -273,24 +273,22 @@ public final class ImageGalleryController { */ public void rebuildDB() { // queue a rebuild task for each stale data source - getStaleDataSourceIds().forEach((dataSourceObjId) - -> queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, this)) - ); + getStaleDataSourceIds().forEach((dataSourceObjId) -> queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, this))); } /** * reset the state of the controller (eg if the case is closed) */ - public synchronized void shutDown() { + public synchronized void reset() { logger.info("Closing ImageGalleryControler for case."); //NON-NLS selectionModel.clearSelection(); - setListeningEnabled(false); thumbnailCache.clearCache(); historyManager.clear(); groupManager.reset(); shutDownDBExecutor(); + dbExecutor = getNewDBExecutor(); } /** @@ -298,7 +296,7 @@ public final class ImageGalleryController { * * @return true if datasources table is stale */ - boolean isDataSourcesTableStale() { + public boolean isDataSourcesTableStale() { return isNotEmpty(getStaleDataSourceIds()); } @@ -325,9 +323,7 @@ public final class ImageGalleryController { List dataSources = getSleuthKitCase().getDataSources(); Set caseDataSourceIds = new HashSet<>(); - dataSources.forEach((dataSource) -> { - caseDataSourceIds.add(dataSource.getId()); - }); + dataSources.stream().map(DataSource::getId).forEach(caseDataSourceIds::add); // collect all data sources already in the table, that are not yet COMPLETE knownDataSourceIds.entrySet().stream().forEach((Map.Entry t) -> { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java index f0f733bd36..5a942132db 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java @@ -27,6 +27,7 @@ import javafx.application.Platform; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import static org.apache.commons.lang3.StringUtils.isNotBlank; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -128,14 +129,8 @@ public class ImageGalleryModule { * @return true if the drawable db is out of date for the given case, false * otherwise */ - public static boolean isDrawableDBStale(Case c) { - synchronized (controllerLock) { - if (controller != null) { - return controller.isDataSourcesTableStale(); - } else { - return false; - } - } + public static boolean isDrawableDBStale(Case c) throws TskCoreException { + return new ImageGalleryController(c).isDataSourcesTableStale(); } /** @@ -147,7 +142,7 @@ public class ImageGalleryModule { * @return true if the given {@link AbstractFile} is "drawable" and not * 'known', else false */ - public static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws TskCoreException, FileTypeDetector.FileTypeDetectorInitException { + public static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws FileTypeDetector.FileTypeDetectorInitException { return (abstractFile.getKnown() != TskData.FileKnown.KNOWN) && FileTypeUtils.isDrawable(abstractFile); } @@ -183,26 +178,29 @@ public class ImageGalleryModule { return; } - synchronized (controllerLock) { - if (controller != null && controller.isListeningEnabled()) { + try { + ImageGalleryController con = getController(); + if (con.isListeningEnabled()) { try { if (isDrawableAndNotKnown(file)) { //this file should be included and we don't already know about it from hash sets (NSRL) - controller.queueDBTask(new ImageGalleryController.UpdateFileTask(file, controller.getDatabase())); + con.queueDBTask(new ImageGalleryController.UpdateFileTask(file, controller.getDatabase())); } 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 */ - controller.queueDBTask(new ImageGalleryController.RemoveFileTask(file, controller.getDatabase())); + con.queueDBTask(new ImageGalleryController.RemoveFileTask(file, controller.getDatabase())); } - } catch (TskCoreException | FileTypeDetector.FileTypeDetectorInitException ex) { + } catch (FileTypeDetector.FileTypeDetectorInitException ex) { 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 (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS } } } @@ -223,15 +221,22 @@ public class ImageGalleryModule { Case.removePropertyChangeListener(this); return; } - synchronized (controllerLock) { - switch (Case.Events.valueOf(evt.getPropertyName())) { - case CURRENT_CASE: + ImageGalleryController con; + try { + con = getController(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS + return; + } + switch (Case.Events.valueOf(evt.getPropertyName())) { + case CURRENT_CASE: + synchronized (controllerLock) { // case has changes: close window, reset everything SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent); if (controller != null) { - controller.shutDown(); - controller = null; + controller.reset(); } + controller = null; Case newCase = (Case) evt.getNewValue(); if (newCase != null) { @@ -242,32 +247,32 @@ public class ImageGalleryModule { logger.log(Level.SEVERE, "Error changing case in ImageGallery.", ex); } } - break; - case DATA_SOURCE_ADDED: - //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 (controller.isListeningEnabled()) { - controller.queueDBTask(new ImageGalleryController.PrePopulateDataSourceFiles(newDataSource.getId(), controller)); - } + } + break; + case DATA_SOURCE_ADDED: + //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 (con.isListeningEnabled()) { + con.queueDBTask(new ImageGalleryController.PrePopulateDataSourceFiles(newDataSource.getId(), controller)); } - break; - case CONTENT_TAG_ADDED: - final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt; - if (controller.getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) { - controller.getTagsManager().fireTagAddedEvent(tagAddedEvent); - } - break; - case CONTENT_TAG_DELETED: - final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) evt; - if (controller.getDatabase().isInDB(tagDeletedEvent.getDeletedTagInfo().getContentID())) { - controller.getTagsManager().fireTagDeletedEvent(tagDeletedEvent); - } - break; - default: - //we don't need to do anything for other events. - break; - } + } + break; + case CONTENT_TAG_ADDED: + final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt; + if (con.getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) { + con.getTagsManager().fireTagAddedEvent(tagAddedEvent); + } + break; + case CONTENT_TAG_DELETED: + final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) evt; + if (con.getDatabase().isInDB(tagDeletedEvent.getDeletedTagInfo().getContentID())) { + con.getTagsManager().fireTagDeletedEvent(tagDeletedEvent); + } + break; + default: + //we don't need to do anything for other events. + break; } } } @@ -292,29 +297,31 @@ public class ImageGalleryModule { } // 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 - synchronized (controllerLock) { - if (controller != null) { - controller.setStale(true); - if (controller.isListeningEnabled() && ImageGalleryTopComponent.isImageGalleryOpen()) { - ImageGalleryController con = controller; - SwingUtilities.invokeLater(() -> { - int showAnswer = JOptionPane.showConfirmDialog(ImageGalleryTopComponent.getTopComponent(), - Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_msg(), - Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_title(), - JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); + ImageGalleryController con; + try { + con = getController(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS + return; + } + con.setStale(true); + if (con.isListeningEnabled() && ImageGalleryTopComponent.isImageGalleryOpen()) { + SwingUtilities.invokeLater(() -> { + int showAnswer = JOptionPane.showConfirmDialog(ImageGalleryTopComponent.getTopComponent(), + Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_msg(), + Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_title(), + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); - switch (showAnswer) { - case JOptionPane.YES_OPTION: - con.rebuildDB(); - break; - case JOptionPane.NO_OPTION: - case JOptionPane.CANCEL_OPTION: - default: - break; //do nothing - } - }); + switch (showAnswer) { + case JOptionPane.YES_OPTION: + con.rebuildDB(); + break; + case JOptionPane.NO_OPTION: + case JOptionPane.CANCEL_OPTION: + default: + break; //do nothing } - } + }); } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index 344bbdfdf2..cefcffb1a8 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -230,7 +230,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl synchronized private void setController(ImageGalleryController controller) { if (this.controller != null) { - this.controller.shutDown(); + this.controller.reset(); } this.controller = controller; Platform.runLater(new Runnable() { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java index 42ea6ef139..efbdc3de00 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/OpenAction.java @@ -143,7 +143,8 @@ public final class OpenAction extends CallableSystemAction { return; } try { - if (ImageGalleryModule.isDrawableDBStale(currentCase)) { + ImageGalleryController controller = ImageGalleryModule.getController(); + if (controller.isDataSourcesTableStale()) { //drawable db is stale, ask what to do int answer = JOptionPane.showConfirmDialog( WindowManager.getDefault().getMainWindow(), @@ -161,7 +162,7 @@ public final class OpenAction extends CallableSystemAction { * user may want to review images, so we rebuild the * database only when a user launches Image Gallery. */ - ImageGalleryController controller = ImageGalleryModule.getController(); + if (currentCase.getCaseType() == Case.CaseType.SINGLE_USER_CASE) { controller.setListeningEnabled(true); From 78575254ee87c0e746c4c4e09327f03c34f78675 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Fri, 14 Sep 2018 12:37:09 +0200 Subject: [PATCH 78/84] don't reset controller if it is not changing. (and thus don't cancel db update). --- .../autopsy/imagegallery/ImageGalleryController.java | 2 +- .../autopsy/imagegallery/ImageGalleryTopComponent.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index a30a633759..da19a20776 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -273,7 +273,7 @@ public final class ImageGalleryController { */ public void rebuildDB() { // queue a rebuild task for each stale data source - getStaleDataSourceIds().forEach((dataSourceObjId) -> queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, this))); + getStaleDataSourceIds().forEach(dataSourceObjId -> queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, this))); } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java index cefcffb1a8..9263913748 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryTopComponent.java @@ -26,7 +26,6 @@ import java.util.Optional; import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; -import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.embed.swing.JFXPanel; import javafx.geometry.Insets; @@ -48,6 +47,7 @@ import javafx.scene.paint.Color; import javafx.stage.Modality; import javax.swing.SwingUtilities; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; +import static org.apache.commons.lang3.ObjectUtils.notEqual; import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerUtils; import org.openide.util.Lookup; @@ -229,7 +229,7 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl } synchronized private void setController(ImageGalleryController controller) { - if (this.controller != null) { + if (this.controller != null && notEqual(this.controller, controller)) { this.controller.reset(); } this.controller = controller; From 3d4bab09ad0086d874a17ee0eda523273355af15 Mon Sep 17 00:00:00 2001 From: Raman Date: Fri, 14 Sep 2018 11:33:17 -0400 Subject: [PATCH 79/84] Removed 'seen' column from groups table. --- .../org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java | 1 - 1 file changed, 1 deletion(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 584ad2b8c1..4870e78ed3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -417,7 +417,6 @@ public final class DrawableDB { "( group_id " + autogenKeyType + " PRIMARY KEY, " //NON-NLS + " value VARCHAR(255) not null, " //NON-NLS + " attribute VARCHAR(255) not null, " //NON-NLS - + " seen integer DEFAULT 0, " //NON-NLS + " UNIQUE(value, attribute) )"; //NON-NLS tskCase.getCaseDbAccessManager().createTable(GROUPS_TABLENAME, tableSchema); From df567df149a9aeb844924cd6fb95fa47b0d3d7e7 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Sat, 15 Sep 2018 12:46:01 +0200 Subject: [PATCH 80/84] cleanup --- .../imagegallery/actions/NextUnseenGroup.java | 2 +- .../imagegallery/datamodel/DrawableDB.java | 89 +++++++++---------- .../datamodel/grouping/GroupManager.java | 4 +- 3 files changed, 42 insertions(+), 53 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index 7cae13caa3..22f0fcb12b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -69,7 +69,7 @@ public class NextUnseenGroup extends Action { Optional.ofNullable(controller.getViewState()) .flatMap(GroupViewState::getGroup) .ifPresent(group -> { - groupManager.setGroupSeen(group, true) + groupManager.markGroupSeen(group, true) .addListener(this::advanceToNextUnseenGroup, MoreExecutors.newDirectExecutorService()); }); }); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index df19e57aa1..0558e895a5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -42,6 +42,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; import java.util.logging.Level; import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -629,6 +630,41 @@ public final class DrawableDB { return names; } + // Callback to process result of seen query + private static class GroupSeenQueryResultProcessor implements CaseDbAccessQueryCallback { + + private interface SQLFunction { + + T2 apply(T1 rs) throws SQLException; + } + + private final SQLFunction resultExtractor; + + GroupSeenQueryResultProcessor(SQLFunction resultExtractor) { + this.resultExtractor = resultExtractor; + } + + private boolean seen = false; + + boolean getGroupSeen() { + return seen; + } + + @Override + public void process(ResultSet resultSet) { + try { + if (resultSet != null) { + while (resultSet.next()) { + seen = resultExtractor.apply(resultSet); //NON-NLS; + return; + } + } + } catch (SQLException ex) { + logger.log(Level.SEVERE, "failed to get group seen", ex); //NON-NLS + } + } + } + /** * Returns true if the specified group has been any examiner * @@ -637,30 +673,9 @@ public final class DrawableDB { * @return */ public boolean isGroupSeen(GroupKey groupKey) { - // Callback to process result of seen query - class GroupSeenQueryResultProcessor implements CaseDbAccessQueryCallback { - - private boolean seen = false; - - boolean getGroupSeen() { - return seen; - } - - @Override - public void process(ResultSet resultSet) { - try { - if (resultSet != null) { - while (resultSet.next()) { - seen = resultSet.getInt("count") > 0; - return; - } - } - } catch (SQLException ex) { - logger.log(Level.SEVERE, "failed to get group seen", ex); //NON-NLS - } - } - } + GroupSeenQueryResultProcessor queryResultProcessor + = new GroupSeenQueryResultProcessor(rs -> rs.getInt("count") > 0); try { @@ -674,8 +689,6 @@ public final class DrawableDB { String groupSeenQueryStmt = "COUNT((*) as count FROM " + GROUPS_SEEN_TABLENAME + " WHERE seen = 1 AND group_id in ( " + groupIdQuery + ")"; - GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor(); - tskCase.getCaseDbAccessManager().select(groupSeenQueryStmt, queryResultProcessor); return queryResultProcessor.getGroupSeen(); } catch (TskCoreException ex) { @@ -696,38 +709,16 @@ public final class DrawableDB { * @return true if the examine has this group, false otherwise */ public boolean isGroupSeenByExaminer(GroupKey groupKey, long examinerId) { - // Callback to process result of seen query - class GroupSeenQueryResultProcessor implements CaseDbAccessQueryCallback { + GroupSeenQueryResultProcessor queryResultProcessor + = new GroupSeenQueryResultProcessor(rs -> rs.getBoolean("seen")); - private boolean seen = false; - - boolean getGroupSeen() { - return seen; - } - - @Override - public void process(ResultSet resultSet) { - try { - if (resultSet != null) { - while (resultSet.next()) { - seen = resultSet.getBoolean("seen"); //NON-NLS; - return; - } - } - } catch (SQLException ex) { - logger.log(Level.SEVERE, "failed to get group seen", ex); //NON-NLS - } - } - } try { - // query to find the group id from attribute/value String groupIdQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME + " WHERE attribute = \'%s\' AND value = \'%s\' )", groupKey.getAttribute().attrName.toString(), groupKey.getValueDisplayName()); String groupSeenQueryStmt = String.format("seen FROM " + GROUPS_SEEN_TABLENAME + " WHERE examiner_id = %d AND group_id in ( %s )", examinerId, groupIdQuery); - GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor(); tskCase.getCaseDbAccessManager().select(groupSeenQueryStmt, queryResultProcessor); return queryResultProcessor.getGroupSeen(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index 3c7a6b99fe..04dad074e7 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -47,7 +47,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.property.ReadOnlyDoubleProperty; -import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; @@ -60,7 +59,6 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import javax.swing.SortOrder; import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; -import org.apache.commons.lang3.ObjectUtils; import static org.apache.commons.lang3.ObjectUtils.notEqual; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.concurrent.BasicThreadFactory; @@ -100,7 +98,7 @@ public class GroupManager { /** An executor to submit async UI related background tasks to. */ private final ListeningExecutorService exec = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( - new BasicThreadFactory.Builder().namingPattern("GUI Task -%d").build())); //NON-NLS + new BasicThreadFactory.Builder().namingPattern("GroupManager BG Thread-%d").build())); //NON-NLS private final ImageGalleryController controller; From 17bef5c68f54d87570def77b073525e4bdbd59ef Mon Sep 17 00:00:00 2001 From: millmanorama Date: Sun, 16 Sep 2018 12:24:24 +0200 Subject: [PATCH 81/84] wire seen by other examiners check box --- .../imagegallery/datamodel/DrawableDB.java | 7 +- .../datamodel/grouping/DrawableGroup.java | 5 +- .../datamodel/grouping/GroupManager.java | 39 ++++-- .../gui/drawableviews/GroupPane.fxml | 37 +++--- .../gui/drawableviews/GroupPane.java | 111 ++++++++++-------- .../gui/navpanel/GroupCellFactory.java | 2 +- 6 files changed, 121 insertions(+), 80 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 3fa6b8a2b4..52d2f8d240 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -634,8 +634,8 @@ public final class DrawableDB { static private String getGroupIdQuery(GroupKey groupKey) { // query to find the group id from attribute/value - return String.format("( SELECT group_id FROM " + GROUPS_TABLENAME - + " WHERE attribute = \'%s\' AND value = \'%s\' AND data_source_obj_id = %d)", + return String.format(" SELECT group_id FROM " + GROUPS_TABLENAME + + " WHERE attribute = \'%s\' AND value = \'%s\' AND data_source_obj_id = %d", groupKey.getAttribute().attrName.toString(), groupKey.getValueDisplayName(), (groupKey.getAttribute() == DrawableAttribute.PATH) ? groupKey.getDataSourceObjId() : 0); @@ -685,7 +685,8 @@ public final class DrawableDB { try { String groupSeenQueryStmt = "COUNT(*) as count FROM " + GROUPS_SEEN_TABLENAME - + " WHERE group_id in ( " + getGroupIdQuery(groupKey) + ")" + + " WHERE seen = 1 " + + " AND group_id in ( " + getGroupIdQuery(groupKey) + ")" + (examinerId > 0 ? " AND examiner_id = " + examinerId : "");// query to find the group id from attribute/value tskCase.getCaseDbAccessManager().select(groupSeenQueryStmt, queryResultProcessor); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java index 64a73d02b8..224ac378c6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/DrawableGroup.java @@ -26,6 +26,7 @@ import java.util.logging.Level; import javafx.beans.binding.Bindings; import javafx.beans.binding.DoubleBinding; import javafx.beans.binding.IntegerBinding; +import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.ReadOnlyLongProperty; import javafx.beans.property.ReadOnlyLongWrapper; @@ -165,8 +166,8 @@ public class DrawableGroup implements Comparable { return seen.get(); } - public ReadOnlyBooleanWrapper seenProperty() { - return seen; + public ReadOnlyBooleanProperty seenProperty() { + return seen.getReadOnlyProperty(); } @Subscribe diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java index e48d4990cc..fa40fdb66a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java @@ -42,10 +42,12 @@ import java.util.TreeSet; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.regex.Pattern; import java.util.stream.Collectors; import javafx.application.Platform; +import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; @@ -126,6 +128,7 @@ public class GroupManager { private final ReadOnlyObjectWrapper< DrawableAttribute> groupByProp = new ReadOnlyObjectWrapper<>(DrawableAttribute.PATH); private final ReadOnlyObjectWrapper sortOrderProp = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING); private final ReadOnlyObjectWrapper dataSourceProp = new ReadOnlyObjectWrapper<>(null);//null indicates all datasources + private final ReadOnlyBooleanWrapper collaborativeModeProp = new ReadOnlyBooleanWrapper(false); private final GroupingService regrouper; @@ -249,15 +252,15 @@ public class GroupManager { Examiner examiner = controller.getSleuthKitCase().getCurrentExaminer(); getDrawableDB().markGroupSeen(group.getGroupKey(), seen, examiner.getId()); group.setSeen(seen); - updateUnSeenGroups(group, seen); + updateUnSeenGroups(group); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS } }); } - synchronized private void updateUnSeenGroups(DrawableGroup group, boolean seen) { - if (seen) { + synchronized private void updateUnSeenGroups(DrawableGroup group) { + if (group.isSeen()) { unSeenGroups.removeAll(group); } else if (unSeenGroups.contains(group) == false) { unSeenGroups.add(group); @@ -580,8 +583,8 @@ public class GroupManager { Set fileIDs = getFileIDsInGroup(groupKey); if (Objects.nonNull(fileIDs)) { - Examiner examiner = controller.getSleuthKitCase().getCurrentExaminer(); - final boolean groupSeen = getDrawableDB().isGroupSeenByExaminer(groupKey, examiner.getId()); + long examinerID = collaborativeModeProp.get() ? -1 : controller.getSleuthKitCase().getCurrentExaminer().getId(); + final boolean groupSeen = getDrawableDB().isGroupSeenByExaminer(groupKey, examinerID); DrawableGroup group; if (groupMap.containsKey(groupKey)) { @@ -598,10 +601,9 @@ public class GroupManager { analyzedGroups.add(group); sortAnalyzedGroups(); } - updateUnSeenGroups(group, groupSeen); + updateUnSeenGroups(group); return group; - } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex); //NON-NLS @@ -634,6 +636,29 @@ public class GroupManager { } } + synchronized public void setCollaborativeMode(Boolean newValue) { + collaborativeModeProp.set(newValue); + analyzedGroups.forEach(group -> { + try { + boolean groupSeenByExaminer = getDrawableDB().isGroupSeenByExaminer( + group.getGroupKey(), + newValue ? -1 : controller.getSleuthKitCase().getCurrentExaminer().getId() + ); + group.setSeen(groupSeenByExaminer); + updateUnSeenGroups(group); + if (group.isSeen()) { + unSeenGroups.removeAll(group); + } else if (unSeenGroups.contains(group) == false) { + unSeenGroups.add(group); + } + + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error checking seen state of group.", ex); + } + }); + sortUnseenGroups(); + } + /** * Task to query database for files in sorted groups and build * DrawableGroups for them. diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml index ec6e539f87..fc286f3c7b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml @@ -2,6 +2,7 @@ + @@ -11,6 +12,7 @@ + @@ -18,7 +20,7 @@ - +
@@ -29,7 +31,7 @@ diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index d730f8346a..faec0b768d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -57,7 +57,9 @@ import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.geometry.Bounds; +import javafx.scene.Cursor; import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; import javafx.scene.control.MenuItem; @@ -85,6 +87,7 @@ import static javafx.scene.input.KeyCode.RIGHT; import static javafx.scene.input.KeyCode.UP; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; +import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Border; import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderStroke; @@ -95,6 +98,7 @@ import javafx.scene.layout.HBox; import javafx.scene.paint.Color; import javafx.util.Duration; import javax.swing.SwingUtilities; +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; import org.apache.commons.lang3.StringUtils; import org.controlsfx.control.GridCell; import org.controlsfx.control.GridView; @@ -135,9 +139,8 @@ import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; import org.sleuthkit.datamodel.TskCoreException; /** - * A GroupPane displays the contents of a {@link DrawableGroup}. It supports - * both a {@link GridView} based view and a {@link SlideShowView} view by - * swapping out its internal components. + * A GroupPane displays the contents of a DrawableGroup. It supports both + * GridView and SlideShowView modes by swapping out its internal components. * * * TODO: Extract the The GridView instance to a separate class analogous to the @@ -150,27 +153,21 @@ import org.sleuthkit.datamodel.TskCoreException; public class GroupPane extends BorderPane { private static final Logger logger = Logger.getLogger(GroupPane.class.getName()); - private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(GroupPane.class); private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2); private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2); private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE); - private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)), + private static final Timeline flashAnimation = new Timeline( + new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)), new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR)) ); - private final FileIDSelectionModel selectionModel; - private static final List categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5, KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5); - private final Back backAction; - - private final Forward forwardAction; - @FXML private Button undoButton; @FXML @@ -178,13 +175,10 @@ public class GroupPane extends BorderPane { @FXML private SplitMenuButton catSelectedSplitMenu; - @FXML private SplitMenuButton tagSelectedSplitMenu; - @FXML private ToolBar headerToolBar; - @FXML private ToggleButton cat0Toggle; @FXML @@ -201,26 +195,25 @@ public class GroupPane extends BorderPane { @FXML private SegmentedButton segButton; - private SlideShowView slideShowPane; - @FXML private ToggleButton slideShowToggle; - - @FXML - private GridView gridView; - @FXML private ToggleButton tileToggle; + private SlideShowView slideShowPane; + + @FXML + private GridView gridView; @FXML private Button nextButton; - + @FXML + private AnchorPane nextButtonPane; + @FXML + private CheckBox seenByOtherExaminersCheckBox; @FXML private Button backButton; - @FXML private Button forwardButton; - @FXML private Label groupLabel; @FXML @@ -237,30 +230,27 @@ public class GroupPane extends BorderPane { @FXML private HBox catSplitMenuContainer; - private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler(); - - private final NextUnseenGroup nextGroupAction; + private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(GroupPane.class); private final ImageGalleryController controller; - private ContextMenu contextMenu; - + private final FileIDSelectionModel selectionModel; private Integer selectionAnchorIndex; + private final UndoAction undoAction; private final RedoAction redoAction; + private final Back backAction; + private final Forward forwardAction; + private final NextUnseenGroup nextGroupAction; - GroupViewMode getGroupViewMode() { - return groupViewMode.get(); - } + private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler(); - /** - * the current GroupViewMode of this GroupPane - */ + private ContextMenu contextMenu; + + /** the current GroupViewMode of this GroupPane */ private final SimpleObjectProperty groupViewMode = new SimpleObjectProperty<>(GroupViewMode.TILE); - /** - * the grouping this pane is currently the view for - */ + /** the grouping this pane is currently the view for */ private final ReadOnlyObjectWrapper grouping = new ReadOnlyObjectWrapper<>(); /** @@ -294,6 +284,10 @@ public class GroupPane extends BorderPane { FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS } + GroupViewMode getGroupViewMode() { + return groupViewMode.get(); + } + @ThreadConfined(type = ThreadType.JFX) public void activateSlideShowViewer(Long slideShowFileID) { groupViewMode.set(GroupViewMode.SLIDE_SHOW); @@ -340,7 +334,9 @@ public class GroupPane extends BorderPane { } /** - * create the string to display in the group header + * Create the string to display in the group header. + * + * @return The string to display in the group header. */ @NbBundle.Messages({"# {0} - default group name", "# {1} - hashset hits count", @@ -391,19 +387,20 @@ public class GroupPane extends BorderPane { "GroupPane.catContainerLabel.displayText=Categorize Selected File:", "GroupPane.catHeadingLabel.displayText=Category:"}) void initialize() { - assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; - assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; - assert cat2Toggle != null : "fx:id=\"cat2Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; - assert cat3Toggle != null : "fx:id=\"cat3Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; - assert cat4Toggle != null : "fx:id=\"cat4Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; - assert cat5Toggle != null : "fx:id=\"cat5Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; + assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'GroupPane.fxml'."; + assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'GroupPane.fxml'."; + assert cat2Toggle != null : "fx:id=\"cat2Toggle\" was not injected: check your FXML file 'GroupPane.fxml'."; + assert cat3Toggle != null : "fx:id=\"cat3Toggle\" was not injected: check your FXML file 'GroupPane.fxml'."; + assert cat4Toggle != null : "fx:id=\"cat4Toggle\" was not injected: check your FXML file 'GroupPane.fxml'."; + assert cat5Toggle != null : "fx:id=\"cat5Toggle\" was not injected: check your FXML file 'GroupPane.fxml'."; assert gridView != null : "fx:id=\"tilePane\" was not injected: check your FXML file 'GroupPane.fxml'."; - assert catSelectedSplitMenu != null : "fx:id=\"grpCatSplitMenu\" was not injected: check your FXML file 'GroupHeader.fxml'."; - assert tagSelectedSplitMenu != null : "fx:id=\"grpTagSplitMenu\" was not injected: check your FXML file 'GroupHeader.fxml'."; - assert headerToolBar != null : "fx:id=\"headerToolBar\" was not injected: check your FXML file 'GroupHeader.fxml'."; - assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'."; - assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'."; - assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'."; + assert catSelectedSplitMenu != null : "fx:id=\"grpCatSplitMenu\" was not injected: check your FXML file 'GroupPane.fxml'."; + assert tagSelectedSplitMenu != null : "fx:id=\"grpTagSplitMenu\" was not injected: check your FXML file 'GroupPane.fxml'."; + assert headerToolBar != null : "fx:id=\"headerToolBar\" was not injected: check your FXML file 'GroupPane.fxml'."; + assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupPane.fxml'."; + assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupPane.fxml'."; + assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupPane.fxml'."; + assert seenByOtherExaminersCheckBox != null : "fx:id=\"seenByOtherExaminersCheckBox\" was not injected: check your FXML file 'GroupPane.fxml'."; for (DhsImageCategory cat : DhsImageCategory.values()) { ToggleButton toggleForCategory = getToggleForCategory(cat); @@ -530,6 +527,16 @@ public class GroupPane extends BorderPane { } }); + seenByOtherExaminersCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> { + nextButtonPane.setDisable(true); + nextButtonPane.setCursor(Cursor.WAIT); + exec.submit(() -> controller.getGroupManager().setCollaborativeMode(newValue)) + .addListener(() -> { + nextButtonPane.setDisable(false); + nextButtonPane.setCursor(Cursor.DEFAULT); + }, Platform::runLater); + }); + //listen to tile selection and make sure it is visible in scroll area selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> { if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW @@ -607,7 +614,7 @@ public class GroupPane extends BorderPane { * assigns a grouping for this pane to represent and initializes grouping * specific properties and listeners * - * @param grouping the new grouping assigned to this group + * @param newViewState */ void setViewState(GroupViewState newViewState) { @@ -894,7 +901,7 @@ public class GroupPane extends BorderPane { if (t.getClickCount() == 1) { selectAllFiles(); } - if (selectionModel.getSelected().isEmpty() == false) { + if (isNotEmpty(selectionModel.getSelected())) { if (contextMenu == null) { contextMenu = buildContextMenu(); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java index d21b76d018..72f602d06a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/GroupCellFactory.java @@ -173,7 +173,7 @@ class GroupCellFactory { private final InvalidationListener groupListener = new GroupListener<>(this); /** - * reference to group files listener that allows us to remove it from a + * Reference to group files listener that allows us to remove it from a * group when a new group is assigned to this Cell */ @Override From 2d4ae925de67cc8ae2c2d833f9605e908913b241 Mon Sep 17 00:00:00 2001 From: millmanorama Date: Sun, 16 Sep 2018 13:05:52 +0200 Subject: [PATCH 82/84] fix next unseen button state and text --- .../imagegallery/actions/NextUnseenGroup.java | 38 +- .../datamodel/grouping/GroupManager.java.orig | 888 ------------------ .../gui/drawableviews/GroupPane.fxml | 2 +- 3 files changed, 30 insertions(+), 898 deletions(-) delete mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java.orig diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java index 22f0fcb12b..b53c36c91e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/NextUnseenGroup.java @@ -39,7 +39,8 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; */ @NbBundle.Messages({ "NextUnseenGroup.markGroupSeen=Mark Group Seen", - "NextUnseenGroup.nextUnseenGroup=Next Unseen group"}) + "NextUnseenGroup.nextUnseenGroup=Next Unseen group", + "NextUnseenGroup.allGroupsSeen=All Groups Have Been Seen"}) public class NextUnseenGroup extends Action { private static final String IMAGE_PATH = "/org/sleuthkit/autopsy/imagegallery/images/"; //NON-NLS @@ -50,6 +51,7 @@ public class NextUnseenGroup extends Action { private static final String MARK_GROUP_SEEN = Bundle.NextUnseenGroup_markGroupSeen(); private static final String NEXT_UNSEEN_GROUP = Bundle.NextUnseenGroup_nextUnseenGroup(); + private static final String ALL_GROUPS_SEEN = Bundle.NextUnseenGroup_allGroupsSeen(); private final ImageGalleryController controller; private final ObservableList unSeenGroups; @@ -63,6 +65,7 @@ public class NextUnseenGroup extends Action { groupManager = controller.getGroupManager(); unSeenGroups = groupManager.getUnSeenGroups(); unSeenGroups.addListener((Observable observable) -> updateButton()); + controller.viewStateProperty().addListener((Observable observable) -> updateButton()); setEventHandler(event -> { //on fx-thread //if there is a group assigned to the view, mark it as seen @@ -88,16 +91,33 @@ public class NextUnseenGroup extends Action { private void updateButton() { int size = unSeenGroups.size(); - Platform.runLater(() -> { - setDisabled(size == 0); - if (size <= 1) { - setText(MARK_GROUP_SEEN); - setGraphic(new ImageView(END_IMAGE)); + if (size < 1) { + //there are no unseen groups. + Platform.runLater(() -> { + setDisabled(true); + setText(ALL_GROUPS_SEEN); + setGraphic(null); + }); + } else { + DrawableGroup get = unSeenGroups.get(0); + DrawableGroup orElse = Optional.ofNullable(controller.getViewState()).flatMap(GroupViewState::getGroup).orElse(null); + boolean equals = get.equals(orElse); + if (size == 1 & equals) { + //The only unseen group is the one that is being viewed. + Platform.runLater(() -> { + setDisabled(false); + setText(MARK_GROUP_SEEN); + setGraphic(new ImageView(END_IMAGE)); + }); } else { - setText(NEXT_UNSEEN_GROUP); - setGraphic(new ImageView(ADVANCE_IMAGE)); + //there are more unseen groups. + Platform.runLater(() -> { + setDisabled(false); + setText(NEXT_UNSEEN_GROUP); + setGraphic(new ImageView(ADVANCE_IMAGE)); + }); } - }); + } } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java.orig b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java.orig deleted file mode 100644 index 668ef7baa4..0000000000 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/grouping/GroupManager.java.orig +++ /dev/null @@ -1,888 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013-18 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.imagegallery.datamodel.grouping; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.google.common.eventbus.Subscribe; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import static java.util.Objects.isNull; -import static java.util.Objects.nonNull; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.logging.Level; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import javafx.application.Platform; -import javafx.beans.property.ReadOnlyDoubleProperty; -import javafx.beans.property.ReadOnlyDoubleWrapper; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.beans.property.ReadOnlyObjectWrapper; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import static javafx.concurrent.Worker.State.CANCELLED; -import static javafx.concurrent.Worker.State.FAILED; -import static javafx.concurrent.Worker.State.READY; -import static javafx.concurrent.Worker.State.RUNNING; -import static javafx.concurrent.Worker.State.SCHEDULED; -import static javafx.concurrent.Worker.State.SUCCEEDED; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import javax.annotation.concurrent.GuardedBy; -import javax.swing.SortOrder; -import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.concurrent.BasicThreadFactory; -import org.netbeans.api.progress.ProgressHandle; -import org.openide.util.Exceptions; -import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; -import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; -import org.sleuthkit.autopsy.coreutils.LoggedTask; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.DhsImageCategory; -import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; -import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; -import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; -import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB; -import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; -import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.ContentTag; -import org.sleuthkit.datamodel.DataSource; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TagName; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData.DbType; -import org.sleuthkit.datamodel.TskDataException; - -/** - * Provides an abstraction layer on top of DrawableDB ( and to some extent - * SleuthkitCase ) to facilitate creation, retrieval, updating, and sorting of - * DrawableGroups. - */ -public class GroupManager { - - private static final Logger logger = Logger.getLogger(GroupManager.class.getName()); - - /** An executor to submit async UI related background tasks to. */ - private final ListeningExecutorService exec = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( - new BasicThreadFactory.Builder().namingPattern("GUI Task -%d").build())); //NON-NLS - - private final ImageGalleryController controller; - - /** list of all analyzed groups */ - @GuardedBy("this") - private final ObservableList analyzedGroups = FXCollections.observableArrayList(); - private final ObservableList unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups); - - /** list of unseen groups */ - @GuardedBy("this") - private final ObservableList unSeenGroups = FXCollections.observableArrayList(); - private final ObservableList unmodifiableUnSeenGroups = FXCollections.unmodifiableObservableList(unSeenGroups); - /** - * map from GroupKey} to DrawableGroupSs. All groups (even not fully - * analyzed or not visible groups could be in this map - */ - @GuardedBy("this") - private final Map, DrawableGroup> groupMap = new HashMap<>(); - - @GuardedBy("this") - private ReGroupTask groupByTask; - - /* - * --- current grouping/sorting attributes --- - */ - @GuardedBy("this") - private final ReadOnlyObjectWrapper< GroupSortBy> sortByProp = new ReadOnlyObjectWrapper<>(GroupSortBy.PRIORITY); - private final ReadOnlyObjectWrapper< DrawableAttribute> groupByProp = new ReadOnlyObjectWrapper<>(DrawableAttribute.PATH); - private final ReadOnlyObjectWrapper sortOrderProp = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING); - private final ReadOnlyObjectWrapper dataSourceProp = new ReadOnlyObjectWrapper<>(null);//null indicates all datasources - - private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper(); - -<<<<<<< HEAD - synchronized DrawableDB getDB() { -======= - public void setDB(DrawableDB db) { - regroup(dataSource, groupBy, sortBy, sortOrder, true); - } - - private DrawableDB getDB() { ->>>>>>> 1010/7-datasource-filtering-test - return controller.getDatabase(); - } - - @SuppressWarnings("ReturnOfCollectionOrArrayField") - public ObservableList getAnalyzedGroups() { - return unmodifiableAnalyzedGroups; - } - - @SuppressWarnings("ReturnOfCollectionOrArrayField") - public ObservableList getUnSeenGroups() { - return unmodifiableUnSeenGroups; - } - - /** - * construct a group manager hooked up to the given db and controller - * - * @param controller - */ - public GroupManager(ImageGalleryController controller) { - this.controller = controller; - } - - /** - * Using the current groupBy set for this manager, find groupkeys for all - * the groups the given file is a part of - * - * @param file - * - * - * @return A a set of GroupKeys representing the group(s) the given file is - * a part of. - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - synchronized public Set> getGroupKeysForFile(DrawableFile file) { - Set> resultSet = new HashSet<>(); - for (Comparable val : getGroupBy().getValue(file)) { - if (getGroupBy() == DrawableAttribute.TAGS) { - if (CategoryManager.isNotCategoryTagName((TagName) val)) { - resultSet.add(new GroupKey(getGroupBy(), val, getDataSource())); - } - } else { - resultSet.add(new GroupKey(getGroupBy(), val, getDataSource())); - } - } - return resultSet; - } - - /** - * Using the current grouping paramaters set for this manager, find - * GroupKeys for all the Groups the given file is a part of. - * - * @param fileID The Id of the file to get group keys for. - * - * @return A set of GroupKeys representing the group(s) the given file is a - * part of - */ - synchronized public Set> getGroupKeysForFileID(Long fileID) { - try { - DrawableDB db = getDB(); - if (nonNull(db)) { - DrawableFile file = db.getFileFromID(fileID); - return getGroupKeysForFile(file); - } else { - Logger.getLogger(GroupManager.class.getName()).log(Level.WARNING, "Failed to load file with id: {0} from database. There is no database assigned.", fileID); //NON-NLS - } - } catch (TskCoreException ex) { - Logger.getLogger(GroupManager.class.getName()).log(Level.SEVERE, "failed to load file with id: " + fileID + " from database", ex); //NON-NLS - } - return Collections.emptySet(); - } - - /** - * @param groupKey - * - * @return return the DrawableGroup (if it exists) for the given GroupKey, - * or null if no group exists for that key. - */ - @Nullable - synchronized public DrawableGroup getGroupForKey(@Nonnull GroupKey groupKey) { - return groupMap.get(groupKey); - } - - synchronized public void reset() { - if (groupByTask != null) { - groupByTask.cancel(true); - } - setSortBy(GroupSortBy.GROUP_BY_VALUE); - setGroupBy(DrawableAttribute.PATH); - setSortOrder(SortOrder.ASCENDING); - setDataSource(null); - - unSeenGroups.forEach(controller.getCategoryManager()::unregisterListener); - unSeenGroups.clear(); - analyzedGroups.forEach(controller.getCategoryManager()::unregisterListener); - analyzedGroups.clear(); - - groupMap.values().forEach(controller.getCategoryManager()::unregisterListener); - groupMap.clear(); - } - - synchronized public boolean isRegrouping() { - if (groupByTask == null) { - return false; - } - - switch (groupByTask.getState()) { - case READY: - case RUNNING: - case SCHEDULED: - return true; - case CANCELLED: - case FAILED: - case SUCCEEDED: - default: - return false; - } - } - - /** - * 'Save' the given group as seen in the drawable db. - * - * @param group The DrawableGroup to mark as seen. - * @param seen The seen state to set for the given group. - * - * @return A ListenableFuture that encapsulates saving the seen state to the - * DB. - */ - synchronized public ListenableFuture setGroupSeen(DrawableGroup group, boolean seen) { - DrawableDB db = getDB(); - if (nonNull(db)) { - return exec.submit(() -> { - try { - - db.setGroupSeen(group.getGroupKey(), seen); - group.setSeen(seen); - updateUnSeenGroups(group, seen); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS - } - }); - } - - return Futures.immediateFuture(null); - } - - synchronized private void updateUnSeenGroups(DrawableGroup group, boolean seen) { - if (seen) { - unSeenGroups.removeAll(group); - } else if (unSeenGroups.contains(group) == false) { - unSeenGroups.add(group); - } - sortUnseenGroups(); - } - - /** - * remove the given file from the group with the given key. If the group - * doesn't exist or doesn't already contain this file, this method is a - * no-op - * - * @param groupKey the value of groupKey - * @param fileID the value of file - * - * @return The DrawableGroup the file was removed from. - * - */ - public synchronized DrawableGroup removeFromGroup(GroupKey groupKey, final Long fileID) { - //get grouping this file would be in - final DrawableGroup group = getGroupForKey(groupKey); - if (group != null) { - synchronized (group) { - group.removeFile(fileID); - - // If we're grouping by category, we don't want to remove empty groups. - if (groupKey.getAttribute() != DrawableAttribute.CATEGORY) { - if (group.getFileIDs().isEmpty()) { - if (analyzedGroups.contains(group)) { - analyzedGroups.remove(group); - sortAnalyzedGroups(); - } - if (unSeenGroups.contains(group)) { - unSeenGroups.remove(group); - sortUnseenGroups(); - } - - } - } - return group; - } - } else { //group == null - // It may be that this was the last unanalyzed file in the group, so test - // whether the group is now fully analyzed. - return popuplateIfAnalyzed(groupKey, null); - } - } - - synchronized private void sortUnseenGroups() { - FXCollections.sort(unSeenGroups, makeGroupComparator(getSortOrder(), getSortBy())); - } - - synchronized private void sortAnalyzedGroups() { - FXCollections.sort(analyzedGroups, makeGroupComparator(getSortOrder(), getSortBy())); - } - - synchronized public Set getFileIDsInGroup(GroupKey groupKey) throws TskCoreException { - Set fileIDsToReturn = Collections.emptySet(); - switch (groupKey.getAttribute().attrName) { - //these cases get special treatment - case CATEGORY: - fileIDsToReturn = getFileIDsWithCategory((DhsImageCategory) groupKey.getValue()); - break; - case TAGS: - fileIDsToReturn = getFileIDsWithTag((TagName) groupKey.getValue()); - break; - case MIME_TYPE: - fileIDsToReturn = getFileIDsWithMimeType((String) groupKey.getValue()); - break; -// case HASHSET: //comment out this case to use db functionality for hashsets -// return getFileIDsWithHashSetName((String) groupKey.getValue()); - default: - DrawableDB db = getDB(); - //straight db query - if (nonNull(db)) { - fileIDsToReturn = db.getFileIDsInGroup(groupKey); - } - } - return fileIDsToReturn; - } - - // @@@ This was kind of slow in the profiler. Maybe we should cache it. - // Unless the list of file IDs is necessary, use countFilesWithCategory() to get the counts. - synchronized public Set getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException { - Set fileIDsToReturn = Collections.emptySet(); - DrawableDB db = getDB(); - if (nonNull(db)) { - try { - final DrawableTagsManager tagsManager = controller.getTagsManager(); - if (category == DhsImageCategory.ZERO) { - List< TagName> tns = Stream.of(DhsImageCategory.ONE, DhsImageCategory.TWO, - DhsImageCategory.THREE, DhsImageCategory.FOUR, DhsImageCategory.FIVE) - .map(tagsManager::getTagName) - .collect(Collectors.toList()); - - Set files = new HashSet<>(); - for (TagName tn : tns) { - if (tn != null) { - List contentTags = tagsManager.getContentTagsByTagName(tn); - files.addAll(contentTags.stream() - .filter(ct -> ct.getContent() instanceof AbstractFile) - .filter(ct -> db.isInDB(ct.getContent().getId())) - .map(ct -> ct.getContent().getId()) - .collect(Collectors.toSet())); - } - } - - fileIDsToReturn = db.findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(files, ',') + ")"); //NON-NLS - } else { - - List contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category)); - fileIDsToReturn = contentTags.stream() - .filter(ct -> ct.getContent() instanceof AbstractFile) - .filter(ct -> db.isInDB(ct.getContent().getId())) - .map(ct -> ct.getContent().getId()) - .collect(Collectors.toSet()); - } - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS - throw ex; - } - } - return fileIDsToReturn; - } - - synchronized public Set getFileIDsWithTag(TagName tagName) throws TskCoreException { - Set files = new HashSet<>(); - try { - - List contentTags = controller.getTagsManager().getContentTagsByTagName(tagName); - DrawableDB db = getDB(); - for (ContentTag ct : contentTags) { - if (ct.getContent() instanceof AbstractFile && nonNull(db) && db.isInDB(ct.getContent().getId())) { - files.add(ct.getContent().getId()); - } - } - return files; - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "TSK error getting files with Tag:" + tagName.getDisplayName(), ex); //NON-NLS - throw ex; - } - } - - public synchronized GroupSortBy getSortBy() { - return sortByProp.get(); - } - - synchronized void setSortBy(GroupSortBy sortBy) { - sortByProp.set(sortBy); - } - - public ReadOnlyObjectProperty< GroupSortBy> getSortByProperty() { - return sortByProp.getReadOnlyProperty(); - } - - public synchronized DrawableAttribute getGroupBy() { - return groupByProp.get(); - } - - synchronized void setGroupBy(DrawableAttribute groupBy) { - groupByProp.set(groupBy); - } - - public ReadOnlyObjectProperty> getGroupByProperty() { - return groupByProp.getReadOnlyProperty(); - } - - public synchronized SortOrder getSortOrder() { - return sortOrderProp.get(); - } - - synchronized void setSortOrder(SortOrder sortOrder) { - sortOrderProp.set(sortOrder); - } - - public ReadOnlyObjectProperty getSortOrderProperty() { - return sortOrderProp.getReadOnlyProperty(); - } - - public synchronized DataSource getDataSource() { - return dataSourceProp.get(); - } - - synchronized void setDataSource(DataSource dataSource) { - dataSourceProp.set(dataSource); - } - - public ReadOnlyObjectProperty getDataSourceProperty() { - return dataSourceProp.getReadOnlyProperty(); - } - - /** - * Regroup all files in the database. see ReGroupTask for more details. - * - * @param The type of the values of the groupBy attriubte. - * @param dataSource The DataSource to show. Null for all data sources. - * @param groupBy The DrawableAttribute to group by - * @param sortBy The GroupSortBy to sort the groups by - * @param sortOrder The SortOrder to use when sorting the groups. - * @param force true to force a full db query regroup, even if only the - * sorting has changed. - */ - public synchronized > void regroup(DataSource dataSource, DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder, Boolean force) { - - if (!Case.isCaseOpen()) { - return; - } - - //only re-query the db if the data source or group by attribute changed or it is forced - if (dataSource != getDataSource() - || groupBy != getGroupBy() - || force) { - - setDataSource(dataSource); - setGroupBy(groupBy); - setSortBy(sortBy); - setSortOrder(sortOrder); - if (groupByTask != null) { - groupByTask.cancel(true); - } - - groupByTask = new ReGroupTask<>(dataSource, groupBy, sortBy, sortOrder); - Platform.runLater(() -> regroupProgress.bind(groupByTask.progressProperty())); - exec.submit(groupByTask); - } else { - // resort the list of groups - setSortBy(sortBy); - setSortOrder(sortOrder); - Platform.runLater(() -> { - FXCollections.sort(analyzedGroups, makeGroupComparator(sortOrder, sortBy)); - FXCollections.sort(unSeenGroups, makeGroupComparator(sortOrder, sortBy)); - }); - } - } - - public ReadOnlyDoubleProperty regroupProgress() { - return regroupProgress.getReadOnlyProperty(); - } - - @Subscribe - synchronized public void handleTagAdded(ContentTagAddedEvent evt) { - GroupKey newGroupKey = null; - final long fileID = evt.getAddedTag().getContent().getId(); - if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(evt.getAddedTag().getName())) { - newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(evt.getAddedTag().getName()), getDataSource()); - for (GroupKey oldGroupKey : groupMap.keySet()) { - if (oldGroupKey.equals(newGroupKey) == false) { - removeFromGroup(oldGroupKey, fileID); - } - } - } else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(evt.getAddedTag().getName())) { - newGroupKey = new GroupKey<>(DrawableAttribute.TAGS, evt.getAddedTag().getName(), getDataSource()); - } - if (newGroupKey != null) { - DrawableGroup g = getGroupForKey(newGroupKey); - addFileToGroup(g, newGroupKey, fileID); - } - } - - @SuppressWarnings("AssignmentToMethodParameter") - synchronized private void addFileToGroup(DrawableGroup g, final GroupKey groupKey, final long fileID) { - if (g == null) { - //if there wasn't already a group check if there should be one now - g = popuplateIfAnalyzed(groupKey, null); - } - DrawableGroup group = g; - if (group != null) { - //if there is aleady a group that was previously deemed fully analyzed, then add this newly analyzed file to it. - group.addFile(fileID); - } - } - - @Subscribe - synchronized public void handleTagDeleted(ContentTagDeletedEvent evt) { - GroupKey groupKey = null; - final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo(); - final TagName tagName = deletedTagInfo.getName(); - if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(tagName)) { - groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(tagName), getDataSource()); - } else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(tagName)) { - groupKey = new GroupKey<>(DrawableAttribute.TAGS, tagName, getDataSource()); - } - if (groupKey != null) { - final long fileID = deletedTagInfo.getContentID(); - DrawableGroup g = removeFromGroup(groupKey, fileID); - } - } - - @Subscribe - synchronized public void handleFileRemoved(Collection removedFileIDs) { - - for (final long fileId : removedFileIDs) { - //get grouping(s) this file would be in - Set> groupsForFile = getGroupKeysForFileID(fileId); - - for (GroupKey gk : groupsForFile) { - removeFromGroup(gk, fileId); - } - } - } - - /** - * Handle notifications sent from Db when files are inserted/updated - * - * @param updatedFileIDs The ID of the inserted/updated files. - */ - @Subscribe - synchronized public void handleFileUpdate(Collection updatedFileIDs) { - /** - * TODO: is there a way to optimize this to avoid quering to db so much. - * the problem is that as a new files are analyzed they might be in new - * groups( if we are grouping by say make or model) -jm - */ - for (long fileId : updatedFileIDs) { - - controller.getHashSetManager().invalidateHashSetsForFile(fileId); - - //get grouping(s) this file would be in - Set> groupsForFile = getGroupKeysForFileID(fileId); - for (GroupKey gk : groupsForFile) { - DrawableGroup g = getGroupForKey(gk); - addFileToGroup(g, gk, fileId); - } - } - - //we fire this event for all files so that the category counts get updated during initial db population - controller.getCategoryManager().fireChange(updatedFileIDs, null); - } - - synchronized private DrawableGroup popuplateIfAnalyzed(GroupKey groupKey, ReGroupTask task) { - /* - * If this method call is part of a ReGroupTask and that task is - * cancelled, no-op. - * - * This allows us to stop if a regroup task has been cancelled (e.g. the - * user picked a different group by attribute, while the current task - * was still running) - */ - if (isNull(task) || task.isCancelled() == false) { - DrawableDB db = getDB(); - /* - * For attributes other than path we can't be sure a group is fully - * analyzed because we don't know all the files that will be a part - * of that group. just show them no matter what. - */ - if (nonNull(db) && ((groupKey.getAttribute() != DrawableAttribute.PATH) || db.isGroupAnalyzed(groupKey))) { - try { - Set fileIDs = getFileIDsInGroup(groupKey); - if (Objects.nonNull(fileIDs)) { - DrawableGroup group; - final boolean groupSeen = db.isGroupSeen(groupKey); - if (groupMap.containsKey(groupKey)) { - group = groupMap.get(groupKey); - group.setFiles(ObjectUtils.defaultIfNull(fileIDs, Collections.emptySet())); - group.setSeen(groupSeen); - } else { - group = new DrawableGroup(groupKey, fileIDs, groupSeen); - controller.getCategoryManager().registerListener(group); - groupMap.put(groupKey, group); - } - - if (analyzedGroups.contains(group) == false) { - analyzedGroups.add(group); - if (isNull(task)) { - sortAnalyzedGroups(); - } - } - updateUnSeenGroups(group, groupSeen); - - return group; - - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex); //NON-NLS - } - } - } - - return null; - } - - synchronized public Set getFileIDsWithMimeType(String mimeType) throws TskCoreException { - - HashSet hashSet = new HashSet<>(); - String query = (null == mimeType) - ? "SELECT obj_id FROM tsk_files WHERE mime_type IS NULL" //NON-NLS - : "SELECT obj_id FROM tsk_files WHERE mime_type = '" + mimeType + "'"; //NON-NLS - DrawableDB db = getDB(); - try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); - ResultSet resultSet = executeQuery.getResultSet();) { - while (resultSet.next()) { - final long fileID = resultSet.getLong("obj_id"); //NON-NLS - if (nonNull(db) && db.isInDB(fileID)) { - hashSet.add(fileID); - } - } - return hashSet; - - } catch (Exception ex) { - throw new TskCoreException("Failed to get file ids with mime type " + mimeType, ex); - } - } - - /** - * Task to query database for files in sorted groups and build - * DrawableGroups for them. - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - @NbBundle.Messages({"# {0} - groupBy attribute Name", - "# {1} - sortBy name", - "# {2} - sort Order", - "ReGroupTask.displayTitle=regrouping files by {0} sorted by {1} in {2} order", - "# {0} - groupBy attribute Name", - "# {1} - atribute value", - "ReGroupTask.progressUpdate=regrouping files by {0} : {1}"}) - private class ReGroupTask> extends LoggedTask { - - private final DataSource dataSource; - private final DrawableAttribute groupBy; - private final GroupSortBy sortBy; - private final SortOrder sortOrder; - - private final ProgressHandle groupProgress; - - ReGroupTask(DataSource dataSource, DrawableAttribute groupBy, GroupSortBy sortBy, SortOrder sortOrder) { - super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), true); - this.dataSource = dataSource; - this.groupBy = groupBy; - this.sortBy = sortBy; - this.sortOrder = sortOrder; - - groupProgress = ProgressHandle.createHandle(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), this); - } - - @Override - public boolean isCancelled() { - return super.isCancelled(); - } - - @Override - protected Void call() throws Exception { - - if (isCancelled()) { - return null; - } - groupProgress.start(); - - synchronized (GroupManager.this) { - analyzedGroups.clear(); - unSeenGroups.clear(); - - // Get the list of group keys - final Multimap valsByDataSource = findValuesForAttribute(); - groupProgress.switchToDeterminate(valsByDataSource.size()); - int p = 0; - // For each key value, partially create the group and add it to the list. - for (final Map.Entry val : valsByDataSource.entries()) { - if (isCancelled()) { - return null; - } - p++; - updateMessage(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val.getValue())); - updateProgress(p, valsByDataSource.size()); - groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); - popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this); - } - } - - DataSource dataSourceOfCurrentGroup - = Optional.ofNullable(controller.getViewState()) - .flatMap(GroupViewState::getGroup) - .map(DrawableGroup::getGroupKey) - .flatMap(GroupKey::getDataSource) - .orElse(null); - if (getDataSource() == null - || Objects.equals(dataSourceOfCurrentGroup, getDataSource())) { - //the current group is for the given datasource, so just keep it in view. - } else { //the current group should not be visible so ... - if (isNotEmpty(unSeenGroups)) {// show then next unseen group - controller.advance(GroupViewState.tile(unSeenGroups.get(0)), false); - } else { // clear the group area. - controller.advance(GroupViewState.tile(null), false); - } - } - - groupProgress.finish(); - updateProgress(1, 1); - return null; - } - - @Override - protected void done() { - super.done(); - try { - get(); - } catch (CancellationException cancelEx) { - //cancellation is normal - } catch (InterruptedException | ExecutionException ex) { - logger.log(Level.SEVERE, "Error while regrouping.", ex); - } - } - - /** - * find the distinct values for the given column (DrawableAttribute) - * - * These values represent the groups of files. - * - * @param groupBy - * - * @return - */ - public Multimap findValuesForAttribute() { - synchronized (GroupManager.this) { - DrawableDB db = getDB(); - Multimap results = HashMultimap.create(); - try { - switch (groupBy.attrName) { - //these cases get special treatment - case CATEGORY: - results.putAll(null, Arrays.asList(DhsImageCategory.values())); - break; - case TAGS: - results.putAll(null, controller.getTagsManager().getTagNamesInUse().stream() - .filter(CategoryManager::isNotCategoryTagName) - .collect(Collectors.toList())); - break; - - case ANALYZED: - results.putAll(null, Arrays.asList(false, true)); - break; - case HASHSET: - if (nonNull(db)) { - results.putAll(null, new TreeSet<>(db.getHashSetNames())); - } - break; - case MIME_TYPE: - if (nonNull(db)) { - HashSet types = new HashSet<>(); - - // Use the group_concat function to get a list of files for each mime type. - // This has different syntax on Postgres vs SQLite - String groupConcatClause; - if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) { - groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids"; - } else { - groupConcatClause = " group_concat(obj_id) as object_ids"; - } - String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type "; - try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS - ResultSet resultSet = executeQuery.getResultSet();) { - while (resultSet.next()) { - final String mimeType = resultSet.getString("mime_type"); //NON-NLS - String objIds = resultSet.getString("object_ids"); //NON-NLS - - Pattern.compile(",").splitAsStream(objIds) - .map(Long::valueOf) - .filter(db::isInDB) - .findAny().ifPresent(obj_id -> types.add(mimeType)); - } - } catch (SQLException | TskCoreException ex) { - Exceptions.printStackTrace(ex); - } - results.putAll(null, types); - } - break; - default: - //otherwise do straight db query - if (nonNull(db)) { - results.putAll(db.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource)); - } - } - - } catch (TskCoreException | TskDataException ex) { - logger.log(Level.SEVERE, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS - } - return results; - } - } - } - - private static Comparator makeGroupComparator(final SortOrder sortOrder, GroupSortBy comparator) { - switch (sortOrder) { - case ASCENDING: - return comparator; - case DESCENDING: - return comparator.reversed(); - case UNSORTED: - default: - return new GroupSortBy.AllEqualComparator<>(); - } - } -} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml index fc286f3c7b..0e788327c4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.fxml @@ -64,7 +64,7 @@ -