From eb9f4750ade314f9bdd062aaab7ff10796ba7635 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 9 Jun 2015 13:07:13 -0400 Subject: [PATCH 1/6] refactor CategoryCounts --- .../actions/CategorizeAction.java | 4 +- .../imagegallery/datamodel/DrawableDB.java | 107 +++++------------- .../imagegallery/grouping/CategoryCache.java | 91 +++++++++++++++ .../imagegallery/grouping/GroupManager.java | 20 +++- .../imagegallery/gui/SummaryTablePane.java | 12 +- 5 files changed, 147 insertions(+), 87 deletions(-) create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/CategoryCache.java diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java index 3f66eda4a5..542d9a243f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java @@ -135,11 +135,11 @@ public class CategorizeAction extends AddTagAction { //TODO: abandon using tags for categories and instead add a new column to DrawableDB if (ct.getName().getDisplayName().startsWith(Category.CATEGORY_PREFIX)) { Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(ct); //tsk db - controller.getDatabase().decrementCategoryCount(Category.fromDisplayName(ct.getName().getDisplayName())); //memory/drawable db + controller.getGroupManager().decrementCategoryCount(Category.fromDisplayName(ct.getName().getDisplayName())); //memory/drawable db } } - controller.getDatabase().incrementCategoryCount(Category.fromDisplayName(tagName.getDisplayName())); //memory/drawable db + controller.getGroupManager().incrementCategoryCount(Category.fromDisplayName(tagName.getDisplayName())); //memory/drawable db if (tagName != Category.ZERO.getTagName()) { // no tags for cat-0 Case.getCurrentCase().getServices().getTagsManager().addContentTag(file, tagName, comment); //tsk db } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index e75d5fa0c4..6a36d48b5c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -35,6 +35,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; @@ -54,6 +55,7 @@ import static org.sleuthkit.autopsy.imagegallery.grouping.GroupSortBy.GROUP_BY_V import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TagName; @@ -1166,16 +1168,7 @@ public class DrawableDB { public void updateHashSetsForFile(Long id) { try { - List arts = ImageGalleryController.getDefault().getSleuthKitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, id); - Set hashNames = new HashSet<>(); - for (BlackboardArtifact a : arts) { - List attrs = a.getAttributes(); - for (BlackboardAttribute attr : attrs) { - if (attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) { - hashNames.add(attr.getValueString()); - } - } - } + Set hashNames = getHashSetsForFileHelper(id); hashSetMap.put(id, hashNames); } catch (IllegalStateException | TskCoreException ex) { LOGGER.log(Level.WARNING, "could not access case during updateHashSetsForFile()", ex); @@ -1183,6 +1176,20 @@ public class DrawableDB { } } + private Set getHashSetsForFileHelper(Long id) throws TskCoreException, IllegalStateException { + List arts = ImageGalleryController.getDefault().getSleuthKitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, id); + Set hashNames = new HashSet<>(); + for (BlackboardArtifact a : arts) { + List attrs = a.getAttributes(); + for (BlackboardAttribute attr : attrs) { + if (attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) { + hashNames.add(attr.getValueString()); + } + } + } + return hashNames; + } + /** * For performance reasons, keep a list of all file IDs currently in the * drawable database. @@ -1224,7 +1231,6 @@ public class DrawableDB { while (analyzedQuery.next()) { addImageFileToList(analyzedQuery.getLong(OBJ_ID)); } - } catch (SQLException ex) { LOGGER.log(Level.WARNING, "problem loading file IDs: ", ex); } finally { @@ -1233,80 +1239,29 @@ public class DrawableDB { } } - /** - * For performance reasons, keep current category counts in memory - */ - @GuardedBy("categoryCounts") - private final Map categoryCounts = new HashMap<>(); + public long getCategoryCount(Category t) { + try { + return Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(t.getTagName()).stream() + .map(ContentTag::getContent) + .map(Content::getId) + .filter(this::isDrawableFile) + .count(); - public void incrementCategoryCount(Category cat) throws TskCoreException { - if (cat != Category.ZERO) { - synchronized (categoryCounts) { - int count = getCategoryCount(cat); - count++; - categoryCounts.put(cat, count); - } - } - } - - public void decrementCategoryCount(Category cat) throws TskCoreException { - if (cat != Category.ZERO) { - synchronized (categoryCounts) { - int count = getCategoryCount(cat); - count--; - categoryCounts.put(cat, count); - } - } - } - - public int getCategoryCount(Category cat) throws TskCoreException { - synchronized (categoryCounts) { - if (cat == Category.ZERO) { - // Keeping track of the uncategorized files is a bit tricky while ingest - // 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. - int allOtherCatCount = getCategoryCount(Category.ONE) + getCategoryCount(Category.TWO) + getCategoryCount(Category.THREE) - + getCategoryCount(Category.FOUR) + getCategoryCount(Category.FIVE); - return getNumberOfImageFilesInList() - allOtherCatCount; - } else if (categoryCounts.containsKey(cat)) { - return categoryCounts.get(cat); - } else { - try { - int fileCount = 0; - List contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(cat.getTagName()); - for (ContentTag ct : contentTags) { - if (ct.getContent() instanceof AbstractFile) { - AbstractFile f = (AbstractFile) ct.getContent(); - if (this.isDrawableFile(f.getId())) { - fileCount++; - } - } - } - categoryCounts.put(cat, fileCount); - return fileCount; - } catch (IllegalStateException ex) { - throw new TskCoreException("Case closed while getting files"); - } - } + } catch (IllegalStateException ex) { + LOGGER.log(Level.WARNING, "Case closed while getting files"); + } catch (TskCoreException ex1) { + LOGGER.log(Level.SEVERE, "Failed to get content tags by tag name.", ex1); } + return -1; } /** * For performance reasons, keep the file type in memory */ - @GuardedBy("videoFileMap") - private final Map videoFileMap = new HashMap<>(); + private final Map videoFileMap = new ConcurrentHashMap<>(); public boolean isVideoFile(AbstractFile f) throws TskCoreException { - synchronized (videoFileMap) { - if (videoFileMap.containsKey(f.getId())) { - return videoFileMap.get(f.getId()); - } - - boolean isVideo = ImageGalleryModule.isVideoFile(f); - videoFileMap.put(f.getId(), isVideo); - return isVideo; - } + return videoFileMap.computeIfAbsent(f, ImageGalleryModule::isVideoFile); } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/CategoryCache.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/CategoryCache.java new file mode 100644 index 0000000000..6c47ca597d --- /dev/null +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/CategoryCache.java @@ -0,0 +1,91 @@ +package org.sleuthkit.autopsy.imagegallery.grouping; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.LongAdder; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.imagegallery.datamodel.Category; +import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + */ +class CategoryCache { + + private static final java.util.logging.Logger LOGGER = Logger.getLogger(CategoryCache.class.getName()); + private final DrawableDB db; + + public CategoryCache(DrawableDB db) { + this.db = db; + } + + /** + * For performance reasons, keep current category counts in memory + */ + private final ConcurrentHashMap categoryCounts = new ConcurrentHashMap<>(); + + /** + * + * @param cat the value of cat + * @param drawableDB the value of drawableDB + * + * @return the long + * + * @throws TskCoreException + */ + public long getCategoryCount(Category cat) throws TskCoreException { + if (cat == Category.ZERO) { + // Keeping track of the uncategorized files is a bit tricky while ingest + // 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(Category.ONE) + getCategoryCount(Category.TWO) + getCategoryCount(Category.THREE) + getCategoryCount(Category.FOUR) + getCategoryCount(Category.FIVE); + return db.getNumberOfImageFilesInList() - allOtherCatCount; + } else { + return categoryCounts.computeIfAbsent(cat, this::getCategoryCountHelper).sum(); + } + } + + /** + * + * @param cat the value of cat + * @param drawableDB the value of drawableDB + * + * @throws TskCoreException + */ + public void incrementCategoryCount(Category cat) { + if (cat != Category.ZERO) { + categoryCounts.computeIfAbsent(cat, this::getCategoryCountHelper).increment(); + } + } + + /** + * + * @param cat the value of cat + * @param drawableDB the value of drawableDB + * + * @throws TskCoreException + */ + public void decrementCategoryCount(Category cat) { + if (cat != Category.ZERO) { + categoryCounts.computeIfAbsent(cat, this::getCategoryCountHelper).decrement(); + } + } + + /** + * + * @param t the value of t + * @param drawableDB the value of drawableDB + */ + private LongAdder getCategoryCountHelper(Category t) { + LongAdder longAdder = new LongAdder(); + longAdder.decrement(); + try { + longAdder.add(db.getCategoryCount(t)); + longAdder.increment(); + } catch (IllegalStateException ex) { + LOGGER.log(Level.WARNING, "Case closed while getting files"); + } + return longAdder; + } +} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java index 84f79dcfe4..d08874bd29 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java @@ -78,7 +78,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { private DrawableDB db; private final ImageGalleryController controller; - + private CategoryCache categoryCache; /** * map from {@link GroupKey}s to {@link DrawableGroup}s. All groups (even * not @@ -138,6 +138,20 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { } + public void incrementCategoryCount(Category cat) { + if (categoryCache == null) { + categoryCache = new CategoryCache(db); + } + categoryCache.incrementCategoryCount(cat); + } + + public void decrementCategoryCount(Category cat) { + if (categoryCache == null) { + categoryCache = new CategoryCache(db); + } + categoryCache.decrementCategoryCount(cat); + } + /** * using the current groupBy set for this manager, find groupkeys for all * the groups the given file is a part of @@ -527,8 +541,8 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { * * @throws TskCoreException */ - public int countFilesWithCategory(Category category) throws TskCoreException { - return db.getCategoryCount(category); + public long countFilesWithCategory(Category category) throws TskCoreException { + return categoryCache.getCategoryCount(category); } public List getFileIDsWithTag(TagName tagName) throws TskCoreException { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java index 1e78e8945e..b2fd5fe076 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java @@ -48,13 +48,13 @@ public class SummaryTablePane extends AnchorPane implements Category.CategoryLis private static SummaryTablePane instance; @FXML - private TableColumn, String> catColumn; + private TableColumn, String> catColumn; @FXML - private TableColumn, Integer> countColumn; + private TableColumn, Long> countColumn; @FXML - private TableView> tableView; + private TableView> tableView; @FXML void initialize() { @@ -68,10 +68,10 @@ public class SummaryTablePane extends AnchorPane implements Category.CategoryLis tableView.prefHeightProperty().set(7 * 25); //set up columns - catColumn.setCellValueFactory((TableColumn.CellDataFeatures, String> p) -> new SimpleObjectProperty<>(p.getValue().getKey().getDisplayName())); + catColumn.setCellValueFactory((TableColumn.CellDataFeatures, String> p) -> new SimpleObjectProperty<>(p.getValue().getKey().getDisplayName())); catColumn.setPrefWidth(USE_COMPUTED_SIZE); - countColumn.setCellValueFactory((TableColumn.CellDataFeatures, Integer> p) -> new SimpleObjectProperty<>(p.getValue().getValue())); + countColumn.setCellValueFactory((TableColumn.CellDataFeatures, Long> p) -> new SimpleObjectProperty<>(p.getValue().getValue())); countColumn.setPrefWidth(USE_COMPUTED_SIZE); tableView.getColumns().setAll(Arrays.asList(catColumn, countColumn)); @@ -96,7 +96,7 @@ public class SummaryTablePane extends AnchorPane implements Category.CategoryLis */ @Override public void handleCategoryChanged(Collection ids) { - final ObservableList> data = FXCollections.observableArrayList(); + final ObservableList> data = FXCollections.observableArrayList(); if (Case.isCaseOpen()) { for (Category cat : Category.values()) { try { From 06e0a33499d14d4395692044d24fd30727bdd678 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 9 Jun 2015 13:45:02 -0400 Subject: [PATCH 2/6] interim refactor of towards HashSetManager --- .../imagegallery/ImageGalleryController.java | 226 ++++++++++-------- .../actions/CategorizeAction.java | 4 +- .../imagegallery/datamodel/Category.java | 36 +-- .../CategoryCache.java | 61 ++++- .../imagegallery/datamodel/DrawableDB.java | 57 +---- .../imagegallery/datamodel/DrawableFile.java | 16 +- .../datamodel/HashSetManager.java | 36 +++ .../imagegallery/grouping/DrawableGroup.java | 23 +- .../imagegallery/grouping/GroupManager.java | 36 +-- .../imagegallery/gui/DrawableTile.java | 4 +- .../imagegallery/gui/DrawableView.java | 11 +- .../imagegallery/gui/MetaDataPane.java | 5 +- .../gui/SingleDrawableViewBase.java | 5 +- .../imagegallery/gui/SlideShowView.java | 7 +- .../imagegallery/gui/SummaryTablePane.java | 7 +- .../gui/navpanel/TreeNodeComparators.java | 43 ++-- ...eanalyzer.txt => license-imagegallery.txt} | 2 +- 17 files changed, 287 insertions(+), 292 deletions(-) rename ImageGallery/src/org/sleuthkit/autopsy/imagegallery/{grouping => datamodel}/CategoryCache.java (57%) create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java rename ImageGallery/src/org/sleuthkit/autopsy/imagegallery/{license-imageanalyzer.txt => license-imagegallery.txt} (93%) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 8e60aefbf1..334b3f9a6c 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -58,8 +58,10 @@ import org.sleuthkit.autopsy.coreutils.History; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; +import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; +import org.sleuthkit.autopsy.imagegallery.datamodel.HashSetManager; import org.sleuthkit.autopsy.imagegallery.grouping.GroupManager; import org.sleuthkit.autopsy.imagegallery.grouping.GroupViewState; import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog; @@ -81,25 +83,25 @@ import org.sleuthkit.datamodel.TskData; * control. */ public final class ImageGalleryController { - + private static final Logger LOGGER = Logger.getLogger(ImageGalleryController.class.getName()); - + private final Region infoOverLayBackground = new Region() { { setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY))); setOpacity(.4); } }; - + private static ImageGalleryController instance; - + public static synchronized ImageGalleryController getDefault() { if (instance == null) { instance = new ImageGalleryController(); } return instance; } - + private final History historyManager = new History<>(); /** @@ -107,71 +109,73 @@ public final class ImageGalleryController { * not listen to speed up ingest */ private final SimpleBooleanProperty listeningEnabled = new SimpleBooleanProperty(false); - + private final ReadOnlyIntegerWrapper queueSizeProperty = new ReadOnlyIntegerWrapper(0); - + private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false); - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private final ReadOnlyBooleanWrapper stale = new ReadOnlyBooleanWrapper(false); - + private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false); - + private final FileIDSelectionModel selectionModel = FileIDSelectionModel.getInstance(); - + private DBWorkerThread dbWorkerThread; - + private DrawableDB db; - + private final GroupManager groupManager = new GroupManager(this); - + private StackPane fullUIStackPane; - + private StackPane centralStackPane; - + private Node infoOverlay; - + private final HashSetManager hashSetManager = new HashSetManager(); + private final CategoryManager categoryManager = new CategoryManager(); + public ReadOnlyBooleanProperty getMetaDataCollapsed() { return metaDataCollapsed.getReadOnlyProperty(); } - + public void setMetaDataCollapsed(Boolean metaDataCollapsed) { this.metaDataCollapsed.set(metaDataCollapsed); } - + private GroupViewState getViewState() { return historyManager.getCurrentState(); } - + public ReadOnlyBooleanProperty regroupDisabled() { return regroupDisabled.getReadOnlyProperty(); } - + public ReadOnlyObjectProperty viewState() { return historyManager.currentState(); } - + public synchronized FileIDSelectionModel getSelectionModel() { - + return selectionModel; } - + public GroupManager getGroupManager() { return groupManager; } - + public DrawableDB getDatabase() { return db; } - + synchronized public void setListeningEnabled(boolean enabled) { listeningEnabled.set(enabled); } - + synchronized boolean isListeningEnabled() { return listeningEnabled.get(); } - + @ThreadConfined(type = ThreadConfined.ThreadType.ANY) void setStale(Boolean b) { Platform.runLater(() -> { @@ -181,18 +185,18 @@ public final class ImageGalleryController { new PerCaseProperties(Case.getCurrentCase()).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.STALE, b.toString()); } } - + public ReadOnlyBooleanProperty stale() { return stale.getReadOnlyProperty(); } - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) boolean isStale() { return stale.get(); } - + private ImageGalleryController() { - + listeningEnabled.addListener((observable, oldValue, newValue) -> { //if we just turned on listening and a case is open and that case is not up to date if (newValue && !oldValue && Case.existsCurrentCase() && ImageGalleryModule.isDrawableDBStale(Case.getCurrentCase())) { @@ -200,28 +204,28 @@ public final class ImageGalleryController { queueDBWorkerTask(new CopyAnalyzedFiles()); } }); - + groupManager.getAnalyzedGroups().addListener((Observable o) -> { if (Case.isCaseOpen()) { checkForGroups(); } }); - + 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))); } }); - + viewState().addListener((Observable observable) -> { selectionModel.clearSelection(); }); - + regroupDisabled.addListener((Observable observable) -> { checkForGroups(); }); - + IngestManager.getInstance().addIngestModuleEventListener((PropertyChangeEvent evt) -> { Platform.runLater(this::updateRegroupDisabled); }); @@ -230,27 +234,27 @@ public final class ImageGalleryController { }); // metaDataCollapsed.bind(Toolbar.getDefault().showMetaDataProperty()); } - + public ReadOnlyBooleanProperty getCanAdvance() { return historyManager.getCanAdvance(); } - + public ReadOnlyBooleanProperty getCanRetreat() { return historyManager.getCanRetreat(); } - + public void advance(GroupViewState newState) { historyManager.advance(newState); } - + public GroupViewState advance() { return historyManager.advance(); } - + public GroupViewState retreat() { return historyManager.retreat(); } - + private void updateRegroupDisabled() { regroupDisabled.set(getFileUpdateQueueSizeProperty().get() > 0 || IngestManager.getInstance().isIngestRunning()); } @@ -272,7 +276,7 @@ public final class ImageGalleryController { new NoGroupsDialog("No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.", new ProgressIndicator())); } - + } else if (getFileUpdateQueueSizeProperty().get() > 0) { replaceNotification(fullUIStackPane, new NoGroupsDialog("No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.", @@ -286,19 +290,19 @@ public final class ImageGalleryController { replaceNotification(fullUIStackPane, new NoGroupsDialog("There are no images/videos in the added datasources.")); } - + } else if (!groupManager.isRegrouping()) { replaceNotification(centralStackPane, new NoGroupsDialog("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.")); } - + } else { clearNotification(); } } - + private void clearNotification() { //remove the ingest spinner if (fullUIStackPane != null) { @@ -309,27 +313,27 @@ public final class ImageGalleryController { centralStackPane.getChildren().remove(infoOverlay); } } - + private void replaceNotification(StackPane stackPane, Node newNode) { clearNotification(); - + infoOverlay = new StackPane(infoOverLayBackground, newNode); if (stackPane != null) { stackPane.getChildren().add(infoOverlay); } } - + private void restartWorker() { if (dbWorkerThread != null) { // Keep using the same worker thread if one exists return; } dbWorkerThread = new DBWorkerThread(); - + getFileUpdateQueueSizeProperty().addListener((Observable o) -> { Platform.runLater(this::updateRegroupDisabled); }); - + Thread th = new Thread(dbWorkerThread); th.setDaemon(false); // we want it to go away when it is done th.start(); @@ -342,7 +346,7 @@ public final class ImageGalleryController { */ public synchronized void setCase(Case theNewCase) { this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), this); - + setListeningEnabled(ImageGalleryModule.isEnabledforCase(theNewCase)); setStale(ImageGalleryModule.isDrawableDBStale(theNewCase)); @@ -351,6 +355,8 @@ public final class ImageGalleryController { restartWorker(); historyManager.clear(); groupManager.setDB(db); + hashSetManager.setDb(db); + categoryManager.setDb(db); db.initializeImageList(); SummaryTablePane.getDefault().handleCategoryChanged(Collections.emptyList()); } @@ -367,7 +373,7 @@ public final class ImageGalleryController { historyManager.clear(); }); Category.clearTagNames(); - + Toolbar.getDefault().reset(); groupManager.clear(); if (db != null) { @@ -389,21 +395,21 @@ public final class ImageGalleryController { } dbWorkerThread.addTask(innerTask); } - + public DrawableFile getFileFromId(Long fileID) throws TskCoreException { return db.getFileFromID(fileID); } - + public void setStacks(StackPane fullUIStack, StackPane centralStack) { fullUIStackPane = fullUIStack; this.centralStackPane = centralStack; Platform.runLater(this::checkForGroups); } - + public final ReadOnlyIntegerProperty getFileUpdateQueueSizeProperty() { return queueSizeProperty.getReadOnlyProperty(); } - + public ReadOnlyDoubleProperty regroupProgress() { return groupManager.regroupProgress(); } @@ -475,6 +481,14 @@ public final class ImageGalleryController { } }); } + + public HashSetManager getHashSetManager() { + return hashSetManager; + } + + public CategoryManager getCategoryManager() { + return categoryManager; + } // @@@ REVIEW IF THIS SHOLD BE STATIC... //TODO: concept seems like the controller deal with how much work to do at a given time @@ -512,7 +526,7 @@ public final class ImageGalleryController { queueSizeProperty.set(workQueue.size()); }); } - + @Override public void run() { @@ -523,22 +537,22 @@ public final class ImageGalleryController { } try { InnerTask it = workQueue.take(); - + if (it.cancelled == false) { it.run(); } - + Platform.runLater(() -> { queueSizeProperty.set(workQueue.size()); }); - + } catch (InterruptedException ex) { Exceptions.printStackTrace(ex); } } } } - + public SleuthkitCase getSleuthKitCase() throws IllegalStateException { if (Case.isCaseOpen()) { return Case.getCurrentCase().getSleuthkitCase(); @@ -551,55 +565,55 @@ public final class ImageGalleryController { * Abstract base class for task to be done on {@link DBWorkerThread} */ static public abstract class InnerTask implements Runnable { - + 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); } SimpleObjectProperty state = new SimpleObjectProperty<>(Worker.State.READY); SimpleDoubleProperty progress = new SimpleDoubleProperty(this, "pregress"); SimpleStringProperty message = new SimpleStringProperty(this, "status"); - + public SimpleDoubleProperty progressProperty() { return progress; } - + public SimpleStringProperty messageProperty() { return message; } - + public Worker.State getState() { return state.get(); } - + protected void updateState(Worker.State newState) { state.set(newState); } - + public ReadOnlyObjectProperty stateProperty() { return new ReadOnlyObjectWrapper<>(state.get()); } - + protected InnerTask() { } - + protected volatile boolean cancelled = false; - + public void cancel() { updateState(Worker.State.CANCELLED); } - + protected boolean isCancelled() { return getState() == Worker.State.CANCELLED; } @@ -609,25 +623,25 @@ public final class ImageGalleryController { * Abstract base class for tasks associated with a file in the database */ static public abstract class FileTask extends InnerTask { - + private final AbstractFile file; - + public AbstractFile getFile() { return file; } - + public FileTask(AbstractFile f) { super(); this.file = f; } - + } /** * task that updates one file in database with results from ingest */ private class UpdateFileTask extends FileTask { - + public UpdateFileTask(AbstractFile f) { super(f); } @@ -654,7 +668,7 @@ public final class ImageGalleryController { * task that updates one file in database with results from ingest */ private class RemoveFileTask extends FileTask { - + public RemoveFileTask(AbstractFile f) { super(f); } @@ -673,7 +687,7 @@ public final class ImageGalleryController { Logger.getLogger(RemoveFileTask.class.getName()).log(Level.SEVERE, "Case was closed out from underneath RemoveFile task"); } } - + } } @@ -685,16 +699,16 @@ public final class ImageGalleryController { * adds them to the Drawable DB */ private class CopyAnalyzedFiles extends InnerTask { - + final private String DRAWABLE_QUERY = "name LIKE '%." + StringUtils.join(ImageGalleryModule.getAllSupportedExtensions(), "' or name LIKE '%.") + "'"; - + private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("populating analyzed image/video database"); - + @Override public void run() { progressHandle.start(); updateMessage("populating analyzed image/video database"); - + try { //grab all files with supported extension or detected mime types final List files = getSleuthKitCase().findAllFilesWhere(DRAWABLE_QUERY + " or tsk_files.obj_id in (select tsk_files.obj_id from tsk_files , blackboard_artifacts, blackboard_attributes" @@ -704,7 +718,7 @@ public final class ImageGalleryController { + " and blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG.getTypeID() + " and blackboard_attributes.value_text in ('" + StringUtils.join(ImageGalleryModule.getSupportedMimes(), "','") + "'))"); progressHandle.switchToDeterminate(files.size()); - + updateProgress(0.0); //do in transaction @@ -718,7 +732,7 @@ public final class ImageGalleryController { } final Boolean hasMimeType = ImageGalleryModule.hasSupportedMimeType(f); final boolean known = f.getKnown() == TskData.FileKnown.KNOWN; - + if (known) { db.removeFile(f.getId(), tr); //remove known files } else { @@ -738,38 +752,38 @@ public final class ImageGalleryController { } } } - + units++; final int prog = units; progressHandle.progress(f.getName(), units); updateProgress(prog - 1 / (double) files.size()); updateMessage(f.getName()); } - + progressHandle.finish(); - + progressHandle = ProgressHandleFactory.createHandle("commiting image/video database"); updateMessage("commiting image/video database"); updateProgress(1.0); - + progressHandle.start(); db.commitTransaction(tr, true); - + } catch (TskCoreException ex) { Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex); } catch (IllegalStateException ex) { Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.SEVERE, "Case was closed out from underneath CopyDataSource task", ex); } - + progressHandle.finish(); - + updateMessage( ""); updateProgress( -1.0); setStale(false); } - + } /** @@ -780,7 +794,7 @@ public final class ImageGalleryController { * netbeans and ImageGallery progress/status */ class PrePopulateDataSourceFiles extends InnerTask { - + private final Content dataSource; /** @@ -790,7 +804,7 @@ public final class ImageGalleryController { */ // (name like '.jpg' or name like '.png' ...) private final String DRAWABLE_QUERY = "(name LIKE '%." + StringUtils.join(ImageGalleryModule.getAllSupportedExtensions(), "' or name LIKE '%.") + "') "; - + private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("prepopulating image/video database"); /** @@ -816,7 +830,7 @@ public final class ImageGalleryController { final List files; try { List fsObjIds = new ArrayList<>(); - + String fsQuery; if (dataSource instanceof Image) { Image image = (Image) dataSource; @@ -830,7 +844,7 @@ public final class ImageGalleryController { else { fsQuery = "(fs_obj_id IS NULL) "; } - + files = getSleuthKitCase().findAllFilesWhere(fsQuery + " and " + DRAWABLE_QUERY); progressHandle.switchToDeterminate(files.size()); @@ -848,21 +862,21 @@ public final class ImageGalleryController { final int prog = units; progressHandle.progress(f.getName(), units); } - + progressHandle.finish(); progressHandle = ProgressHandleFactory.createHandle("commiting image/video database"); - + progressHandle.start(); db.commitTransaction(tr, false); - + } catch (TskCoreException ex) { Logger.getLogger(PrePopulateDataSourceFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex); } catch (IllegalStateException | NullPointerException ex) { Logger.getLogger(PrePopulateDataSourceFiles.class.getName()).log(Level.WARNING, "Case was closed out from underneath prepopulating database"); } - + progressHandle.finish(); } } - + } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java index 542d9a243f..69b0844fec 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java @@ -135,11 +135,11 @@ public class CategorizeAction extends AddTagAction { //TODO: abandon using tags for categories and instead add a new column to DrawableDB if (ct.getName().getDisplayName().startsWith(Category.CATEGORY_PREFIX)) { Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(ct); //tsk db - controller.getGroupManager().decrementCategoryCount(Category.fromDisplayName(ct.getName().getDisplayName())); //memory/drawable db + controller.getCategoryManager().decrementCategoryCount(Category.fromDisplayName(ct.getName().getDisplayName())); //memory/drawable db } } - controller.getGroupManager().incrementCategoryCount(Category.fromDisplayName(tagName.getDisplayName())); //memory/drawable db + controller.getCategoryManager().incrementCategoryCount(Category.fromDisplayName(tagName.getDisplayName())); //memory/drawable db if (tagName != Category.ZERO.getTagName()) { // no tags for cat-0 Case.getCurrentCase().getServices().getTagsManager().addContentTag(file, tagName, comment); //tsk db } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java index 030dc5329d..0d2304ac4d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java @@ -19,12 +19,9 @@ package org.sleuthkit.autopsy.imagegallery.datamodel; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.logging.Level; import javafx.event.ActionEvent; import javafx.event.EventHandler; @@ -33,7 +30,6 @@ import javafx.scene.control.SplitMenuButton; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.paint.Color; -import javax.annotation.concurrent.GuardedBy; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.TagUtils; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; @@ -62,31 +58,7 @@ public enum Category implements Comparable { } } - @GuardedBy("listeners") - private final static Set listeners = new HashSet<>(); - - public static void fireChange(Collection ids) { - Set listenersCopy = new HashSet<>(); - synchronized (listeners) { - listenersCopy.addAll(listeners); - } - for (CategoryListener list : listenersCopy) { - list.handleCategoryChanged(ids); - } - - } - - public static void registerListener(CategoryListener aThis) { - synchronized (listeners) { - listeners.add(aThis); - } - } - - public static void unregisterListener(CategoryListener aThis) { - synchronized (listeners) { - listeners.remove(aThis); - } - } + public KeyCode getHotKeycode() { return KeyCode.getKeyCode(Integer.toString(id)); @@ -165,10 +137,4 @@ public enum Category implements Comparable { Category.FOUR.tagName = null; Category.FIVE.tagName = null; } - - public static interface CategoryListener { - - public void handleCategoryChanged(Collection ids); - - } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/CategoryCache.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryCache.java similarity index 57% rename from ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/CategoryCache.java rename to ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryCache.java index 6c47ca597d..d3d1d9baf3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/CategoryCache.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryCache.java @@ -1,29 +1,35 @@ -package org.sleuthkit.autopsy.imagegallery.grouping; +package org.sleuthkit.autopsy.imagegallery.datamodel; -import java.util.concurrent.ConcurrentHashMap; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.atomic.LongAdder; import java.util.logging.Level; +import javax.annotation.concurrent.GuardedBy; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.imagegallery.datamodel.Category; -import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB; import org.sleuthkit.datamodel.TskCoreException; /** * */ -class CategoryCache { +public class CategoryManager { - private static final java.util.logging.Logger LOGGER = Logger.getLogger(CategoryCache.class.getName()); - private final DrawableDB db; + private static final java.util.logging.Logger LOGGER = Logger.getLogger(CategoryManager.class.getName()); + private DrawableDB db; - public CategoryCache(DrawableDB db) { + public void setDb(DrawableDB db) { this.db = db; + categoryCounts.invalidateAll(); + Category.clearTagNames(); } /** * For performance reasons, keep current category counts in memory */ - private final ConcurrentHashMap categoryCounts = new ConcurrentHashMap<>(); + private final LoadingCache categoryCounts = CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper)); /** * @@ -42,7 +48,7 @@ class CategoryCache { long allOtherCatCount = getCategoryCount(Category.ONE) + getCategoryCount(Category.TWO) + getCategoryCount(Category.THREE) + getCategoryCount(Category.FOUR) + getCategoryCount(Category.FIVE); return db.getNumberOfImageFilesInList() - allOtherCatCount; } else { - return categoryCounts.computeIfAbsent(cat, this::getCategoryCountHelper).sum(); + return categoryCounts.getUnchecked(cat).sum(); } } @@ -55,7 +61,7 @@ class CategoryCache { */ public void incrementCategoryCount(Category cat) { if (cat != Category.ZERO) { - categoryCounts.computeIfAbsent(cat, this::getCategoryCountHelper).increment(); + categoryCounts.getUnchecked(cat).increment(); } } @@ -68,7 +74,7 @@ class CategoryCache { */ public void decrementCategoryCount(Category cat) { if (cat != Category.ZERO) { - categoryCounts.computeIfAbsent(cat, this::getCategoryCountHelper).decrement(); + categoryCounts.getUnchecked(cat).decrement(); } } @@ -88,4 +94,35 @@ class CategoryCache { } return longAdder; } + @GuardedBy("listeners") + private final Set listeners = new HashSet<>(); + + public void fireChange(Collection ids) { + Set listenersCopy = new HashSet<>(); + synchronized (listeners) { + listenersCopy.addAll(listeners); + } + for (CategoryListener list : listenersCopy) { + list.handleCategoryChanged(ids); + } + + } + + public void registerListener(CategoryListener aThis) { + synchronized (listeners) { + listeners.add(aThis); + } + } + + public void unregisterListener(CategoryListener aThis) { + synchronized (listeners) { + listeners.remove(aThis); + } + } + + public static interface CategoryListener { + + public void handleCategoryChanged(Collection ids); + + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 6a36d48b5c..e35b67bf1a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -1139,55 +1139,24 @@ public class DrawableDB { * * I don't like having multiple copies of the data, but these were causing * major bottlenecks when they were all database lookups. - * - * TODO: factor these out to seperate classes such as HashSetHitCache or - * CategoryCountCache - * - * TODO: use guava Caches for this instead of lower level HashMaps */ - @GuardedBy("hashSetMap") - private final Map> hashSetMap = new HashMap<>(); - - @GuardedBy("hashSetMap") - public boolean isInHashSet(Long id) { - if (!hashSetMap.containsKey(id)) { - updateHashSetsForFile(id); - } - return (!hashSetMap.get(id).isEmpty()); - } - - @GuardedBy("hashSetMap") - public Set getHashSetsForFile(Long id) { - if (!isInHashSet(id)) { - updateHashSetsForFile(id); - } - return hashSetMap.get(id); - } - - @GuardedBy("hashSetMap") - public void updateHashSetsForFile(Long id) { - + Set getHashSetsForFile(Long id) { try { - Set hashNames = getHashSetsForFileHelper(id); - hashSetMap.put(id, hashNames); - } catch (IllegalStateException | TskCoreException ex) { - LOGGER.log(Level.WARNING, "could not access case during updateHashSetsForFile()", ex); - - } - } - - private Set getHashSetsForFileHelper(Long id) throws TskCoreException, IllegalStateException { - List arts = ImageGalleryController.getDefault().getSleuthKitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, id); - Set hashNames = new HashSet<>(); - for (BlackboardArtifact a : arts) { - List attrs = a.getAttributes(); - for (BlackboardAttribute attr : attrs) { - if (attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) { - hashNames.add(attr.getValueString()); + Set hashNames = new HashSet<>(); + List arts = ImageGalleryController.getDefault().getSleuthKitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, id); + for (BlackboardArtifact a : arts) { + List attrs = a.getAttributes(); + for (BlackboardAttribute attr : attrs) { + if (attr.getAttributeTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) { + hashNames.add(attr.getValueString()); + } } + return hashNames; } + } catch (TskCoreException tskCoreException) { + throw new IllegalStateException(tskCoreException); } - return hashNames; + return Collections.emptySet(); } /** diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java index c04cb2c900..78bb8c6401 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableFile.java @@ -68,7 +68,7 @@ public abstract class DrawableFile extends AbstractFile return new ImageFile<>(abstractFileById, analyzed); } } - + /** * Skip the database query if we have already determined the file type. */ @@ -78,7 +78,7 @@ public abstract class DrawableFile extends AbstractFile } else { return new ImageFile<>(abstractFileById, analyzed); } - } + } public static DrawableFile create(Long id, boolean analyzed) throws TskCoreException, IllegalStateException { @@ -105,7 +105,8 @@ public abstract class DrawableFile extends AbstractFile protected DrawableFile(T file, Boolean analyzed) { /* @TODO: the two 'new Integer(0).shortValue()' values and null are * placeholders because the super constructor expects values i can't get - * easily at the moment. I assume this is related to why ReadContentInputStream can't read from DrawableFiles.*/ + * easily at the moment. I assume this is related to why + * ReadContentInputStream can't read from DrawableFiles. */ super(file.getSleuthkitCase(), file.getId(), file.getAttrType(), file.getAttrId(), file.getName(), file.getType(), file.getMetaAddr(), (int) file.getMetaSeq(), file.getDirType(), file.getMetaType(), null, new Integer(0).shortValue(), file.getSize(), file.getCtime(), file.getCrtime(), file.getAtime(), file.getMtime(), new Integer(0).shortValue(), file.getUid(), file.getGid(), file.getMd5Hash(), file.getKnown(), file.getParentPath()); this.analyzed = new SimpleBooleanProperty(analyzed); @@ -115,9 +116,8 @@ public abstract class DrawableFile extends AbstractFile public abstract boolean isVideo(); - synchronized public Collection getHashHitSetNames() { - Collection hashHitSetNames = ImageGalleryController.getDefault().getDatabase().getHashSetsForFile(getId()); - return hashHitSetNames; + public Collection getHashHitSetNames() { + return ImageGalleryController.getDefault().getHashSetManager().getHashSetsForFile(getId()); } @Override @@ -290,10 +290,10 @@ public abstract class DrawableFile extends AbstractFile } } catch (TskCoreException ex) { Logger.getLogger(DrawableFile.class.getName()).log(Level.WARNING, "problem looking up category for file " + this.getName(), ex); - } catch (IllegalStateException ex){ + } catch (IllegalStateException ex) { // We get here many times if the case is closed during ingest, so don't print out a ton of warnings. } - + } public abstract Image getThumbnail(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java new file mode 100644 index 0000000000..ca4c18d84c --- /dev/null +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java @@ -0,0 +1,36 @@ +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.util.Set; + +/** + * + */ +public class HashSetManager { + + private DrawableDB db = null; + + public void setDb(DrawableDB db) { + this.db = db; + hashSetCache.invalidateAll(); + } + private final LoadingCache> hashSetCache = CacheBuilder.newBuilder().build(CacheLoader.from(this::getHashSetsForFileHelper)); + + private Set getHashSetsForFileHelper(Long id) { + return db.getHashSetsForFile(id); + } + + public boolean isInHashSet(Long id) { + return hashSetCache.getUnchecked(id).isEmpty() == false; + } + + public Set getHashSetsForFile(Long id) { + return hashSetCache.getUnchecked(id); + } + + public void invalidateHashSetsForFile(Long id) { + hashSetCache.invalidate(id); + } +} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/DrawableGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/DrawableGroup.java index 024ef12f8b..41b972e695 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/DrawableGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/DrawableGroup.java @@ -43,7 +43,7 @@ public class DrawableGroup implements Comparable { private final ObservableList fileIDs = FXCollections.observableArrayList(); //cache the number of files in this groups with hashset hits - private int hashSetHitsCount = -1; + private long hashSetHitsCount = -1; private final ReadOnlyBooleanWrapper seen = new ReadOnlyBooleanWrapper(false); synchronized public ObservableList fileIds() { @@ -89,22 +89,21 @@ public class DrawableGroup implements Comparable { hashSetHitsCount = -1; } - synchronized public int getHashSetHitsCount() { + synchronized public long getHashSetHitsCount() { //TODO: use the drawable db for this ? -jm if (hashSetHitsCount < 0) { - hashSetHitsCount = 0; - for (Long fileID : fileIds()) { + try { + hashSetHitsCount + = fileIDs.stream() + .map(fileID -> ImageGalleryController.getDefault().getHashSetManager().isInHashSet(fileID)) + .filter(Boolean::booleanValue) + .count(); - try { - if (ImageGalleryController.getDefault().getDatabase().isInHashSet(fileID)) { - hashSetHitsCount++; - } - } catch (IllegalStateException | NullPointerException ex) { - LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()"); - break; - } + } catch (IllegalStateException | NullPointerException ex) { + LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()"); } } + return hashSetHitsCount; } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java index d08874bd29..ed4f71dc1b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java @@ -78,7 +78,6 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { private DrawableDB db; private final ImageGalleryController controller; - private CategoryCache categoryCache; /** * map from {@link GroupKey}s to {@link DrawableGroup}s. All groups (even * not @@ -138,20 +137,6 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { } - public void incrementCategoryCount(Category cat) { - if (categoryCache == null) { - categoryCache = new CategoryCache(db); - } - categoryCache.incrementCategoryCount(cat); - } - - public void decrementCategoryCount(Category cat) { - if (categoryCache == null) { - categoryCache = new CategoryCache(db); - } - categoryCache.decrementCategoryCount(cat); - } - /** * using the current groupBy set for this manager, find groupkeys for all * the groups the given file is a part of @@ -530,21 +515,6 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { } } - /** - * Count the number of files with the given category. - * This is faster than getFileIDsWithCategory and should be used if only the - * counts are needed and not the file IDs. - * - * @param category Category to match against - * - * @return Number of files with the given category - * - * @throws TskCoreException - */ - public long countFilesWithCategory(Category category) throws TskCoreException { - return categoryCache.getCategoryCount(category); - } - public List getFileIDsWithTag(TagName tagName) throws TskCoreException { try { List files = new ArrayList<>(); @@ -669,8 +639,6 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { if (checkAnalyzed != null) { // => the group is analyzed, so add it to the ui populateAnalyzedGroup(gk, checkAnalyzed); } - } else { - g.invalidateHashSetHitsCount(); } } } @@ -691,7 +659,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { */ for (final long fileId : fileIDs) { - db.updateHashSetsForFile(fileId); + controller.getHashSetManager().invalidateHashSetsForFile(fileId); //get grouping(s) this file would be in Set> groupsForFile = getGroupKeysForFileID(fileId); @@ -714,7 +682,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { } //we fire this event for all files so that the category counts get updated during initial db population - Category.fireChange(fileIDs); + controller.getCategoryManager().fireChange(fileIDs); if (evt.getChangedAttribute() == DrawableAttribute.TAGS) { TagUtils.fireChange(fileIDs); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java index e7dccedb53..1d72c6d712 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java @@ -34,7 +34,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.TagUtils; -import org.sleuthkit.autopsy.imagegallery.datamodel.Category; +import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; /** * GUI component that represents a single image as a tile with an icon, a label @@ -44,7 +44,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.Category; * * TODO: refactor this to extend from {@link Control}? -jm */ -public class DrawableTile extends SingleDrawableViewBase implements Category.CategoryListener, TagUtils.TagListener { +public class DrawableTile extends SingleDrawableViewBase implements CategoryManager.CategoryListener, TagUtils.TagListener { private static final DropShadow LAST_SELECTED_EFFECT = new DropShadow(10, Color.BLUE); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java index 9de7704d7a..27115a5e65 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java @@ -14,13 +14,14 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.TagUtils; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; +import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; /** * TODO: extract common interface out of {@link SingleImageView} and * {@link MetaDataPane} */ -public interface DrawableView extends Category.CategoryListener, TagUtils.TagListener { +public interface DrawableView extends CategoryManager.CategoryListener, TagUtils.TagListener { //TODO: do this all in css? -jm static final int CAT_BORDER_WIDTH = 10; @@ -58,9 +59,9 @@ public interface DrawableView extends Category.CategoryListener, TagUtils.TagLis void handleTagsChanged(Collection ids); default boolean hasHashHit() { - try{ + try { return getFile().getHashHitSetNames().isEmpty() == false; - } catch (NullPointerException ex){ + } catch (NullPointerException ex) { // I think this happens when we're in the process of removing images from the view while // also trying to update it? Logger.getLogger(DrawableView.class.getName()).log(Level.WARNING, "Error looking up hash set hits"); @@ -90,8 +91,8 @@ public interface DrawableView extends Category.CategoryListener, TagUtils.TagLis default Category updateCategoryBorder() { final Category category = getFile().getCategory(); final Border border = hasHashHit() && (category == Category.ZERO) - ? HASH_BORDER - : DrawableView.getCategoryBorder(category); + ? HASH_BORDER + : DrawableView.getCategoryBorder(category); Platform.runLater(() -> { getBorderable().setBorder(border); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java index b8cfe0aa4e..fdfcdf2efe 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java @@ -51,6 +51,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.TagUtils; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; +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; @@ -59,7 +60,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * */ -public class MetaDataPane extends AnchorPane implements Category.CategoryListener, TagUtils.TagListener, DrawableView { +public class MetaDataPane extends AnchorPane implements CategoryManager.CategoryListener, TagUtils.TagListener, DrawableView { private static final Logger LOGGER = Logger.getLogger(MetaDataPane.class.getName()); @@ -102,7 +103,7 @@ public class MetaDataPane extends AnchorPane implements Category.CategoryListene assert tableView != null : "fx:id=\"tableView\" was not injected: check your FXML file 'MetaDataPane.fxml'."; assert valueColumn != null : "fx:id=\"valueColumn\" was not injected: check your FXML file 'MetaDataPane.fxml'."; TagUtils.registerListener(this); - Category.registerListener(this); + ImageGalleryController.getDefault().getCategoryManager().registerListener(this); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setPlaceholder(new Label("Select a file to show its details here.")); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java index 7d3a55a365..02ff0c90fb 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java @@ -71,7 +71,6 @@ import org.sleuthkit.autopsy.imagegallery.TagUtils; import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter; -import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.grouping.GroupKey; @@ -360,14 +359,14 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa disposeContent(); if (this.fileID == null || Case.isCaseOpen() == false) { - Category.unregisterListener(this); + ImageGalleryController.getDefault().getCategoryManager().unregisterListener(this); TagUtils.unregisterListener(this); file = null; Platform.runLater(() -> { clearContent(); }); } else { - Category.registerListener(this); + ImageGalleryController.getDefault().getCategoryManager().registerListener(this); TagUtils.registerListener(this); getFile(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java index a6cca03fe6..dcfc12613a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java @@ -53,6 +53,7 @@ import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; import org.sleuthkit.autopsy.imagegallery.TagUtils; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; +import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.ImageFile; import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile; @@ -64,7 +65,7 @@ import org.sleuthkit.datamodel.TskCoreException; * GroupPane. TODO: Extract a subclass for video files in slideshow mode-jm * TODO: reduce coupling to GroupPane */ -public class SlideShowView extends SingleDrawableViewBase implements TagUtils.TagListener, Category.CategoryListener { +public class SlideShowView extends SingleDrawableViewBase implements TagUtils.TagListener, CategoryManager.CategoryListener { private static final Logger LOGGER = Logger.getLogger(SlideShowView.class.getName()); @@ -201,7 +202,7 @@ public class SlideShowView extends SingleDrawableViewBase implements TagUtils.Ta @ThreadConfined(type = ThreadType.ANY) private void syncButtonVisibility() { - try{ + try { final boolean hasMultipleFiles = groupPane.getGrouping().fileIds().size() > 1; Platform.runLater(() -> { rightButton.setVisible(hasMultipleFiles); @@ -209,7 +210,7 @@ public class SlideShowView extends SingleDrawableViewBase implements TagUtils.Ta rightButton.setManaged(hasMultipleFiles); leftButton.setManaged(hasMultipleFiles); }); - } catch (NullPointerException ex){ + } catch (NullPointerException ex) { // The case has likely been closed LOGGER.log(Level.WARNING, "Error accessing groupPane"); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java index b2fd5fe076..4dbc8a3537 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java @@ -38,12 +38,13 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; +import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.datamodel.TskCoreException; /** * Displays summary statistics (counts) for each group */ -public class SummaryTablePane extends AnchorPane implements Category.CategoryListener { +public class SummaryTablePane extends AnchorPane implements CategoryManager.CategoryListener { private static SummaryTablePane instance; @@ -77,7 +78,7 @@ public class SummaryTablePane extends AnchorPane implements Category.CategoryLis tableView.getColumns().setAll(Arrays.asList(catColumn, countColumn)); // //register for category events - Category.registerListener(this); + ImageGalleryController.getDefault().getCategoryManager().registerListener(this); } private SummaryTablePane() { @@ -100,7 +101,7 @@ public class SummaryTablePane extends AnchorPane implements Category.CategoryLis if (Case.isCaseOpen()) { for (Category cat : Category.values()) { try { - data.add(new Pair<>(cat, ImageGalleryController.getDefault().getGroupManager().countFilesWithCategory(cat))); + data.add(new Pair<>(cat, ImageGalleryController.getDefault().getCategoryManager().getCategoryCount(cat))); } catch (TskCoreException ex) { Logger.getLogger(SummaryTablePane.class.getName()).log(Level.WARNING, "Error performing category file count"); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/TreeNodeComparators.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/TreeNodeComparators.java index f16cc0cef6..c0ea6e7813 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/TreeNodeComparators.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/TreeNodeComparators.java @@ -28,31 +28,34 @@ import javafx.scene.control.TreeItem; enum TreeNodeComparators implements Comparator>, NonNullCompareable { ALPHABETICAL("Group Name") { - @Override + @Override public int nonNullCompare(TreeItem o1, TreeItem o2) { - return o1.getValue().getGroup().groupKey.getValue().toString().compareTo(o2.getValue().getGroup().groupKey.getValue().toString()); - } - }, HIT_COUNT("Hit Count") { - @Override - public int nonNullCompare(TreeItem o1, TreeItem o2) { + return o1.getValue().getGroup().groupKey.getValue().toString().compareTo(o2.getValue().getGroup().groupKey.getValue().toString()); + } + }, + HIT_COUNT("Hit Count") { + @Override + public int nonNullCompare(TreeItem o1, TreeItem o2) { - return -Integer.compare(o1.getValue().getGroup().getHashSetHitsCount(), o2.getValue().getGroup().getHashSetHitsCount()); - } - }, FILE_COUNT("Group Size") { - @Override - public int nonNullCompare(TreeItem o1, TreeItem o2) { + return -Long.compare(o1.getValue().getGroup().getHashSetHitsCount(), o2.getValue().getGroup().getHashSetHitsCount()); + } + }, + FILE_COUNT("Group Size") { + @Override + public int nonNullCompare(TreeItem o1, TreeItem o2) { - return -Integer.compare(o1.getValue().getGroup().getSize(), o2.getValue().getGroup().getSize()); - } - }, HIT_FILE_RATIO("Hit Density") { - @Override - public int nonNullCompare(TreeItem o1, TreeItem o2) { + return -Integer.compare(o1.getValue().getGroup().getSize(), o2.getValue().getGroup().getSize()); + } + }, + HIT_FILE_RATIO("Hit Density") { + @Override + public int nonNullCompare(TreeItem o1, TreeItem o2) { - return -Double.compare(o1.getValue().getGroup().getHashSetHitsCount() / (double) o1.getValue().getGroup().getSize(), - o2.getValue().getGroup().getHashSetHitsCount() / (double) o2.getValue().getGroup().getSize()); - } - }; + return -Double.compare(o1.getValue().getGroup().getHashSetHitsCount() / (double) o1.getValue().getGroup().getSize(), + o2.getValue().getGroup().getHashSetHitsCount() / (double) o2.getValue().getGroup().getSize()); + } + }; @Override public int compare(TreeItem o1, TreeItem o2) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/license-imageanalyzer.txt b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/license-imagegallery.txt similarity index 93% rename from ImageGallery/src/org/sleuthkit/autopsy/imagegallery/license-imageanalyzer.txt rename to ImageGallery/src/org/sleuthkit/autopsy/imagegallery/license-imagegallery.txt index fb3e1b684a..40b778ffd9 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/license-imageanalyzer.txt +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/license-imagegallery.txt @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-14 Basis Technology Corp. + * Copyright 2015 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); From a20f0400666ab498ca6b8bd113dd74a51a01f5e3 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 9 Jun 2015 14:34:53 -0400 Subject: [PATCH 3/6] use event bus for category change events --- .../imagegallery/ImageGalleryController.java | 219 +++++++++--------- .../datamodel/CategoryChangeEvent.java | 39 ++++ ...ategoryCache.java => CategoryManager.java} | 34 +-- .../imagegallery/gui/DrawableTile.java | 3 +- .../imagegallery/gui/DrawableView.java | 8 +- .../imagegallery/gui/MetaDataPane.java | 9 +- .../gui/SingleDrawableViewBase.java | 9 +- .../imagegallery/gui/SlideShowView.java | 3 +- .../imagegallery/gui/SummaryTablePane.java | 13 +- 9 files changed, 185 insertions(+), 152 deletions(-) create mode 100644 ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryChangeEvent.java rename ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/{CategoryCache.java => CategoryManager.java} (78%) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 334b3f9a6c..349f44b5c5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.imagegallery; import java.beans.PropertyChangeEvent; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -83,25 +82,25 @@ import org.sleuthkit.datamodel.TskData; * control. */ public final class ImageGalleryController { - + private static final Logger LOGGER = Logger.getLogger(ImageGalleryController.class.getName()); - + private final Region infoOverLayBackground = new Region() { { setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY))); setOpacity(.4); } }; - + private static ImageGalleryController instance; - + public static synchronized ImageGalleryController getDefault() { if (instance == null) { instance = new ImageGalleryController(); } return instance; } - + private final History historyManager = new History<>(); /** @@ -109,73 +108,73 @@ public final class ImageGalleryController { * not listen to speed up ingest */ private final SimpleBooleanProperty listeningEnabled = new SimpleBooleanProperty(false); - + private final ReadOnlyIntegerWrapper queueSizeProperty = new ReadOnlyIntegerWrapper(0); - + private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false); - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) private final ReadOnlyBooleanWrapper stale = new ReadOnlyBooleanWrapper(false); - + private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false); - + private final FileIDSelectionModel selectionModel = FileIDSelectionModel.getInstance(); - + private DBWorkerThread dbWorkerThread; - + private DrawableDB db; - + private final GroupManager groupManager = new GroupManager(this); - + private StackPane fullUIStackPane; - + private StackPane centralStackPane; - + private Node infoOverlay; private final HashSetManager hashSetManager = new HashSetManager(); private final CategoryManager categoryManager = new CategoryManager(); - + public ReadOnlyBooleanProperty getMetaDataCollapsed() { return metaDataCollapsed.getReadOnlyProperty(); } - + public void setMetaDataCollapsed(Boolean metaDataCollapsed) { this.metaDataCollapsed.set(metaDataCollapsed); } - + private GroupViewState getViewState() { return historyManager.getCurrentState(); } - + public ReadOnlyBooleanProperty regroupDisabled() { return regroupDisabled.getReadOnlyProperty(); } - + public ReadOnlyObjectProperty viewState() { return historyManager.currentState(); } - + public synchronized FileIDSelectionModel getSelectionModel() { - + return selectionModel; } - + public GroupManager getGroupManager() { return groupManager; } - + public DrawableDB getDatabase() { return db; } - + synchronized public void setListeningEnabled(boolean enabled) { listeningEnabled.set(enabled); } - + synchronized boolean isListeningEnabled() { return listeningEnabled.get(); } - + @ThreadConfined(type = ThreadConfined.ThreadType.ANY) void setStale(Boolean b) { Platform.runLater(() -> { @@ -185,18 +184,18 @@ public final class ImageGalleryController { new PerCaseProperties(Case.getCurrentCase()).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.STALE, b.toString()); } } - + public ReadOnlyBooleanProperty stale() { return stale.getReadOnlyProperty(); } - + @ThreadConfined(type = ThreadConfined.ThreadType.JFX) boolean isStale() { return stale.get(); } - + private ImageGalleryController() { - + listeningEnabled.addListener((observable, oldValue, newValue) -> { //if we just turned on listening and a case is open and that case is not up to date if (newValue && !oldValue && Case.existsCurrentCase() && ImageGalleryModule.isDrawableDBStale(Case.getCurrentCase())) { @@ -204,28 +203,28 @@ public final class ImageGalleryController { queueDBWorkerTask(new CopyAnalyzedFiles()); } }); - + groupManager.getAnalyzedGroups().addListener((Observable o) -> { if (Case.isCaseOpen()) { checkForGroups(); } }); - + 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))); } }); - + viewState().addListener((Observable observable) -> { selectionModel.clearSelection(); }); - + regroupDisabled.addListener((Observable observable) -> { checkForGroups(); }); - + IngestManager.getInstance().addIngestModuleEventListener((PropertyChangeEvent evt) -> { Platform.runLater(this::updateRegroupDisabled); }); @@ -234,27 +233,27 @@ public final class ImageGalleryController { }); // metaDataCollapsed.bind(Toolbar.getDefault().showMetaDataProperty()); } - + public ReadOnlyBooleanProperty getCanAdvance() { return historyManager.getCanAdvance(); } - + public ReadOnlyBooleanProperty getCanRetreat() { return historyManager.getCanRetreat(); } - + public void advance(GroupViewState newState) { historyManager.advance(newState); } - + public GroupViewState advance() { return historyManager.advance(); } - + public GroupViewState retreat() { return historyManager.retreat(); } - + private void updateRegroupDisabled() { regroupDisabled.set(getFileUpdateQueueSizeProperty().get() > 0 || IngestManager.getInstance().isIngestRunning()); } @@ -276,7 +275,7 @@ public final class ImageGalleryController { new NoGroupsDialog("No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.", new ProgressIndicator())); } - + } else if (getFileUpdateQueueSizeProperty().get() > 0) { replaceNotification(fullUIStackPane, new NoGroupsDialog("No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.", @@ -290,19 +289,19 @@ public final class ImageGalleryController { replaceNotification(fullUIStackPane, new NoGroupsDialog("There are no images/videos in the added datasources.")); } - + } else if (!groupManager.isRegrouping()) { replaceNotification(centralStackPane, new NoGroupsDialog("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.")); } - + } else { clearNotification(); } } - + private void clearNotification() { //remove the ingest spinner if (fullUIStackPane != null) { @@ -313,27 +312,27 @@ public final class ImageGalleryController { centralStackPane.getChildren().remove(infoOverlay); } } - + private void replaceNotification(StackPane stackPane, Node newNode) { clearNotification(); - + infoOverlay = new StackPane(infoOverLayBackground, newNode); if (stackPane != null) { stackPane.getChildren().add(infoOverlay); } } - + private void restartWorker() { if (dbWorkerThread != null) { // Keep using the same worker thread if one exists return; } dbWorkerThread = new DBWorkerThread(); - + getFileUpdateQueueSizeProperty().addListener((Observable o) -> { Platform.runLater(this::updateRegroupDisabled); }); - + Thread th = new Thread(dbWorkerThread); th.setDaemon(false); // we want it to go away when it is done th.start(); @@ -346,7 +345,7 @@ public final class ImageGalleryController { */ public synchronized void setCase(Case theNewCase) { this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), this); - + setListeningEnabled(ImageGalleryModule.isEnabledforCase(theNewCase)); setStale(ImageGalleryModule.isDrawableDBStale(theNewCase)); @@ -358,7 +357,7 @@ public final class ImageGalleryController { hashSetManager.setDb(db); categoryManager.setDb(db); db.initializeImageList(); - SummaryTablePane.getDefault().handleCategoryChanged(Collections.emptyList()); + SummaryTablePane.getDefault().refresh(); } /** @@ -373,7 +372,7 @@ public final class ImageGalleryController { historyManager.clear(); }); Category.clearTagNames(); - + Toolbar.getDefault().reset(); groupManager.clear(); if (db != null) { @@ -395,21 +394,21 @@ public final class ImageGalleryController { } dbWorkerThread.addTask(innerTask); } - + public DrawableFile getFileFromId(Long fileID) throws TskCoreException { return db.getFileFromID(fileID); } - + public void setStacks(StackPane fullUIStack, StackPane centralStack) { fullUIStackPane = fullUIStack; this.centralStackPane = centralStack; Platform.runLater(this::checkForGroups); } - + public final ReadOnlyIntegerProperty getFileUpdateQueueSizeProperty() { return queueSizeProperty.getReadOnlyProperty(); } - + public ReadOnlyDoubleProperty regroupProgress() { return groupManager.regroupProgress(); } @@ -481,11 +480,11 @@ public final class ImageGalleryController { } }); } - + public HashSetManager getHashSetManager() { return hashSetManager; } - + public CategoryManager getCategoryManager() { return categoryManager; } @@ -526,7 +525,7 @@ public final class ImageGalleryController { queueSizeProperty.set(workQueue.size()); }); } - + @Override public void run() { @@ -537,22 +536,22 @@ public final class ImageGalleryController { } try { InnerTask it = workQueue.take(); - + if (it.cancelled == false) { it.run(); } - + Platform.runLater(() -> { queueSizeProperty.set(workQueue.size()); }); - + } catch (InterruptedException ex) { Exceptions.printStackTrace(ex); } } } } - + public SleuthkitCase getSleuthKitCase() throws IllegalStateException { if (Case.isCaseOpen()) { return Case.getCurrentCase().getSleuthkitCase(); @@ -565,55 +564,55 @@ public final class ImageGalleryController { * Abstract base class for task to be done on {@link DBWorkerThread} */ static public abstract class InnerTask implements Runnable { - + 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); } SimpleObjectProperty state = new SimpleObjectProperty<>(Worker.State.READY); SimpleDoubleProperty progress = new SimpleDoubleProperty(this, "pregress"); SimpleStringProperty message = new SimpleStringProperty(this, "status"); - + public SimpleDoubleProperty progressProperty() { return progress; } - + public SimpleStringProperty messageProperty() { return message; } - + public Worker.State getState() { return state.get(); } - + protected void updateState(Worker.State newState) { state.set(newState); } - + public ReadOnlyObjectProperty stateProperty() { return new ReadOnlyObjectWrapper<>(state.get()); } - + protected InnerTask() { } - + protected volatile boolean cancelled = false; - + public void cancel() { updateState(Worker.State.CANCELLED); } - + protected boolean isCancelled() { return getState() == Worker.State.CANCELLED; } @@ -623,25 +622,25 @@ public final class ImageGalleryController { * Abstract base class for tasks associated with a file in the database */ static public abstract class FileTask extends InnerTask { - + private final AbstractFile file; - + public AbstractFile getFile() { return file; } - + public FileTask(AbstractFile f) { super(); this.file = f; } - + } /** * task that updates one file in database with results from ingest */ private class UpdateFileTask extends FileTask { - + public UpdateFileTask(AbstractFile f) { super(f); } @@ -668,7 +667,7 @@ public final class ImageGalleryController { * task that updates one file in database with results from ingest */ private class RemoveFileTask extends FileTask { - + public RemoveFileTask(AbstractFile f) { super(f); } @@ -687,7 +686,7 @@ public final class ImageGalleryController { Logger.getLogger(RemoveFileTask.class.getName()).log(Level.SEVERE, "Case was closed out from underneath RemoveFile task"); } } - + } } @@ -699,16 +698,16 @@ public final class ImageGalleryController { * adds them to the Drawable DB */ private class CopyAnalyzedFiles extends InnerTask { - + final private String DRAWABLE_QUERY = "name LIKE '%." + StringUtils.join(ImageGalleryModule.getAllSupportedExtensions(), "' or name LIKE '%.") + "'"; - + private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("populating analyzed image/video database"); - + @Override public void run() { progressHandle.start(); updateMessage("populating analyzed image/video database"); - + try { //grab all files with supported extension or detected mime types final List files = getSleuthKitCase().findAllFilesWhere(DRAWABLE_QUERY + " or tsk_files.obj_id in (select tsk_files.obj_id from tsk_files , blackboard_artifacts, blackboard_attributes" @@ -718,7 +717,7 @@ public final class ImageGalleryController { + " and blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG.getTypeID() + " and blackboard_attributes.value_text in ('" + StringUtils.join(ImageGalleryModule.getSupportedMimes(), "','") + "'))"); progressHandle.switchToDeterminate(files.size()); - + updateProgress(0.0); //do in transaction @@ -732,7 +731,7 @@ public final class ImageGalleryController { } final Boolean hasMimeType = ImageGalleryModule.hasSupportedMimeType(f); final boolean known = f.getKnown() == TskData.FileKnown.KNOWN; - + if (known) { db.removeFile(f.getId(), tr); //remove known files } else { @@ -752,38 +751,38 @@ public final class ImageGalleryController { } } } - + units++; final int prog = units; progressHandle.progress(f.getName(), units); updateProgress(prog - 1 / (double) files.size()); updateMessage(f.getName()); } - + progressHandle.finish(); - + progressHandle = ProgressHandleFactory.createHandle("commiting image/video database"); updateMessage("commiting image/video database"); updateProgress(1.0); - + progressHandle.start(); db.commitTransaction(tr, true); - + } catch (TskCoreException ex) { Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex); } catch (IllegalStateException ex) { Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.SEVERE, "Case was closed out from underneath CopyDataSource task", ex); } - + progressHandle.finish(); - + updateMessage( ""); updateProgress( -1.0); setStale(false); } - + } /** @@ -794,7 +793,7 @@ public final class ImageGalleryController { * netbeans and ImageGallery progress/status */ class PrePopulateDataSourceFiles extends InnerTask { - + private final Content dataSource; /** @@ -804,7 +803,7 @@ public final class ImageGalleryController { */ // (name like '.jpg' or name like '.png' ...) private final String DRAWABLE_QUERY = "(name LIKE '%." + StringUtils.join(ImageGalleryModule.getAllSupportedExtensions(), "' or name LIKE '%.") + "') "; - + private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("prepopulating image/video database"); /** @@ -830,7 +829,7 @@ public final class ImageGalleryController { final List files; try { List fsObjIds = new ArrayList<>(); - + String fsQuery; if (dataSource instanceof Image) { Image image = (Image) dataSource; @@ -844,7 +843,7 @@ public final class ImageGalleryController { else { fsQuery = "(fs_obj_id IS NULL) "; } - + files = getSleuthKitCase().findAllFilesWhere(fsQuery + " and " + DRAWABLE_QUERY); progressHandle.switchToDeterminate(files.size()); @@ -862,21 +861,21 @@ public final class ImageGalleryController { final int prog = units; progressHandle.progress(f.getName(), units); } - + progressHandle.finish(); progressHandle = ProgressHandleFactory.createHandle("commiting image/video database"); - + progressHandle.start(); db.commitTransaction(tr, false); - + } catch (TskCoreException ex) { Logger.getLogger(PrePopulateDataSourceFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex); } catch (IllegalStateException | NullPointerException ex) { Logger.getLogger(PrePopulateDataSourceFiles.class.getName()).log(Level.WARNING, "Case was closed out from underneath prepopulating database"); } - + progressHandle.finish(); } } - + } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryChangeEvent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryChangeEvent.java new file mode 100644 index 0000000000..57ab451b8e --- /dev/null +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryChangeEvent.java @@ -0,0 +1,39 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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 java.util.Collection; +import java.util.Collections; + +/** + * + */ +public class CategoryChangeEvent { + + private final Collection ids; + + public Collection getIds() { + return Collections.unmodifiableCollection(ids); + } + + public CategoryChangeEvent(Collection ids) { + this.ids = ids; + } + +} diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryCache.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java similarity index 78% rename from ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryCache.java rename to ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java index d3d1d9baf3..c24c8ba9f7 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryCache.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java @@ -3,12 +3,11 @@ 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 com.google.common.eventbus.EventBus; import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import java.util.Collections; import java.util.concurrent.atomic.LongAdder; import java.util.logging.Level; -import javax.annotation.concurrent.GuardedBy; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.TskCoreException; @@ -19,6 +18,7 @@ public class CategoryManager { private static final java.util.logging.Logger LOGGER = Logger.getLogger(CategoryManager.class.getName()); private DrawableDB db; + private final EventBus categoryEventBus = new EventBus("Category Event Bus"); public void setDb(DrawableDB db) { this.db = db; @@ -94,35 +94,19 @@ public class CategoryManager { } return longAdder; } - @GuardedBy("listeners") - private final Set listeners = new HashSet<>(); public void fireChange(Collection ids) { - Set listenersCopy = new HashSet<>(); - synchronized (listeners) { - listenersCopy.addAll(listeners); - } - for (CategoryListener list : listenersCopy) { - list.handleCategoryChanged(ids); - } + + categoryEventBus.post(new CategoryChangeEvent(ids)); } - public void registerListener(CategoryListener aThis) { - synchronized (listeners) { - listeners.add(aThis); - } + public void registerListener(Object aThis) { + categoryEventBus.register(aThis); } - public void unregisterListener(CategoryListener aThis) { - synchronized (listeners) { - listeners.remove(aThis); - } + public void unregisterListener(Object aThis) { + categoryEventBus.unregister(aThis); } - public static interface CategoryListener { - - public void handleCategoryChanged(Collection ids); - - } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java index 1d72c6d712..39c2224ea3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java @@ -34,7 +34,6 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.TagUtils; -import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; /** * GUI component that represents a single image as a tile with an icon, a label @@ -44,7 +43,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; * * TODO: refactor this to extend from {@link Control}? -jm */ -public class DrawableTile extends SingleDrawableViewBase implements CategoryManager.CategoryListener, TagUtils.TagListener { +public class DrawableTile extends SingleDrawableViewBase implements TagUtils.TagListener { private static final DropShadow LAST_SELECTED_EFFECT = new DropShadow(10, Color.BLUE); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java index 27115a5e65..f9269f23cb 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java @@ -1,5 +1,6 @@ package org.sleuthkit.autopsy.imagegallery.gui; +import com.google.common.eventbus.Subscribe; import java.util.Collection; import java.util.logging.Level; import javafx.application.Platform; @@ -14,6 +15,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.TagUtils; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; +import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; @@ -21,7 +23,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; * TODO: extract common interface out of {@link SingleImageView} and * {@link MetaDataPane} */ -public interface DrawableView extends CategoryManager.CategoryListener, TagUtils.TagListener { +public interface DrawableView extends TagUtils.TagListener { //TODO: do this all in css? -jm static final int CAT_BORDER_WIDTH = 10; @@ -52,8 +54,8 @@ public interface DrawableView extends CategoryManager.CategoryListener, TagUtils Long getFileID(); - @Override - void handleCategoryChanged(Collection ids); + @Subscribe + void handleCategoryChanged(CategoryChangeEvent evt); @Override void handleTagsChanged(Collection ids); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java index fdfcdf2efe..fc8a19ccd8 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.imagegallery.gui; +import com.google.common.eventbus.Subscribe; import java.io.IOException; import java.net.URL; import java.util.Arrays; @@ -51,6 +52,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.TagUtils; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; +import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; @@ -60,7 +62,7 @@ import org.sleuthkit.datamodel.TskCoreException; /** * */ -public class MetaDataPane extends AnchorPane implements CategoryManager.CategoryListener, TagUtils.TagListener, DrawableView { +public class MetaDataPane extends AnchorPane implements TagUtils.TagListener, DrawableView { private static final Logger LOGGER = Logger.getLogger(MetaDataPane.class.getName()); @@ -237,9 +239,10 @@ public class MetaDataPane extends AnchorPane implements CategoryManager.Category return imageBorder; } + @Subscribe @Override - public void handleCategoryChanged(Collection ids) { - if (getFile() != null && ids.contains(getFileID())) { + public void handleCategoryChanged(CategoryChangeEvent evt) { + if (getFile() != null && evt.getIds().contains(getFileID())) { updateUI(); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java index 02ff0c90fb..b3be4ec54e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java @@ -19,6 +19,7 @@ */ package org.sleuthkit.autopsy.imagegallery.gui; +import com.google.common.eventbus.Subscribe; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -71,6 +72,8 @@ import org.sleuthkit.autopsy.imagegallery.TagUtils; import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter; +import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent; +import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.grouping.GroupKey; @@ -396,9 +399,9 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa return imageBorder; } - @Override - public void handleCategoryChanged(Collection ids) { - if (ids.contains(fileID)) { + @Subscribe + public void handleCategoryChanged(CategoryChangeEvent evt) { + if (evt.getIds().contains(fileID)) { updateCategoryBorder(); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java index dcfc12613a..7b60d837b7 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java @@ -53,7 +53,6 @@ import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; import org.sleuthkit.autopsy.imagegallery.TagUtils; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; -import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.ImageFile; import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile; @@ -65,7 +64,7 @@ import org.sleuthkit.datamodel.TskCoreException; * GroupPane. TODO: Extract a subclass for video files in slideshow mode-jm * TODO: reduce coupling to GroupPane */ -public class SlideShowView extends SingleDrawableViewBase implements TagUtils.TagListener, CategoryManager.CategoryListener { +public class SlideShowView extends SingleDrawableViewBase implements TagUtils.TagListener { private static final Logger LOGGER = Logger.getLogger(SlideShowView.class.getName()); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java index 4dbc8a3537..d027c679a3 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java @@ -18,8 +18,8 @@ */ package org.sleuthkit.autopsy.imagegallery.gui; +import com.google.common.eventbus.Subscribe; import java.util.Arrays; -import java.util.Collection; import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; @@ -38,13 +38,14 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; +import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.datamodel.TskCoreException; /** * Displays summary statistics (counts) for each group */ -public class SummaryTablePane extends AnchorPane implements CategoryManager.CategoryListener { +public class SummaryTablePane extends AnchorPane { private static SummaryTablePane instance; @@ -95,8 +96,12 @@ public class SummaryTablePane extends AnchorPane implements CategoryManager.Cate /** * listen to Category updates and rebuild the table */ - @Override - public void handleCategoryChanged(Collection ids) { + @Subscribe + public void handleCategoryChanged(CategoryChangeEvent evt) { + refresh(); + } + + public void refresh() { final ObservableList> data = FXCollections.observableArrayList(); if (Case.isCaseOpen()) { for (Category cat : Category.values()) { From bcca575c9db9372788594b27142576027c1694b0 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 9 Jun 2015 15:20:48 -0400 Subject: [PATCH 4/6] bug fixes --- .../autopsy/imagegallery/gui/GroupPane.java | 195 +++++++++--------- .../imagegallery/gui/SlideShowView.java | 8 +- 2 files changed, 103 insertions(+), 100 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GroupPane.java index 46362fc0dc..00315821ab 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GroupPane.java @@ -85,6 +85,7 @@ 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; @@ -127,67 +128,67 @@ import org.sleuthkit.datamodel.TskCoreException; * instance to a separate class analogous to the SlideShow */ public class GroupPane extends BorderPane implements GroupView { - + private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName()); - + 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 static final FileIDSelectionModel globalSelectionModel = FileIDSelectionModel.getInstance(); - + private final Back backAction; - + private final Forward forwardAction; - + @FXML private SplitMenuButton grpCatSplitMenu; - + @FXML private SplitMenuButton grpTagSplitMenu; - + @FXML private ToolBar headerToolBar; - + @FXML private SegmentedButton segButton; - + private SlideShowView slideShowPane; - + @FXML private ToggleButton slideShowToggle; - + @FXML private Region spacer; - + @FXML private GridView gridView; - + @FXML private ToggleButton tileToggle; - + @FXML private Button nextButton; - + @FXML private Button backButton; - + @FXML private Button forwardButton; - + @FXML private Label groupLabel; - + private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler(); - + private final NextUnseenGroup nextGroupAction; - + private final ImageGalleryController controller; - + private ContextMenu contextMenu; - + private Integer selectionAnchorIndex; /** @@ -205,12 +206,10 @@ public class GroupPane extends BorderPane implements GroupView { * to determine whether fileIDs are visible or are offscreen. No entry * indicates the given fileID is not displayed on screen. DrawableCells are * responsible for adding and removing themselves from this map. - * - * TODO: use ConcurrentHashMap ? */ - @ThreadConfined(type = ThreadType.UI) + @ThreadConfined(type = ThreadType.JFX) private final Map cellMap = new HashMap<>(); - + private final InvalidationListener filesSyncListener = (observable) -> { final String header = getHeaderString(); final List fileIds = getGrouping().fileIds(); @@ -219,7 +218,7 @@ public class GroupPane extends BorderPane implements GroupView { groupLabel.setText(header); }); }; - + public GroupPane(ImageGalleryController controller) { this.controller = controller; nextGroupAction = new NextUnseenGroup(controller); @@ -227,7 +226,7 @@ public class GroupPane extends BorderPane implements GroupView { forwardAction = new Forward(controller); FXMLConstructor.construct(this, "GroupPane.fxml"); } - + public void activateSlideShowViewer(Long slideShowFileId) { groupViewMode.set(GroupViewMode.SLIDE_SHOW); @@ -245,9 +244,9 @@ public class GroupPane extends BorderPane implements GroupView { setCenter(slideShowPane); slideShowPane.requestFocus(); } - + public void activateTileViewer() { - + groupViewMode.set(GroupViewMode.TILE); setCenter(gridView); gridView.requestFocus(); @@ -256,11 +255,11 @@ public class GroupPane extends BorderPane implements GroupView { } this.scrollToFileID(globalSelectionModel.lastSelectedProperty().get()); } - + public DrawableGroup getGrouping() { return grouping.get(); } - + private MenuItem createGrpCatMenuItem(final Category cat) { final MenuItem menuItem = new MenuItem(cat.getDisplayName(), new ImageView(DrawableAttribute.CATEGORY.getIcon())); menuItem.setOnAction(new EventHandler() { @@ -268,14 +267,14 @@ public class GroupPane extends BorderPane implements GroupView { public void handle(ActionEvent t) { Set fileIdSet = new HashSet<>(getGrouping().fileIds()); new CategorizeAction().addTagsToFiles(cat.getTagName(), "", fileIdSet); - + grpCatSplitMenu.setText(cat.getDisplayName()); grpCatSplitMenu.setOnAction(this); } }); return menuItem; } - + private MenuItem createGrpTagMenuItem(final TagName tn) { final MenuItem menuItem = new MenuItem(tn.getDisplayName(), new ImageView(DrawableAttribute.TAGS.getIcon())); menuItem.setOnAction(new EventHandler() { @@ -283,14 +282,14 @@ public class GroupPane extends BorderPane implements GroupView { public void handle(ActionEvent t) { Set fileIdSet = new HashSet<>(getGrouping().fileIds()); AddDrawableTagAction.getInstance().addTagsToFiles(tn, "", fileIdSet); - + grpTagSplitMenu.setText(tn.getDisplayName()); grpTagSplitMenu.setOnAction(this); } }); return menuItem; } - + private void selectAllFiles() { globalSelectionModel.clearAndSelectAll(getGrouping().fileIds()); } @@ -298,14 +297,14 @@ public class GroupPane extends BorderPane implements GroupView { /** create the string to display in the group header */ protected String getHeaderString() { return isNull(getGrouping()) ? "" - : defaultIfBlank(getGrouping().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()) + " -- " + : StringUtils.defaultIfBlank(getGrouping().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()) + " -- " + getGrouping().getHashSetHitsCount() + " hash set hits / " + getGrouping().getSize() + " files"; } - + ContextMenu getContextMenu() { return contextMenu; } - + ReadOnlyObjectProperty grouping() { return grouping.getReadOnlyProperty(); } @@ -337,7 +336,7 @@ public class GroupPane extends BorderPane implements GroupView { //configure toolbar properties HBox.setHgrow(spacer, Priority.ALWAYS); spacer.setMinWidth(Region.USE_PREF_SIZE); - + ArrayList grpTagMenues = new ArrayList<>(); for (final TagName tn : TagUtils.getNonCategoryTagNames()) { MenuItem menuItem = createGrpTagMenuItem(tn); @@ -351,7 +350,7 @@ public class GroupPane extends BorderPane implements GroupView { } grpTagSplitMenu.setGraphic(new ImageView(DrawableAttribute.TAGS.getIcon())); grpTagSplitMenu.getItems().setAll(grpTagMenues); - + ArrayList grpCategoryMenues = new ArrayList<>(); for (final Category cat : Category.values()) { MenuItem menuItem = createGrpCatMenuItem(cat); @@ -361,7 +360,7 @@ public class GroupPane extends BorderPane implements GroupView { grpCatSplitMenu.setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon())); grpCatSplitMenu.getItems().setAll(grpCategoryMenues); grpCatSplitMenu.setOnAction(createGrpCatMenuItem(Category.FIVE).getOnAction()); - + Runnable syncMode = () -> { switch (groupViewMode.get()) { case SLIDE_SHOW: @@ -377,7 +376,7 @@ public class GroupPane extends BorderPane implements GroupView { groupViewMode.addListener((o) -> { syncMode.run(); }); - + slideShowToggle.toggleGroupProperty().addListener((o) -> { slideShowToggle.getToggleGroup().selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> { if (newToggle == null) { @@ -390,34 +389,34 @@ public class GroupPane extends BorderPane implements GroupView { slideShowToggle.setOnAction((ActionEvent t) -> { activateSlideShowViewer(globalSelectionModel.lastSelectedProperty().get()); }); - + tileToggle.setOnAction((ActionEvent t) -> { activateTileViewer(); }); - + controller.viewState().addListener((ObservableValue observable, GroupViewState oldValue, GroupViewState newValue) -> { setViewState(newValue); }); - + addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler); gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler() { - + private ContextMenu buildContextMenu() { ArrayList menuItems = new ArrayList<>(); - + menuItems.add(CategorizeAction.getPopupMenu()); - + menuItems.add(AddDrawableTagAction.getInstance().getPopupMenu()); - + 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())); } } @@ -430,12 +429,12 @@ public class GroupPane extends BorderPane implements GroupView { }); }); 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()) { @@ -455,7 +454,7 @@ public class GroupPane extends BorderPane implements GroupView { if (contextMenu == null) { contextMenu = buildContextMenu(); } - + contextMenu.hide(); contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY()); t.consume(); @@ -463,7 +462,7 @@ public class GroupPane extends BorderPane implements GroupView { } } }); - + ActionUtils.configureButton(nextGroupAction, nextButton); final EventHandler onAction = nextButton.getOnAction(); nextButton.setOnAction((ActionEvent event) -> { @@ -471,10 +470,10 @@ public class GroupPane extends BorderPane implements GroupView { nextButton.setEffect(null); onAction.handle(event); }); - + ActionUtils.configureButton(forwardAction, forwardButton); ActionUtils.configureButton(backAction, backButton); - + nextGroupAction.disabledProperty().addListener((ObservableValue observable, Boolean oldValue, Boolean newValue) -> { nextButton.setEffect(newValue ? null : DROP_SHADOW); if (newValue == false) { @@ -490,28 +489,29 @@ public class GroupPane extends BorderPane implements GroupView { if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW) { slideShowPane.setFile(newFileId); } else { - + scrollToFileID(newFileId); } }); - + setViewState(controller.viewState().get()); } - + @ThreadConfined(type = ThreadType.JFX) private void scrollToFileID(final Long newFileID) { 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); @@ -527,10 +527,10 @@ public class GroupPane extends BorderPane implements GroupView { .max().getAsInt(); //[minIndex, maxIndex] is the range of indexes in the fileIDs list that are currently displayed - if (minIndex < 0 && maxIndex < 0) { + if (minIndex < 0) { return; } - + if (selectedIndex < minIndex) { scrollBar.decrement(); } else if (selectedIndex > maxIndex) { @@ -542,14 +542,14 @@ public class GroupPane extends BorderPane implements GroupView { } 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()) { @@ -570,7 +570,7 @@ public class GroupPane extends BorderPane implements GroupView { if (nonNull(getGrouping())) { getGrouping().fileIds().removeListener(filesSyncListener); } - + if (isNull(viewState) || isNull(viewState.getGroup())) { this.grouping.set(null); final List fileIds = Collections.emptyList(); @@ -584,13 +584,13 @@ public class GroupPane extends BorderPane implements GroupView { cellMap.values().stream().forEach(DrawableCell::resetItem); } }); - + } else { if (this.grouping.get() != viewState.getGroup()) { this.grouping.set(viewState.getGroup()); - + this.getGrouping().fileIds().addListener(filesSyncListener); - + final String header = getHeaderString(); final ObservableList fileIds = getGrouping().fileIds(); Platform.runLater(() -> { @@ -606,18 +606,18 @@ public class GroupPane extends BorderPane implements GroupView { } } } - + @ThreadConfined(type = ThreadType.JFX) private void resetScrollBar() { getScrollBar().ifPresent((scrollBar) -> { scrollBar.setValue(0); }); } - + private class DrawableCell extends GridCell { - + private final DrawableTile tile = new DrawableTile(GroupPane.this); - + public DrawableCell() { itemProperty().addListener((ObservableValue observable, Long oldValue, Long newValue) -> { if (oldValue != null) { @@ -633,24 +633,25 @@ public class GroupPane extends BorderPane implements GroupView { } } cellMap.put(newValue, DrawableCell.this); - + } }); - + setGraphic(tile); } - + @Override protected void updateItem(Long item, boolean empty) { super.updateItem(item, empty); tile.setFile(item); } - + void resetItem() { + updateItem(null, true); tile.setFile(null); } } - + 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); @@ -659,10 +660,10 @@ public class GroupPane extends BorderPane implements GroupView { * arrows) */ private class KeyboardHandler implements EventHandler { - + @Override public void handle(KeyEvent t) { - + if (t.getEventType() == KeyEvent.KEY_PRESSED) { switch (t.getCode()) { case SHIFT: @@ -705,7 +706,7 @@ public class GroupPane extends BorderPane implements GroupView { t.consume(); break; } - + if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) { selectAllFiles(); t.consume(); @@ -739,18 +740,18 @@ public class GroupPane extends BorderPane implements GroupView { } } } - + } - + private void handleArrows(KeyEvent t) { Long lastSelectFileId = globalSelectionModel.lastSelectedProperty().get(); - + int lastSelectedIndex = lastSelectFileId != null ? grouping.get().fileIds().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 @@ -769,7 +770,7 @@ public class GroupPane extends BorderPane implements GroupView { } } } - + @ThreadConfined(type = ThreadType.JFX) private Optional getScrollBar() { if (gridView == null || gridView.getSkin() == null) { @@ -777,16 +778,16 @@ public class GroupPane extends BorderPane implements GroupView { } return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); } - + void makeSelection(Boolean shiftDown, Long newFileID) { - + if (shiftDown) { //TODO: do more hear to implement slicker multiselect int endIndex = grouping.get().fileIds().indexOf(newFileID); int startIndex = IntStream.of(grouping.get().fileIds().size(), selectionAnchorIndex, endIndex).min().getAsInt(); endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt(); List subList = grouping.get().fileIds().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().fileIds().size()) + 1); - + globalSelectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()])); globalSelectionModel.select(newFileID); } else { @@ -794,5 +795,5 @@ public class GroupPane extends BorderPane implements GroupView { globalSelectionModel.clearAndSelect(newFileID); } } - + } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java index 7b60d837b7..cec79ebfda 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java @@ -193,9 +193,11 @@ public class SlideShowView extends SingleDrawableViewBase implements TagUtils.Ta groupPane.grouping().addListener((Observable observable) -> { syncButtonVisibility(); - groupPane.getGrouping().fileIds().addListener((Observable observable1) -> { - syncButtonVisibility(); - }); + if (groupPane.getGrouping() != null) { + groupPane.getGrouping().fileIds().addListener((Observable observable1) -> { + syncButtonVisibility(); + }); + } }); } From c2bdb0fc312c6547a67a8ae30d3c1c4b51526e8e Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 9 Jun 2015 16:37:18 -0400 Subject: [PATCH 5/6] clear last selected effect from tiles as they scroll out of view --- .../autopsy/imagegallery/gui/DrawableTile.java | 13 ++++++++++++- .../autopsy/imagegallery/gui/DrawableView.java | 2 -- .../autopsy/imagegallery/gui/MetaDataPane.java | 1 - .../imagegallery/gui/SingleDrawableViewBase.java | 3 +-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java index 39c2224ea3..503976d5f4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java @@ -22,6 +22,7 @@ import java.net.URL; import java.util.Objects; import java.util.ResourceBundle; import java.util.logging.Level; +import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.CacheHint; import javafx.scene.control.Control; @@ -34,6 +35,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.TagUtils; +import static org.sleuthkit.autopsy.imagegallery.gui.SingleDrawableViewBase.globalSelectionModel; /** * GUI component that represents a single image as a tile with an icon, a label @@ -98,11 +100,20 @@ public class DrawableTile extends SingleDrawableViewBase implements TagUtils.Tag } @Override - @ThreadConfined(type = ThreadType.UI) + @ThreadConfined(type = ThreadType.JFX) protected void clearContent() { imageView.setImage(null); } + @Override + protected void updateSelectionState() { + super.updateSelectionState(); + final boolean lastSelected = Objects.equals(globalSelectionModel.lastSelectedProperty().get(), fileID); + Platform.runLater(() -> { + setEffect(lastSelected ? LAST_SELECTED_EFFECT : null); + }); + } + @Override protected Runnable getContentUpdateRunnable() { Image image = file.getThumbnail(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java index f9269f23cb..65224c9f26 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java @@ -16,7 +16,6 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.TagUtils; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent; -import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; /** @@ -101,5 +100,4 @@ public interface DrawableView extends TagUtils.TagListener { }); return category; } - } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java index fc8a19ccd8..6c731aafb5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java @@ -53,7 +53,6 @@ import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.TagUtils; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent; -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; diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java index b3be4ec54e..35004ac587 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java @@ -73,7 +73,6 @@ import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent; -import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.grouping.GroupKey; @@ -387,7 +386,7 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa } } - private void updateSelectionState() { + protected void updateSelectionState() { final boolean selected = globalSelectionModel.isSelected(fileID); Platform.runLater(() -> { SingleDrawableViewBase.this.setBorder(selected ? SELECTED_BORDER : UNSELECTED_BORDER); From b9f25256c3902547456af8a5e8e6b43967afa377 Mon Sep 17 00:00:00 2001 From: jmillman Date: Tue, 9 Jun 2015 16:42:17 -0400 Subject: [PATCH 6/6] Lots of cleanup and comments rename and comment DrawableViewBase more cleanup of category related code comments license header; cleanup in DrawableDB, GroupManager, and ImageGalleryController cleanup HashSetManager and DrawableGroup cleanup/commetns DrawableTile, DrawableView, DrawableViewBase more cleanup ind DrawableDB --- .../imagegallery/ImageGalleryController.java | 13 +- .../imagegallery/ImageGalleryModule.java | 2 + .../actions/CategorizeAction.java | 3 +- .../imagegallery/datamodel/Category.java | 112 ++++------ .../datamodel/CategoryChangeEvent.java | 9 +- .../datamodel/CategoryManager.java | 132 ++++++++---- .../imagegallery/datamodel/DrawableDB.java | 134 ++++++------ .../datamodel/HashSetManager.java | 55 ++++- .../imagegallery/grouping/DrawableGroup.java | 20 +- .../imagegallery/grouping/GroupManager.java | 15 +- .../imagegallery/gui/DrawableTile.java | 19 +- .../imagegallery/gui/DrawableView.java | 15 +- ...bleViewBase.java => DrawableViewBase.java} | 17 +- .../autopsy/imagegallery/gui/GroupPane.java | 197 +++++++++--------- .../imagegallery/gui/MetaDataPane.java | 25 +-- .../imagegallery/gui/SlideShowView.java | 2 +- .../imagegallery/gui/SummaryTablePane.java | 10 +- .../autopsy/imagegallery/gui/Toolbar.java | 31 +-- .../gui/navpanel/TreeNodeComparators.java | 6 +- 19 files changed, 450 insertions(+), 367 deletions(-) rename ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/{SingleDrawableViewBase.java => DrawableViewBase.java} (96%) diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 349f44b5c5..b93f362dae 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -125,14 +125,14 @@ public final class ImageGalleryController { private DrawableDB db; private final GroupManager groupManager = new GroupManager(this); + private final HashSetManager hashSetManager = new HashSetManager(); + private final CategoryManager categoryManager = new CategoryManager(); private StackPane fullUIStackPane; private StackPane centralStackPane; private Node infoOverlay; - private final HashSetManager hashSetManager = new HashSetManager(); - private final CategoryManager categoryManager = new CategoryManager(); public ReadOnlyBooleanProperty getMetaDataCollapsed() { return metaDataCollapsed.getReadOnlyProperty(); @@ -344,7 +344,7 @@ public final class ImageGalleryController { * @param theNewCase the case to configure the controller for */ public synchronized void setCase(Case theNewCase) { - this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), this); + this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), getSleuthKitCase()); setListeningEnabled(ImageGalleryModule.isEnabledforCase(theNewCase)); setStale(ImageGalleryModule.isDrawableDBStale(theNewCase)); @@ -356,7 +356,6 @@ public final class ImageGalleryController { groupManager.setDB(db); hashSetManager.setDb(db); categoryManager.setDb(db); - db.initializeImageList(); SummaryTablePane.getDefault().refresh(); } @@ -386,7 +385,7 @@ public final class ImageGalleryController { * * @param innerTask */ - public final void queueDBWorkerTask(InnerTask innerTask) { + public void queueDBWorkerTask(InnerTask innerTask) { // @@@ We could make a lock for the worker thread if (dbWorkerThread == null) { @@ -405,7 +404,7 @@ public final class ImageGalleryController { Platform.runLater(this::checkForGroups); } - public final ReadOnlyIntegerProperty getFileUpdateQueueSizeProperty() { + public ReadOnlyIntegerProperty getFileUpdateQueueSizeProperty() { return queueSizeProperty.getReadOnlyProperty(); } @@ -653,7 +652,7 @@ public final class ImageGalleryController { try { DrawableFile drawableFile = DrawableFile.create(getFile(), true, db.isVideoFile(getFile())); db.updateFile(drawableFile); - } catch (NullPointerException | TskCoreException ex) { + } catch (NullPointerException ex) { // This is one of the places where we get many errors if the case is closed during processing. // We don't want to print out a ton of exceptions if this is the case. if (Case.isCaseOpen()) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java index 595e501e59..8518d093a9 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryModule.java @@ -162,6 +162,8 @@ public class ImageGalleryModule { * * @return true if the given file has a supported video mime type or * extension, else false + * + * //TODO: convert this to use the new FileTypeDetector? */ public static boolean isVideoFile(AbstractFile file) { try { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java index 69b0844fec..79a04f535d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java @@ -26,6 +26,7 @@ import java.util.logging.Level; import javafx.event.ActionEvent; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; +import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javax.swing.JOptionPane; import org.sleuthkit.autopsy.casemodule.Case; @@ -101,7 +102,7 @@ public class CategorizeAction extends AddTagAction { final CategorizeAction categorizeAction = new CategorizeAction(); categorizeAction.addTag(cat.getTagName(), NO_COMMENT); }); - categoryItem.setAccelerator(new KeyCodeCombination(cat.getHotKeycode())); + categoryItem.setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber())))); getItems().add(categoryItem); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java index 0d2304ac4d..780e851876 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/Category.java @@ -18,26 +18,19 @@ */ package org.sleuthkit.autopsy.imagegallery.datamodel; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.logging.Level; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.scene.control.MenuItem; -import javafx.scene.control.SplitMenuButton; -import javafx.scene.image.ImageView; -import javafx.scene.input.KeyCode; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javafx.scene.paint.Color; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.TagUtils; -import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** - * + * Enum to represent the six categories in the DHs image categorization scheme. */ public enum Category implements Comparable { @@ -48,35 +41,47 @@ public enum Category implements Comparable { FOUR(Color.BISQUE, 4, "CAT-4, Exemplar/Comparison (Internal Use Only)"), FIVE(Color.GREEN, 5, "CAT-5, Non-pertinent"); - final static private Map nameMap = new HashMap<>(); - - private static final List valuesList = Arrays.asList(values()); - - static { - for (Category cat : values()) { - nameMap.put(cat.displayName, cat); - } - } - - - - public KeyCode getHotKeycode() { - return KeyCode.getKeyCode(Integer.toString(id)); - } + /** map from displayName to enum value */ + private static final Map nameMap + = Stream.of(values()).collect(Collectors.toMap(Category::getDisplayName, + Function.identity())); public static final String CATEGORY_PREFIX = "CAT-"; - private TagName tagName; - - public static List valuesList() { - return valuesList; + public static Category fromDisplayName(String displayName) { + return nameMap.get(displayName); } - private Color color; + /** + * Use when closing a case to make sure everything is re-initialized in the + * next case. + */ + public static void clearTagNames() { + Category.ZERO.tagName = null; + Category.ONE.tagName = null; + Category.TWO.tagName = null; + Category.THREE.tagName = null; + Category.FOUR.tagName = null; + Category.FIVE.tagName = null; + } - private String displayName; + private TagName tagName; - private int id; + private final Color color; + + private final String displayName; + + private final int id; + + private Category(Color color, int id, String name) { + this.color = color; + this.displayName = name; + this.id = id; + } + + public int getCategoryNumber() { + return id; + } public Color getColor() { return color; @@ -91,18 +96,12 @@ public enum Category implements Comparable { return displayName; } - static public Category fromDisplayName(String displayName) { - return nameMap.get(displayName); - } - - private Category(Color color, int id, String name) { - this.color = color; - this.displayName = name; - this.id = id; - } - + /** + * get the TagName used to store this Category in the main autopsy db. + * + * @return the TagName used for this Category + */ public TagName getTagName() { - if (tagName == null) { try { tagName = TagUtils.getTagName(displayName); @@ -112,29 +111,4 @@ public enum Category implements Comparable { } return tagName; } - - public MenuItem createSelCatMenuItem(final SplitMenuButton catSelectedMenuButton) { - final MenuItem menuItem = new MenuItem(this.getDisplayName(), new ImageView(DrawableAttribute.CATEGORY.getIcon())); - menuItem.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent t) { - new CategorizeAction().addTag(Category.this.getTagName(), ""); - catSelectedMenuButton.setText(Category.this.getDisplayName()); - catSelectedMenuButton.setOnAction(this); - } - }); - return menuItem; - } - - /** - * Use when closing a case to make sure everything is re-initialized in the next case. - */ - public static void clearTagNames(){ - Category.ZERO.tagName = null; - Category.ONE.tagName = null; - Category.TWO.tagName = null; - Category.THREE.tagName = null; - Category.FOUR.tagName = null; - Category.FIVE.tagName = null; - } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryChangeEvent.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryChangeEvent.java index 57ab451b8e..f53acac5f5 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryChangeEvent.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryChangeEvent.java @@ -20,14 +20,20 @@ package org.sleuthkit.autopsy.imagegallery.datamodel; import java.util.Collection; import java.util.Collections; +import javax.annotation.concurrent.Immutable; /** - * + * Event broadcast to various UI componenets when one or more files' category + * has been changed */ +@Immutable public class CategoryChangeEvent { private final Collection ids; + /** + * @return the fileIDs of the files whose categories have changed + */ public Collection getIds() { return Collections.unmodifiableCollection(ids); } @@ -35,5 +41,4 @@ public class CategoryChangeEvent { public CategoryChangeEvent(Collection ids) { this.ids = ids; } - } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java index c24c8ba9f7..699af41bbb 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/CategoryManager.java @@ -1,25 +1,76 @@ -package org.sleuthkit.autopsy.imagegallery.datamodel; +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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; import com.google.common.cache.LoadingCache; import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; import java.util.Collection; -import java.util.Collections; import java.util.concurrent.atomic.LongAdder; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; -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. + * + * To receive CategoryChangeEvents, a listener must register itself, and + * implement a method annotated with {@link 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? * */ public class CategoryManager { private static final java.util.logging.Logger LOGGER = Logger.getLogger(CategoryManager.class.getName()); + + /** + * the DrawableDB that backs the category counts cache. The counts are + * initialized from this, and the counting of CAT-0 is always delegated to + * this db. + */ private DrawableDB db; + + /** + * Used to distribute {@link CategoryChangeEvent}s + */ private final EventBus categoryEventBus = new EventBus("Category Event Bus"); + /** + * For performance reasons, keep current category counts in memory. All of + * 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)); + + /** + * assign a new db. the counts cache is invalidated and all subsequent db + * lookups go to the new db. + * + * Also clears the Category TagNames (should this happen here?) + * + * @param db + */ public void setDb(DrawableDB db) { this.db = db; categoryCounts.invalidateAll(); @@ -27,20 +78,13 @@ public class CategoryManager { } /** - * For performance reasons, keep current category counts in memory + * get the number of file with the given {@link Category} + * + * @param cat get the number of files with Category = cat + * + * @return the long the number of files with the given Category */ - private final LoadingCache categoryCounts = CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper)); - - /** - * - * @param cat the value of cat - * @param drawableDB the value of drawableDB - * - * @return the long - * - * @throws TskCoreException - */ - public long getCategoryCount(Category cat) throws TskCoreException { + public long getCategoryCount(Category cat) { if (cat == Category.ZERO) { // Keeping track of the uncategorized files is a bit tricky while ingest // is going on, so always use the list of file IDs we already have along with the @@ -53,11 +97,10 @@ public class CategoryManager { } /** + * increment the cached value for the number of files with the given + * {@link Category} * - * @param cat the value of cat - * @param drawableDB the value of drawableDB - * - * @throws TskCoreException + * @param cat the Category to increment */ public void incrementCategoryCount(Category cat) { if (cat != Category.ZERO) { @@ -66,11 +109,10 @@ public class CategoryManager { } /** + * decrement the cached value for the number of files with the given + * {@link Category} * - * @param cat the value of cat - * @param drawableDB the value of drawableDB - * - * @throws TskCoreException + * @param cat the Category to decrement */ public void decrementCategoryCount(Category cat) { if (cat != Category.ZERO) { @@ -79,15 +121,20 @@ public class CategoryManager { } /** + * helper method that looks up the number of files with the given Category + * from the db and wraps it in a long adder to use in the cache * - * @param t the value of t - * @param drawableDB the value of drawableDB + * + * @param cat the Category to count + * + * @return a LongAdder whose value is set to the number of file with the + * given Category */ - private LongAdder getCategoryCountHelper(Category t) { + private LongAdder getCategoryCountHelper(Category cat) { LongAdder longAdder = new LongAdder(); longAdder.decrement(); try { - longAdder.add(db.getCategoryCount(t)); + longAdder.add(db.getCategoryCount(cat)); longAdder.increment(); } catch (IllegalStateException ex) { LOGGER.log(Level.WARNING, "Case closed while getting files"); @@ -95,18 +142,31 @@ public class CategoryManager { return longAdder; } - public void fireChange(Collection ids) { - - categoryEventBus.post(new CategoryChangeEvent(ids)); - + /** + * fire a CategoryChangeEvent with the given fileIDs + * + * @param fileIDs + */ + public void fireChange(Collection fileIDs) { + categoryEventBus.post(new CategoryChangeEvent(fileIDs)); } - public void registerListener(Object aThis) { - categoryEventBus.register(aThis); + /** + * register an object to receive CategoryChangeEvents + * + * @param listner + */ + public void registerListener(Object listner) { + categoryEventBus.register(listner); } - public void unregisterListener(Object aThis) { - categoryEventBus.unregister(aThis); + /** + * unregister an object from receiving CategoryChangeEvents + * + * @param listener + */ + public void unregisterListener(Object listener) { + categoryEventBus.unregister(listener); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index e35b67bf1a..4c2ce2c691 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-14 Basis Technology Corp. + * Copyright 2013-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,6 +39,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; +import javax.annotation.Nonnull; import javax.annotation.concurrent.GuardedBy; import javax.swing.SortOrder; import org.apache.commons.lang3.StringUtils; @@ -67,10 +68,10 @@ import org.sqlite.SQLiteJDBCLoader; * 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} + * TODO: Creating an abstract base class for sqlite databases may make sense in + * the future. see also {@link EventsDB} in the timeline viewer. */ -public class DrawableDB { +public final class DrawableDB { private static final java.util.logging.Logger LOGGER = Logger.getLogger(DrawableDB.class.getName()); @@ -129,9 +130,7 @@ public class DrawableDB { */ private final HashSet updateListeners = new HashSet<>(); - private GroupManager manager; - - private ImageGalleryController controller; + private GroupManager groupManager; private final Path dbPath; @@ -148,6 +147,7 @@ public class DrawableDB { LOGGER.log(Level.SEVERE, "Failed to load sqlite JDBC driver", ex); } } + private final SleuthkitCase tskCase; //////////////general database logic , mostly borrowed from sleuthkitcase /** @@ -196,11 +196,11 @@ public class DrawableDB { * * @throws SQLException if there is problem creating or configuring the db */ - private DrawableDB(Path dbPath) throws SQLException, ExceptionInInitializerError, IOException { - + private DrawableDB(Path dbPath, SleuthkitCase tskCase) throws SQLException, ExceptionInInitializerError, IOException { this.dbPath = dbPath; + this.tskCase = tskCase; Files.createDirectories(dbPath.getParent()); - if (initializeDB()) { + if (initializeDBSchema()) { updateFileStmt = prepareStatement( "INSERT OR REPLACE INTO drawable_files (obj_id , path, name, created_time, modified_time, make, model, analyzed) " + "VALUES (?,?,?,?,?,?,?,?)"); @@ -230,9 +230,11 @@ public class DrawableDB { insertHashHitStmt = prepareStatement("insert or ignore into hash_set_hits (hash_set_id, obj_id) values (?,?)"); + initializeImageList(); } else { throw new ExceptionInInitializerError(); } + } /** @@ -282,14 +284,10 @@ public class DrawableDB { * * @return */ - public static DrawableDB getDrawableDB(Path dbPath, ImageGalleryController controller) { + public static DrawableDB getDrawableDB(Path dbPath, SleuthkitCase tskCase) { try { - Path dbFilePath = dbPath.resolve("drawable.db"); - - DrawableDB drawableDB = new DrawableDB(dbFilePath); - drawableDB.controller = controller; - return drawableDB; + return new DrawableDB(dbPath.resolve("drawable.db"), tskCase); } catch (SQLException ex) { LOGGER.log(Level.SEVERE, "sql error creating database connection", ex); return null; @@ -341,7 +339,7 @@ public class DrawableDB { * @return the number of rows in the table , count > 0 indicating an * existing table */ - private boolean initializeDB() { + private boolean initializeDBSchema() { try { if (isClosed()) { openDBCon(); @@ -956,7 +954,7 @@ public class DrawableDB { */ private DrawableFile getFileFromID(Long id, boolean analyzed) throws TskCoreException { try { - AbstractFile f = controller.getSleuthKitCase().getAbstractFileById(id); + AbstractFile f = tskCase.getAbstractFileById(id); return DrawableFile.create(f, analyzed, isVideoFile(f)); } catch (IllegalStateException ex) { LOGGER.log(Level.SEVERE, "there is no case open; failed to load file with id: " + id, ex); @@ -974,7 +972,7 @@ public class DrawableDB { */ public DrawableFile getFileFromID(Long id) throws TskCoreException { try { - AbstractFile f = controller.getSleuthKitCase().getAbstractFileById(id); + AbstractFile f = tskCase.getAbstractFileById(id); return DrawableFile.create(f, areFilesAnalyzed(Collections.singleton(id)), isVideoFile(f)); } catch (IllegalStateException ex) { @@ -988,9 +986,9 @@ public class DrawableDB { if (groupKey.getAttribute().isDBColumn) { switch (groupKey.getAttribute().attrName) { case CATEGORY: - return manager.getFileIDsWithCategory((Category) groupKey.getValue()); + return groupManager.getFileIDsWithCategory((Category) groupKey.getValue()); case TAGS: - return manager.getFileIDsWithTag((TagName) groupKey.getValue()); + return groupManager.getFileIDsWithTag((TagName) groupKey.getValue()); } } List files = new ArrayList<>(); @@ -1131,19 +1129,24 @@ public class DrawableDB { } } - /* - * The following groups of functions are used to store information in memory - * instead of in the database. Due to the change listeners in the GUI, - * this data is requested many, many times when browsing the images, and - * especially when making any changes to things like categories. + /** + * For the given fileID, get the names of all the hashsets that the file is + * in. * - * I don't like having multiple copies of the data, but these were causing - * major bottlenecks when they were all database lookups. + * @param fileID the fileID to file all the hash sets for + * + * @return a set of names, each of which is a hashset that the given file is + * in. + * + * + * //TODO: why does this go to the SKC? don't we already have this in =fo + * in the drawable db? */ - Set getHashSetsForFile(Long id) { + @Nonnull + Set getHashSetsForFile(long fileID) { try { Set hashNames = new HashSet<>(); - List arts = ImageGalleryController.getDefault().getSleuthKitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, id); + List arts = ImageGalleryController.getDefault().getSleuthKitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, fileID); for (BlackboardArtifact a : arts) { List attrs = a.getAttributes(); for (BlackboardAttribute attr : attrs) { @@ -1161,38 +1164,38 @@ public class DrawableDB { /** * For performance reasons, keep a list of all file IDs currently in the - * drawable database. - * Otherwise the database is queried many times to retrieve the same data. + * drawable database. Otherwise the database is queried many times to + * retrieve the same data. */ @GuardedBy("fileIDlist") - private final Set fileIDlist = new HashSet<>(); + private final Set fileIDsInDB = new HashSet<>(); - public boolean isDrawableFile(Long id) { - synchronized (fileIDlist) { - return fileIDlist.contains(id); + public boolean isInDB(Long id) { + synchronized (fileIDsInDB) { + return fileIDsInDB.contains(id); } } - public void addImageFileToList(Long id) { - synchronized (fileIDlist) { - fileIDlist.add(id); + private void addImageFileToList(Long id) { + synchronized (fileIDsInDB) { + fileIDsInDB.add(id); } } - public void removeImageFileFromList(Long id) { - synchronized (fileIDlist) { - fileIDlist.remove(id); + private void removeImageFileFromList(Long id) { + synchronized (fileIDsInDB) { + fileIDsInDB.remove(id); } } public int getNumberOfImageFilesInList() { - synchronized (fileIDlist) { - return fileIDlist.size(); + synchronized (fileIDsInDB) { + return fileIDsInDB.size(); } } - public void initializeImageList() { - synchronized (fileIDlist) { + private void initializeImageList() { + synchronized (fileIDsInDB) { dbReadLock(); try { Statement stmt = con.createStatement(); @@ -1208,12 +1211,36 @@ public class DrawableDB { } } - public long getCategoryCount(Category t) { + /** + * For performance reasons, keep the file type in memory + */ + private final Map videoFileMap = new ConcurrentHashMap<>(); + + public boolean isVideoFile(AbstractFile f) { + return videoFileMap.computeIfAbsent(f, ImageGalleryModule::isVideoFile); + } + + /** + * get the number of files with the given category. + * + * NOTE: although the category data is stored in autopsy as Tags, this + * method is provided on DrawableDb to provide a single point of access for + * ImageGallery data. + * + * //TODO: think about moving this and similar methods that don't actually + * get their data form the drawabledb to a layer wrapping the drawable db: + * something like ImageGalleryCaseData? + * + * @param cat the category to count the number of files for + * + * @return the number of the with the given category + */ + public long getCategoryCount(Category cat) { try { - return Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(t.getTagName()).stream() + return Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(cat.getTagName()).stream() .map(ContentTag::getContent) .map(Content::getId) - .filter(this::isDrawableFile) + .filter(this::isInDB) .count(); } catch (IllegalStateException ex) { @@ -1224,15 +1251,6 @@ public class DrawableDB { return -1; } - /** - * For performance reasons, keep the file type in memory - */ - private final Map videoFileMap = new ConcurrentHashMap<>(); - - public boolean isVideoFile(AbstractFile f) throws TskCoreException { - return videoFileMap.computeIfAbsent(f, ImageGalleryModule::isVideoFile); - } - /** * inner class that can reference access database connection */ diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java index ca4c18d84c..1ca547c81e 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java @@ -6,31 +6,66 @@ import com.google.common.cache.LoadingCache; import java.util.Set; /** - * + * Manages a cache of hashset hits as a map from fileID to hashset names. + * Initial/invalid values are loaded from the backing DrawableDB */ public class HashSetManager { + /** The db that initial values are loaded from. */ private DrawableDB db = null; + /** the internal cache from fileID to a set of hashset names. */ + 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(); } - private final LoadingCache> hashSetCache = CacheBuilder.newBuilder().build(CacheLoader.from(this::getHashSetsForFileHelper)); - private Set getHashSetsForFileHelper(Long id) { - return db.getHashSetsForFile(id); + /** + * helper method to load hashset hits for the given fileID from the db + * + * @param fileID + * + * @return the names of the hashsets the given fileID is in + */ + private Set getHashSetsForFileHelper(long fileID) { + return db.getHashSetsForFile(fileID); } - public boolean isInHashSet(Long id) { - return hashSetCache.getUnchecked(id).isEmpty() == false; + /** + * is the given fileID in any hashset + * + * @param fileID + * + * @return true if the file is in any hashset + */ + public boolean isInAnyHashSet(long fileID) { + return getHashSetsForFile(fileID).isEmpty() == false; } - public Set getHashSetsForFile(Long id) { - return hashSetCache.getUnchecked(id); + /** + * get the names of the hash sets the given fileId is in + * + * @param fileID + * + * @return a set containging the names of the hash sets for the given file + */ + public Set getHashSetsForFile(long fileID) { + return hashSetCache.getUnchecked(fileID); } - public void invalidateHashSetsForFile(Long id) { - hashSetCache.invalidate(id); + /** + * invalidate the cached hashset names for the given fileID + * + * @param fileID the fileID to invalidate in the cache + */ + public void invalidateHashSetsForFile(long fileID) { + hashSetCache.invalidate(fileID); } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/DrawableGroup.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/DrawableGroup.java index 41b972e695..445b336ba9 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/DrawableGroup.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/DrawableGroup.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,7 +47,7 @@ public class DrawableGroup implements Comparable { private final ReadOnlyBooleanWrapper seen = new ReadOnlyBooleanWrapper(false); synchronized public ObservableList fileIds() { - return fileIDs; + return FXCollections.unmodifiableObservableList(fileIDs); } final public GroupKey groupKey; @@ -82,23 +82,23 @@ public class DrawableGroup implements Comparable { } /** - * Call to indicate that an image has been added or removed from the group, - * so the hash counts may not longer be accurate. + * Call to indicate that an file has been added or removed from the group, + * so the hash counts may no longer be accurate. */ - synchronized public void invalidateHashSetHitsCount() { + synchronized private void invalidateHashSetHitsCount() { hashSetHitsCount = -1; } + /** + * @return the number of files in this group that have hash set hits + */ synchronized public long getHashSetHitsCount() { - //TODO: use the drawable db for this ? -jm if (hashSetHitsCount < 0) { try { - hashSetHitsCount - = fileIDs.stream() - .map(fileID -> ImageGalleryController.getDefault().getHashSetManager().isInHashSet(fileID)) + hashSetHitsCount = fileIDs.stream() + .map(fileID -> ImageGalleryController.getDefault().getHashSetManager().isInAnyHashSet(fileID)) .filter(Boolean::booleanValue) .count(); - } catch (IllegalStateException | NullPointerException ex) { LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()"); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java index ed4f71dc1b..1af4c4ea71 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/grouping/GroupManager.java @@ -435,7 +435,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { switch (groupBy.attrName) { //these cases get special treatment case CATEGORY: - values = (List) Category.valuesList(); + values = (List) Arrays.asList(Category.values()); break; case TAGS: values = (List) Case.getCurrentCase().getServices().getTagsManager().getTagNamesInUse().stream() @@ -446,7 +446,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { values = (List) Arrays.asList(false, true); break; case HASHSET: - TreeSet names = new TreeSet<>((Set) db.getHashSetNames()); + TreeSet names = new TreeSet<>((Collection) db.getHashSetNames()); values = new ArrayList<>(names); break; default: @@ -456,8 +456,8 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { return values; } catch (TskCoreException ex) { - LOGGER.log(Level.WARNING, "TSK error getting list of type " + groupBy.getDisplayName()); - return new ArrayList(); + LOGGER.log(Level.WARNING, "TSK error getting list of type {0}", groupBy.getDisplayName()); + return Collections.emptyList(); } } @@ -489,7 +489,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { for (TagName tn : tns) { List contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(tn); for (ContentTag ct : contentTags) { - if (ct.getContent() instanceof AbstractFile && db.isDrawableFile(((AbstractFile) ct.getContent()).getId())) { + if (ct.getContent() instanceof AbstractFile && db.isInDB(ct.getContent().getId())) { files.add(ct.getContent().getId()); } } @@ -501,8 +501,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { List files = new ArrayList<>(); List contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(category.getTagName()); for (ContentTag ct : contentTags) { - if (ct.getContent() instanceof AbstractFile && db.isDrawableFile(((AbstractFile) ct.getContent()).getId())) { - + if (ct.getContent() instanceof AbstractFile && db.isInDB(ct.getContent().getId())) { files.add(ct.getContent().getId()); } } @@ -520,7 +519,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener { List files = new ArrayList<>(); List contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(tagName); for (ContentTag ct : contentTags) { - if (ct.getContent() instanceof AbstractFile && db.isDrawableFile(((AbstractFile) ct.getContent()).getId())) { + if (ct.getContent() instanceof AbstractFile && db.isInDB(ct.getContent().getId())) { files.add(ct.getContent().getId()); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java index 503976d5f4..7a9cba6011 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableTile.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-15 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,7 @@ */ package org.sleuthkit.autopsy.imagegallery.gui; -import java.net.URL; import java.util.Objects; -import java.util.ResourceBundle; import java.util.logging.Level; import javafx.application.Platform; import javafx.fxml.FXML; @@ -35,17 +33,17 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.TagUtils; -import static org.sleuthkit.autopsy.imagegallery.gui.SingleDrawableViewBase.globalSelectionModel; +import static org.sleuthkit.autopsy.imagegallery.gui.DrawableViewBase.globalSelectionModel; /** - * GUI component that represents a single image as a tile with an icon, a label + * GUI component that represents a single image as a tile with an icon, a label, * a color coded border and possibly other controls. Designed to be in a * {@link GroupPane}'s TilePane or SlideShow. * * * TODO: refactor this to extend from {@link Control}? -jm */ -public class DrawableTile extends SingleDrawableViewBase implements TagUtils.TagListener { +public class DrawableTile extends DrawableViewBase implements TagUtils.TagListener { private static final DropShadow LAST_SELECTED_EFFECT = new DropShadow(10, Color.BLUE); @@ -57,12 +55,6 @@ public class DrawableTile extends SingleDrawableViewBase implements TagUtils.Tag @FXML private ImageView imageView; - @FXML - private ResourceBundle resources; - - @FXML - private URL location; - @Override protected void disposeContent() { //no-op @@ -105,6 +97,9 @@ public class DrawableTile extends SingleDrawableViewBase implements TagUtils.Tag imageView.setImage(null); } + /** + * {@inheritDoc } + */ @Override protected void updateSelectionState() { super.updateSelectionState(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java index 65224c9f26..566fcb079b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableView.java @@ -16,11 +16,14 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.imagegallery.TagUtils; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent; +import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; /** - * TODO: extract common interface out of {@link SingleImageView} and - * {@link MetaDataPane} + * Interface for classes that are views of a single DrawableFile. Implementation + * of DrawableView must be registered with {@link CategoryManager#registerListener(java.lang.Object) + * } to have there {@link DrawableView#handleCategoryChanged(org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent) + * } method invoked */ public interface DrawableView extends TagUtils.TagListener { @@ -53,6 +56,14 @@ public interface DrawableView extends TagUtils.TagListener { Long getFileID(); + /** + * 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 + * + * @param evt the CategoryChangeEvent to handle + */ @Subscribe void handleCategoryChanged(CategoryChangeEvent evt); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableViewBase.java similarity index 96% rename from ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java rename to ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableViewBase.java index 35004ac587..6792d6d38f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SingleDrawableViewBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/DrawableViewBase.java @@ -89,9 +89,9 @@ import org.sleuthkit.datamodel.TskCoreException; * of {@link DrawableView}s should implement the interface directly * */ -public abstract class SingleDrawableViewBase extends AnchorPane implements DrawableView { +public abstract class DrawableViewBase extends AnchorPane implements DrawableView { - private static final Logger LOGGER = Logger.getLogger(SingleDrawableViewBase.class.getName()); + private static final Logger LOGGER = Logger.getLogger(DrawableViewBase.class.getName()); private static final Border UNSELECTED_BORDER = new Border(new BorderStroke(Color.GRAY, BorderStrokeStyle.SOLID, new CornerRadii(2), new BorderWidths(3))); @@ -142,11 +142,11 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa protected Long fileID; /** - * the groupPane this {@link SingleDrawableViewBase} is embedded in + * the groupPane this {@link DrawableViewBase} is embedded in */ protected GroupPane groupPane; - protected SingleDrawableViewBase() { + protected DrawableViewBase() { globalSelectionModel.getSelected().addListener((Observable observable) -> { updateSelectionState(); @@ -187,7 +187,7 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa groupContextMenu.hide(); } contextMenu = buildContextMenu(); - contextMenu.show(SingleDrawableViewBase.this, t.getScreenX(), t.getScreenY()); + contextMenu.show(DrawableViewBase.this, t.getScreenX(), t.getScreenY()); break; } @@ -386,10 +386,14 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa } } + /** + * update the visual representation of the selection state of this + * DrawableView + */ protected void updateSelectionState() { final boolean selected = globalSelectionModel.isSelected(fileID); Platform.runLater(() -> { - SingleDrawableViewBase.this.setBorder(selected ? SELECTED_BORDER : UNSELECTED_BORDER); + setBorder(selected ? SELECTED_BORDER : UNSELECTED_BORDER); }); } @@ -399,6 +403,7 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa } @Subscribe + @Override public void handleCategoryChanged(CategoryChangeEvent evt) { if (evt.getIds().contains(fileID)) { updateCategoryBorder(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GroupPane.java index 00315821ab..ddca55e4a6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/GroupPane.java @@ -124,71 +124,76 @@ import org.sleuthkit.datamodel.TskCoreException; * both a {@link GridView} based view and a {@link SlideShowView} view by * swapping out its internal components. * - * TODO: review for synchronization issues. TODO: Extract the The GridView - * instance to a separate class analogous to the SlideShow + * + * TODO: Extract the The GridView instance to a separate class analogous to the + * SlideShow. Move selection model into controlsfx GridView and submit pull + * request to them. + * https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview + * + * */ public class GroupPane extends BorderPane implements GroupView { - + private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName()); - + 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 static final FileIDSelectionModel globalSelectionModel = FileIDSelectionModel.getInstance(); - + private final Back backAction; - + private final Forward forwardAction; - + @FXML private SplitMenuButton grpCatSplitMenu; - + @FXML private SplitMenuButton grpTagSplitMenu; - + @FXML private ToolBar headerToolBar; - + @FXML private SegmentedButton segButton; - + private SlideShowView slideShowPane; - + @FXML private ToggleButton slideShowToggle; - + @FXML private Region spacer; - + @FXML private GridView gridView; - + @FXML private ToggleButton tileToggle; - + @FXML private Button nextButton; - + @FXML private Button backButton; - + @FXML private Button forwardButton; - + @FXML private Label groupLabel; - + private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler(); - + private final NextUnseenGroup nextGroupAction; - + private final ImageGalleryController controller; - + private ContextMenu contextMenu; - + private Integer selectionAnchorIndex; /** @@ -209,7 +214,7 @@ public class GroupPane extends BorderPane implements GroupView { */ @ThreadConfined(type = ThreadType.JFX) private final Map cellMap = new HashMap<>(); - + private final InvalidationListener filesSyncListener = (observable) -> { final String header = getHeaderString(); final List fileIds = getGrouping().fileIds(); @@ -218,7 +223,7 @@ public class GroupPane extends BorderPane implements GroupView { groupLabel.setText(header); }); }; - + public GroupPane(ImageGalleryController controller) { this.controller = controller; nextGroupAction = new NextUnseenGroup(controller); @@ -226,7 +231,7 @@ public class GroupPane extends BorderPane implements GroupView { forwardAction = new Forward(controller); FXMLConstructor.construct(this, "GroupPane.fxml"); } - + public void activateSlideShowViewer(Long slideShowFileId) { groupViewMode.set(GroupViewMode.SLIDE_SHOW); @@ -244,9 +249,9 @@ public class GroupPane extends BorderPane implements GroupView { setCenter(slideShowPane); slideShowPane.requestFocus(); } - + public void activateTileViewer() { - + groupViewMode.set(GroupViewMode.TILE); setCenter(gridView); gridView.requestFocus(); @@ -255,11 +260,11 @@ public class GroupPane extends BorderPane implements GroupView { } this.scrollToFileID(globalSelectionModel.lastSelectedProperty().get()); } - + public DrawableGroup getGrouping() { return grouping.get(); } - + private MenuItem createGrpCatMenuItem(final Category cat) { final MenuItem menuItem = new MenuItem(cat.getDisplayName(), new ImageView(DrawableAttribute.CATEGORY.getIcon())); menuItem.setOnAction(new EventHandler() { @@ -267,14 +272,14 @@ public class GroupPane extends BorderPane implements GroupView { public void handle(ActionEvent t) { Set fileIdSet = new HashSet<>(getGrouping().fileIds()); new CategorizeAction().addTagsToFiles(cat.getTagName(), "", fileIdSet); - + grpCatSplitMenu.setText(cat.getDisplayName()); grpCatSplitMenu.setOnAction(this); } }); return menuItem; } - + private MenuItem createGrpTagMenuItem(final TagName tn) { final MenuItem menuItem = new MenuItem(tn.getDisplayName(), new ImageView(DrawableAttribute.TAGS.getIcon())); menuItem.setOnAction(new EventHandler() { @@ -282,14 +287,14 @@ public class GroupPane extends BorderPane implements GroupView { public void handle(ActionEvent t) { Set fileIdSet = new HashSet<>(getGrouping().fileIds()); AddDrawableTagAction.getInstance().addTagsToFiles(tn, "", fileIdSet); - + grpTagSplitMenu.setText(tn.getDisplayName()); grpTagSplitMenu.setOnAction(this); } }); return menuItem; } - + private void selectAllFiles() { globalSelectionModel.clearAndSelectAll(getGrouping().fileIds()); } @@ -300,11 +305,11 @@ public class GroupPane extends BorderPane implements GroupView { : StringUtils.defaultIfBlank(getGrouping().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()) + " -- " + getGrouping().getHashSetHitsCount() + " hash set hits / " + getGrouping().getSize() + " files"; } - + ContextMenu getContextMenu() { return contextMenu; } - + ReadOnlyObjectProperty grouping() { return grouping.getReadOnlyProperty(); } @@ -336,7 +341,7 @@ public class GroupPane extends BorderPane implements GroupView { //configure toolbar properties HBox.setHgrow(spacer, Priority.ALWAYS); spacer.setMinWidth(Region.USE_PREF_SIZE); - + ArrayList grpTagMenues = new ArrayList<>(); for (final TagName tn : TagUtils.getNonCategoryTagNames()) { MenuItem menuItem = createGrpTagMenuItem(tn); @@ -350,7 +355,7 @@ public class GroupPane extends BorderPane implements GroupView { } grpTagSplitMenu.setGraphic(new ImageView(DrawableAttribute.TAGS.getIcon())); grpTagSplitMenu.getItems().setAll(grpTagMenues); - + ArrayList grpCategoryMenues = new ArrayList<>(); for (final Category cat : Category.values()) { MenuItem menuItem = createGrpCatMenuItem(cat); @@ -360,7 +365,7 @@ public class GroupPane extends BorderPane implements GroupView { grpCatSplitMenu.setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon())); grpCatSplitMenu.getItems().setAll(grpCategoryMenues); grpCatSplitMenu.setOnAction(createGrpCatMenuItem(Category.FIVE).getOnAction()); - + Runnable syncMode = () -> { switch (groupViewMode.get()) { case SLIDE_SHOW: @@ -376,7 +381,7 @@ public class GroupPane extends BorderPane implements GroupView { groupViewMode.addListener((o) -> { syncMode.run(); }); - + slideShowToggle.toggleGroupProperty().addListener((o) -> { slideShowToggle.getToggleGroup().selectedToggleProperty().addListener((observable, oldToggle, newToggle) -> { if (newToggle == null) { @@ -389,34 +394,34 @@ public class GroupPane extends BorderPane implements GroupView { slideShowToggle.setOnAction((ActionEvent t) -> { activateSlideShowViewer(globalSelectionModel.lastSelectedProperty().get()); }); - + tileToggle.setOnAction((ActionEvent t) -> { activateTileViewer(); }); - + controller.viewState().addListener((ObservableValue observable, GroupViewState oldValue, GroupViewState newValue) -> { setViewState(newValue); }); - + addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler); gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler() { - + private ContextMenu buildContextMenu() { ArrayList menuItems = new ArrayList<>(); - + menuItems.add(CategorizeAction.getPopupMenu()); - + menuItems.add(AddDrawableTagAction.getInstance().getPopupMenu()); - + 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())); } } @@ -429,12 +434,12 @@ public class GroupPane extends BorderPane implements GroupView { }); }); 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()) { @@ -454,7 +459,7 @@ public class GroupPane extends BorderPane implements GroupView { if (contextMenu == null) { contextMenu = buildContextMenu(); } - + contextMenu.hide(); contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY()); t.consume(); @@ -462,7 +467,7 @@ public class GroupPane extends BorderPane implements GroupView { } } }); - + ActionUtils.configureButton(nextGroupAction, nextButton); final EventHandler onAction = nextButton.getOnAction(); nextButton.setOnAction((ActionEvent event) -> { @@ -470,10 +475,10 @@ public class GroupPane extends BorderPane implements GroupView { nextButton.setEffect(null); onAction.handle(event); }); - + ActionUtils.configureButton(forwardAction, forwardButton); ActionUtils.configureButton(backAction, backButton); - + nextGroupAction.disabledProperty().addListener((ObservableValue observable, Boolean oldValue, Boolean newValue) -> { nextButton.setEffect(newValue ? null : DROP_SHADOW); if (newValue == false) { @@ -489,29 +494,29 @@ public class GroupPane extends BorderPane implements GroupView { if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW) { slideShowPane.setFile(newFileId); } else { - + scrollToFileID(newFileId); } }); - + setViewState(controller.viewState().get()); } - + @ThreadConfined(type = ThreadType.JFX) private void scrollToFileID(final Long newFileID) { 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); @@ -527,10 +532,6 @@ public class GroupPane extends BorderPane implements GroupView { .max().getAsInt(); //[minIndex, maxIndex] is the range of indexes in the fileIDs list that are currently displayed - if (minIndex < 0) { - return; - } - if (selectedIndex < minIndex) { scrollBar.decrement(); } else if (selectedIndex > maxIndex) { @@ -542,14 +543,14 @@ public class GroupPane extends BorderPane implements GroupView { } 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()) { @@ -570,7 +571,7 @@ public class GroupPane extends BorderPane implements GroupView { if (nonNull(getGrouping())) { getGrouping().fileIds().removeListener(filesSyncListener); } - + if (isNull(viewState) || isNull(viewState.getGroup())) { this.grouping.set(null); final List fileIds = Collections.emptyList(); @@ -584,13 +585,13 @@ public class GroupPane extends BorderPane implements GroupView { cellMap.values().stream().forEach(DrawableCell::resetItem); } }); - + } else { if (this.grouping.get() != viewState.getGroup()) { this.grouping.set(viewState.getGroup()); - + this.getGrouping().fileIds().addListener(filesSyncListener); - + final String header = getHeaderString(); final ObservableList fileIds = getGrouping().fileIds(); Platform.runLater(() -> { @@ -606,18 +607,18 @@ public class GroupPane extends BorderPane implements GroupView { } } } - + @ThreadConfined(type = ThreadType.JFX) private void resetScrollBar() { getScrollBar().ifPresent((scrollBar) -> { scrollBar.setValue(0); }); } - + private class DrawableCell extends GridCell { - + private final DrawableTile tile = new DrawableTile(GroupPane.this); - + public DrawableCell() { itemProperty().addListener((ObservableValue observable, Long oldValue, Long newValue) -> { if (oldValue != null) { @@ -633,25 +634,25 @@ public class GroupPane extends BorderPane implements GroupView { } } cellMap.put(newValue, DrawableCell.this); - + } }); - + setGraphic(tile); } - + @Override protected void updateItem(Long item, boolean empty) { super.updateItem(item, empty); tile.setFile(item); } - + void resetItem() { updateItem(null, true); tile.setFile(null); } } - + 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); @@ -660,10 +661,10 @@ public class GroupPane extends BorderPane implements GroupView { * arrows) */ private class KeyboardHandler implements EventHandler { - + @Override public void handle(KeyEvent t) { - + if (t.getEventType() == KeyEvent.KEY_PRESSED) { switch (t.getCode()) { case SHIFT: @@ -706,7 +707,7 @@ public class GroupPane extends BorderPane implements GroupView { t.consume(); break; } - + if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) { selectAllFiles(); t.consume(); @@ -740,18 +741,18 @@ public class GroupPane extends BorderPane implements GroupView { } } } - + } - + private void handleArrows(KeyEvent t) { Long lastSelectFileId = globalSelectionModel.lastSelectedProperty().get(); - + int lastSelectedIndex = lastSelectFileId != null ? grouping.get().fileIds().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 @@ -770,7 +771,7 @@ public class GroupPane extends BorderPane implements GroupView { } } } - + @ThreadConfined(type = ThreadType.JFX) private Optional getScrollBar() { if (gridView == null || gridView.getSkin() == null) { @@ -778,16 +779,16 @@ public class GroupPane extends BorderPane implements GroupView { } return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); } - + void makeSelection(Boolean shiftDown, Long newFileID) { - + if (shiftDown) { //TODO: do more hear to implement slicker multiselect int endIndex = grouping.get().fileIds().indexOf(newFileID); int startIndex = IntStream.of(grouping.get().fileIds().size(), selectionAnchorIndex, endIndex).min().getAsInt(); endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt(); List subList = grouping.get().fileIds().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().fileIds().size()) + 1); - + globalSelectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()])); globalSelectionModel.select(newFileID); } else { @@ -795,5 +796,5 @@ public class GroupPane extends BorderPane implements GroupView { globalSelectionModel.clearAndSelect(newFileID); } } - + } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java index 6c731aafb5..e5382173c4 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/MetaDataPane.java @@ -20,17 +20,13 @@ package org.sleuthkit.autopsy.imagegallery.gui; import com.google.common.eventbus.Subscribe; import java.io.IOException; -import java.net.URL; import java.util.Arrays; import java.util.Collection; -import java.util.ResourceBundle; import java.util.logging.Level; import java.util.stream.Collectors; import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -47,7 +43,6 @@ import static javafx.scene.layout.Region.USE_COMPUTED_SIZE; import javafx.scene.text.Text; import javafx.util.Pair; import org.apache.commons.lang3.StringUtils; -import org.openide.util.Exceptions; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.TagUtils; @@ -59,7 +54,7 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** - * + * Shows details of the selected file. */ public class MetaDataPane extends AnchorPane implements TagUtils.TagListener, DrawableView { @@ -72,12 +67,6 @@ public class MetaDataPane extends AnchorPane implements TagUtils.TagListener, Dr @FXML private ImageView imageView; - @FXML - private ResourceBundle resources; - - @FXML - private URL location; - @FXML private TableColumn, ? extends Object>, DrawableAttribute> attributeColumn; @@ -133,7 +122,7 @@ public class MetaDataPane extends AnchorPane implements TagUtils.TagListener, Dr .filter((String t) -> t.startsWith(Category.CATEGORY_PREFIX) == false) .collect(Collectors.joining(" ; ", "", ""))); } else { - return new SimpleStringProperty(StringUtils.join((Collection) p.getValue().getValue(), " ; ")); + return new SimpleStringProperty(StringUtils.join((Iterable) p.getValue().getValue(), " ; ")); } }); valueColumn.setPrefWidth(USE_COMPUTED_SIZE); @@ -194,15 +183,8 @@ public class MetaDataPane extends AnchorPane implements TagUtils.TagListener, Dr try { file = controller.getFileFromId(fileID); updateUI(); - - file.categoryProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue ov, Category t, final Category t1) { - updateUI(); - } - }); } catch (TskCoreException ex) { - Exceptions.printStackTrace(ex); + LOGGER.log(Level.WARNING, "Failed to get drawable file from ID", ex); } } } @@ -238,6 +220,7 @@ public class MetaDataPane extends AnchorPane implements TagUtils.TagListener, Dr return imageBorder; } + /** {@inheritDoc } */ @Subscribe @Override public void handleCategoryChanged(CategoryChangeEvent evt) { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java index cec79ebfda..56811ae7a6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SlideShowView.java @@ -64,7 +64,7 @@ import org.sleuthkit.datamodel.TskCoreException; * GroupPane. TODO: Extract a subclass for video files in slideshow mode-jm * TODO: reduce coupling to GroupPane */ -public class SlideShowView extends SingleDrawableViewBase implements TagUtils.TagListener { +public class SlideShowView extends DrawableViewBase implements TagUtils.TagListener { private static final Logger LOGGER = Logger.getLogger(SlideShowView.class.getName()); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java index d027c679a3..6692bf873f 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/SummaryTablePane.java @@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.imagegallery.gui; import com.google.common.eventbus.Subscribe; import java.util.Arrays; -import java.util.logging.Level; import javafx.application.Platform; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; @@ -34,13 +33,10 @@ import static javafx.scene.layout.Region.USE_COMPUTED_SIZE; import javafx.scene.layout.VBox; import javafx.util.Pair; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent; -import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; -import org.sleuthkit.datamodel.TskCoreException; /** * Displays summary statistics (counts) for each group @@ -105,11 +101,7 @@ public class SummaryTablePane extends AnchorPane { final ObservableList> data = FXCollections.observableArrayList(); if (Case.isCaseOpen()) { for (Category cat : Category.values()) { - try { - data.add(new Pair<>(cat, ImageGalleryController.getDefault().getCategoryManager().getCategoryCount(cat))); - } catch (TskCoreException ex) { - Logger.getLogger(SummaryTablePane.class.getName()).log(Level.WARNING, "Error performing category file count"); - } + data.add(new Pair<>(cat, ImageGalleryController.getDefault().getCategoryManager().getCategoryCount(cat))); } } Platform.runLater(() -> { diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java index 445854adee..bf62f89084 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/Toolbar.java @@ -18,9 +18,7 @@ */ package org.sleuthkit.autopsy.imagegallery.gui; -import java.net.URL; import java.util.ArrayList; -import java.util.ResourceBundle; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.Observable; @@ -29,6 +27,7 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.event.ActionEvent; +import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; @@ -44,9 +43,10 @@ import javax.swing.SortOrder; import org.openide.util.Exceptions; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; -import org.sleuthkit.autopsy.imagegallery.ThumbnailCache; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.TagUtils; +import org.sleuthkit.autopsy.imagegallery.ThumbnailCache; +import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.grouping.GroupSortBy; @@ -54,18 +54,12 @@ import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TskCoreException; /** - * Controller for the ToolBar + * Controller for the ToolBar */ public class Toolbar extends ToolBar { private static final int SIZE_SLIDER_DEFAULT = 100; - @FXML - private ResourceBundle resources; - - @FXML - private URL location; - @FXML private ComboBox> groupByBox; @@ -175,14 +169,14 @@ public class Toolbar extends ToolBar { } }); - catSelectedMenuButton.setOnAction(Category.FIVE.createSelCatMenuItem(catSelectedMenuButton).getOnAction()); + catSelectedMenuButton.setOnAction(createSelCatMenuItem(Category.FIVE, catSelectedMenuButton).getOnAction()); catSelectedMenuButton.setText(Category.FIVE.getDisplayName()); catSelectedMenuButton.setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon())); catSelectedMenuButton.showingProperty().addListener((ObservableValue ov, Boolean t, Boolean t1) -> { if (t1) { ArrayList categoryMenues = new ArrayList<>(); for (final Category cat : Category.values()) { - MenuItem menuItem = cat.createSelCatMenuItem(catSelectedMenuButton); + MenuItem menuItem = createSelCatMenuItem(cat, catSelectedMenuButton); categoryMenues.add(menuItem); } catSelectedMenuButton.getItems().setAll(categoryMenues); @@ -230,4 +224,17 @@ public class Toolbar extends ToolBar { private Toolbar() { FXMLConstructor.construct(this, "Toolbar.fxml"); } + + private static MenuItem createSelCatMenuItem(Category cat, final SplitMenuButton catSelectedMenuButton) { + final MenuItem menuItem = new MenuItem(cat.getDisplayName(), new ImageView(DrawableAttribute.CATEGORY.getIcon())); + menuItem.setOnAction(new EventHandler() { + @Override + public void handle(ActionEvent t) { + new CategorizeAction().addTag(cat.getTagName(), ""); + catSelectedMenuButton.setText(cat.getDisplayName()); + catSelectedMenuButton.setOnAction(this); + } + }); + return menuItem; + } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/TreeNodeComparators.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/TreeNodeComparators.java index c0ea6e7813..096dd681a1 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/TreeNodeComparators.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/navpanel/TreeNodeComparators.java @@ -29,29 +29,25 @@ enum TreeNodeComparators implements Comparator>, NonNullCompa ALPHABETICAL("Group Name") { @Override - public int nonNullCompare(TreeItem o1, TreeItem o2) { - + public int nonNullCompare(TreeItem o1, TreeItem o2) { return o1.getValue().getGroup().groupKey.getValue().toString().compareTo(o2.getValue().getGroup().groupKey.getValue().toString()); } }, HIT_COUNT("Hit Count") { @Override public int nonNullCompare(TreeItem o1, TreeItem o2) { - return -Long.compare(o1.getValue().getGroup().getHashSetHitsCount(), o2.getValue().getGroup().getHashSetHitsCount()); } }, FILE_COUNT("Group Size") { @Override public int nonNullCompare(TreeItem o1, TreeItem o2) { - return -Integer.compare(o1.getValue().getGroup().getSize(), o2.getValue().getGroup().getSize()); } }, HIT_FILE_RATIO("Hit Density") { @Override public int nonNullCompare(TreeItem o1, TreeItem o2) { - return -Double.compare(o1.getValue().getGroup().getHashSetHitsCount() / (double) o1.getValue().getGroup().getSize(), o2.getValue().getGroup().getHashSetHitsCount() / (double) o2.getValue().getGroup().getSize()); }