Initial ImageGalleryTopComponent improvements with TODOs

This commit is contained in:
Richard Cordovano 2018-11-29 09:07:04 -05:00
parent 868dc3b95d
commit 93b1e81df2
8 changed files with 545 additions and 488 deletions

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2013-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -183,7 +183,9 @@ public final class ImageGalleryController {
}
ImageGalleryController(@Nonnull Case newCase) throws TskCoreException {
this.autopsyCase = Objects.requireNonNull(newCase);
this.sleuthKitCase = newCase.getSleuthkitCase();
@ -318,17 +320,14 @@ public final class ImageGalleryController {
/**
* reset the state of the controller (eg if the case is closed)
*/
public synchronized void reset() {
logger.info("Closing ImageGalleryControler for case."); //NON-NLS
public synchronized void shutDown() {
selectionModel.clearSelection();
thumbnailCache.clearCache();
historyManager.clear();
groupManager.reset();
shutDownDBExecutor();
drawableDB.close();
dbExecutor = getNewDBExecutor();
//dbExecutor = getNewDBExecutor(); RJCTODO
}
/**

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-18 Basis Technology Corp.
* Copyright 2013-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -22,12 +22,14 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.EnumSet;
import java.util.Set;
import java.util.logging.Level;
import javafx.application.Platform;
import javax.annotation.concurrent.GuardedBy;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
@ -35,11 +37,9 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.IngestManager.IngestJobEvent;
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.FILE_DONE;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
@ -53,71 +53,100 @@ import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/** static definitions, utilities, and listeners for the ImageGallery module */
/**
* This class is reponsible for handling selected application events for the
* image gallery module, managing the image gallery module's per case MVC
* controller and keeping track of the following state: the module name, the
* module output directory and whether or not the ingest gallery module is
* enabled for the current case.
*/
@NbBundle.Messages({"ImageGalleryModule.moduleName=Image Gallery"})
public class ImageGalleryModule {
private static final Logger logger = Logger.getLogger(ImageGalleryModule.class.getName());
private static final String MODULE_NAME = Bundle.ImageGalleryModule_moduleName();
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(
Case.Events.CURRENT_CASE,
Case.Events.DATA_SOURCE_ADDED,
Case.Events.CONTENT_TAG_ADDED,
Case.Events.CONTENT_TAG_DELETED
);
private static final Object controllerLock = new Object();
@GuardedBy("controllerLock")
private static ImageGalleryController controller;
public static ImageGalleryController getController() throws TskCoreException, NoCurrentCaseException {
/**
* Gets the per case image gallery controller for the current case. The
* controller is changed in the case event listener.
*
* @return The image gallery controller for the current case.
*
* @throws TskCoreException If there is a problem creating the controller.
*/
public static ImageGalleryController getController() throws TskCoreException {
synchronized (controllerLock) {
if (controller == null) {
controller = new ImageGalleryController(Case.getCurrentCaseThrows());
try {
Case currentCase = Case.getCurrentCaseThrows();
controller = new ImageGalleryController(currentCase);
} catch (NoCurrentCaseException ex) {
throw new TskCoreException("no current case", ex);
}
}
return controller;
}
}
/**
*
*
* This method is invoked by virtue of the OnStart annotation on the OnStart
* class class
* Sets the implicit exit property attribute of the JavaFX Runtime to false
* and sets up listeners for application events. It is invoked at
* application start up by virtue of the OnStart annotation on the OnStart
* class in this package.
*/
static void onStart() {
Platform.setImplicitExit(false);
logger.info("Setting up ImageGallery listeners"); //NON-NLS
IngestManager.getInstance().addIngestJobEventListener(new IngestJobEventListener());
IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener());
Case.addPropertyChangeListener(new CaseEventListener());
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, new CaseEventListener());
}
/**
* Gets the image gallery module name.
*
* @return The module name,
*/
static String getModuleName() {
return MODULE_NAME;
}
/**
* get the Path to the Case's ImageGallery ModuleOutput subfolder; ie
* ".../[CaseName]/ModuleOutput/Image Gallery/"
* Gets the path to the image gallery module output folder for a given case.
*
* @param theCase the case to get the ImageGallery ModuleOutput subfolder
* for
* @param theCase The case.
*
* @return the Path to the ModuleOuput subfolder for Image Gallery
* @return The path to the image gallery module output folder for the case.
*/
public static Path getModuleOutputDir(Case theCase) {
return Paths.get(theCase.getModuleDirectory(), getModuleName());
}
/** provides static utilities, can not be instantiated */
/**
* Prevents instantiation.
*/
private ImageGalleryModule() {
}
/** is listening enabled for the given case
/**
* Indicates whether or not the image gallery module is enabled for a given
* case.
*
* @param c
* @param theCase The case.
*
* @return true if listening is enabled for the given case, false otherwise
* @return True or false.
*/
static boolean isEnabledforCase(Case c) {
if (c != null) {
String enabledforCaseProp = new PerCaseProperties(c).getConfigSetting(ImageGalleryModule.MODULE_NAME, PerCaseProperties.ENABLED);
static boolean isEnabledforCase(Case theCase) {
if (theCase != null) {
String enabledforCaseProp = new PerCaseProperties(theCase).getConfigSetting(ImageGalleryModule.MODULE_NAME, PerCaseProperties.ENABLED);
return isNotBlank(enabledforCaseProp) ? Boolean.valueOf(enabledforCaseProp) : ImageGalleryPreferences.isEnabledByDefault();
} else {
return false;
@ -125,197 +154,184 @@ public class ImageGalleryModule {
}
/**
* Is the given file 'supported' and not 'known'(nsrl hash hit). If so we
* should include it in {@link DrawableDB} and UI
* Indicates whether or not a given file is of interest to the image gallery
* module (is "drawable") and is not marked as a "known" file (e.g., is not
* a file in the NSRL hash set).
*
* @param abstractFile
* @param file The file.
*
* @return true if the given {@link AbstractFile} is "drawable" and not
* 'known', else false
* @return True if the file is "drawable" and not "known", false otherwise.
*
* @throws
* org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector.FileTypeDetectorInitException
* @throws FileTypeDetectorInitException If there is an error determining
* the type of the file.
*/
public static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws FileTypeDetector.FileTypeDetectorInitException {
private static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws FileTypeDetector.FileTypeDetectorInitException {
return (abstractFile.getKnown() != TskData.FileKnown.KNOWN) && FileTypeUtils.isDrawable(abstractFile);
}
/**
* Listener for IngestModuleEvents
* A listener for ingest module application events.
*/
static private class IngestModuleEventListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
public void propertyChange(PropertyChangeEvent event) {
/*
* If running in "headless" mode, there is no need to process any
* ingest module events during the current session.
*
* Note that this check cannot be done earlier on start up because
* the "headless" property may not have been set yet.
*/
if (RuntimeProperties.runningWithGUI() == false) {
/*
* Running in "headless" mode, no need to process any events.
* This cannot be done earlier because the switch to core
* components inactive may not have been made at start up.
*/
IngestManager.getInstance().removeIngestModuleEventListener(this);
return;
}
/* only process individual files in realtime on the node that is
* running the ingest. on a remote node, image files are processed
* enblock when ingest is complete */
if (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL) {
/*
* Only process individual files and artifacts in "real time" on the
* node that is running the ingest job. On a remote node, image
* files are processed as a group when the ingest job is complete.
*/
if (((AutopsyEvent) event).getSourceType() != AutopsyEvent.SourceType.LOCAL) {
return;
}
// Bail out if the case is closed
ImageGalleryController currentController;
try {
if (controller == null || Case.getCurrentCaseThrows() == null) {
return;
}
} catch (NoCurrentCaseException ex) {
currentController = getController();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Failed to handle %s event", event.getPropertyName()), ex); //NON-NLS
return;
}
if (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName()) == FILE_DONE) {
// getOldValue has fileID getNewValue has Abstractfile
AbstractFile file = (AbstractFile) evt.getNewValue();
if (false == file.isFile()) {
return;
}
try {
ImageGalleryController con = getController();
if (con.isListeningEnabled()) {
String eventType = event.getPropertyName();
switch (IngestManager.IngestModuleEvent.valueOf(eventType)) {
case FILE_DONE:
AbstractFile file = (AbstractFile) event.getNewValue();
if (!file.isFile()) {
return;
}
if (currentController.isListeningEnabled()) {
try {
// Update the entry if it is a picture and not in NSRL
if (isDrawableAndNotKnown(file)) {
con.queueDBTask(new ImageGalleryController.UpdateFileTask(file, controller.getDatabase()));
}
// Remove it from the DB if it is no longer relevant, but had the correct extension
else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) {
/* Doing this check results in fewer tasks queued
* up, and faster completion of db update. This file
* would have gotten scooped up in initial grab, but
* actually we don't need it */
con.queueDBTask(new ImageGalleryController.RemoveFileTask(file, controller.getDatabase()));
currentController.queueDBTask(new ImageGalleryController.UpdateFileTask(file, currentController.getDatabase()));
}
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
logger.log(Level.SEVERE, "Unable to determine if file is drawable and not known. Not making any changes to DB", ex); //NON-NLS
MessageNotifyUtil.Notify.error("Image Gallery Error",
"Unable to determine if file is drawable and not known. Not making any changes to DB. See the logs for details.");
logger.log(Level.SEVERE, String.format("Failed to determine if file is of interest to the image gallery module, ignoring file (obj_id=%d)", file.getId()), ex); //NON-NLS
}
}
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS
}
}
else if (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName()) == DATA_ADDED) {
ModuleDataEvent mde = (ModuleDataEvent) evt.getOldValue();
if (mde.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID()) {
DrawableDB drawableDB = controller.getDatabase();
if (mde.getArtifacts() != null) {
for (BlackboardArtifact art : mde.getArtifacts()) {
drawableDB.addExifCache(art.getObjectID());
break;
case DATA_ADDED:
ModuleDataEvent artifactAddedEvent = (ModuleDataEvent) event.getOldValue();
if (artifactAddedEvent.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID()) {
DrawableDB drawableDB = currentController.getDatabase();
if (artifactAddedEvent.getArtifacts() != null) {
for (BlackboardArtifact art : artifactAddedEvent.getArtifacts()) {
drawableDB.addExifCache(art.getObjectID());
}
}
} else if (artifactAddedEvent.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) {
DrawableDB drawableDB = currentController.getDatabase();
if (artifactAddedEvent.getArtifacts() != null) {
for (BlackboardArtifact art : artifactAddedEvent.getArtifacts()) {
drawableDB.addHashSetCache(art.getObjectID());
}
}
}
}
else if (mde.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) {
DrawableDB drawableDB = controller.getDatabase();
if (mde.getArtifacts() != null) {
for (BlackboardArtifact art : mde.getArtifacts()) {
drawableDB.addHashSetCache(art.getObjectID());
}
}
}
break;
default:
break;
}
}
}
/**
* Listener for case events.
* A listener for case application events.
*/
static private class CaseEventListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
public void propertyChange(PropertyChangeEvent event) {
/*
* If running in "headless" mode, there is no need to process any
* case events during the current session. Note that this check
* cannot be done earlier in onStart because the "headless" property
* may not have been set yet.
*/
if (RuntimeProperties.runningWithGUI() == false) {
/*
* Running in "headless" mode, no need to process any events.
* This cannot be done earlier because the switch to core
* components inactive may not have been made at start up.
*/
Case.removePropertyChangeListener(this);
return;
}
ImageGalleryController con;
try {
con = getController();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
return;
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS
return;
}
switch (Case.Events.valueOf(evt.getPropertyName())) {
case CURRENT_CASE:
synchronized (controllerLock) {
// case has changes: close window, reset everything
SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent);
if (controller != null) {
controller.reset();
}
controller = null;
Case newCase = (Case) evt.getNewValue();
if (newCase != null) {
// a new case has been opened: connect db, groupmanager, start worker thread
try {
controller = new ImageGalleryController(newCase);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error changing case in ImageGallery.", ex);
Case.Events eventType = Case.Events.valueOf(event.getPropertyName());
if (eventType == Case.Events.CURRENT_CASE) {
synchronized (controllerLock) {
if (event.getNewValue() != null && event.getNewValue() instanceof Case) {
/*
* CURRENT_CASE(_OPENED) event. Construct a new Image
* Gallery controller.
*/
Case newCase = (Case) event.getNewValue();
try {
controller = new ImageGalleryController(newCase);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Failed to construct controller for new case", ex);
}
} else if (event.getOldValue() != null && event.getOldValue() instanceof Case) {
/*
* CURRENT_CASE(_CLOSED) event. Shut down the controller
* for the case and close the top component, if it is
* open.
*/
SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent);
controller.shutDown();
controller = null;
}
}
} else {
ImageGalleryController currentController;
try {
currentController = getController();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Failed to handle %s event", event.getPropertyName()), ex); //NON-NLS
return;
}
switch (eventType) {
case DATA_SOURCE_ADDED:
if (((AutopsyEvent) event).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
Content newDataSource = (Content) event.getNewValue();
if (currentController.isListeningEnabled()) {
currentController.getDatabase().insertOrUpdateDataSource(newDataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.DEFAULT);
}
}
}
break;
case DATA_SOURCE_ADDED:
//For a data source added on the local node, prepopulate all file data to drawable database
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
Content newDataSource = (Content) evt.getNewValue();
if (con.isListeningEnabled()) {
controller.getDatabase().insertOrUpdateDataSource(newDataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.DEFAULT);
break;
case CONTENT_TAG_ADDED:
final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) event;
long objId = tagAddedEvent.getAddedTag().getContent().getId();
DrawableDB drawableDB = currentController.getDatabase();
drawableDB.addTagCache(objId);
if (drawableDB.isInDB(objId)) { // RJCTODO: Put in cache before in DB check?
currentController.getTagsManager().fireTagAddedEvent(tagAddedEvent);
}
}
break;
case CONTENT_TAG_ADDED:
final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt;
long objId = tagAddedEvent.getAddedTag().getContent().getId();
// update the cache
DrawableDB drawableDB = controller.getDatabase();
drawableDB.addTagCache(objId);
if (con.getDatabase().isInDB(objId)) {
con.getTagsManager().fireTagAddedEvent(tagAddedEvent);
}
break;
case CONTENT_TAG_DELETED:
final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) evt;
if (con.getDatabase().isInDB(tagDeletedEvent.getDeletedTagInfo().getContentID())) {
con.getTagsManager().fireTagDeletedEvent(tagDeletedEvent);
}
break;
default:
//we don't need to do anything for other events.
break;
break;
case CONTENT_TAG_DELETED:
final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) event;
if (currentController.getDatabase().isInDB(tagDeletedEvent.getDeletedTagInfo().getContentID())) {
currentController.getTagsManager().fireTagDeletedEvent(tagDeletedEvent);
}
break;
default:
logger.log(Level.SEVERE, String.format("Received %s event with no subscription", event.getPropertyName())); //NON-NLS
break;
}
}
}
}
/**
* Listener for Ingest Job events.
* A listener for ingest job application events.
*/
static private class IngestJobEventListener implements PropertyChangeListener {
@ -326,42 +342,54 @@ public class ImageGalleryModule {
"ImageGalleryController.dataSourceAnalyzed.confDlg.title=Image Gallery"
})
@Override
public void propertyChange(PropertyChangeEvent evt) {
IngestJobEvent eventType = IngestJobEvent.valueOf(evt.getPropertyName());
public void propertyChange(PropertyChangeEvent event) {
ImageGalleryController controller;
try {
ImageGalleryController controller = getController();
if (eventType == IngestJobEvent.DATA_SOURCE_ANALYSIS_STARTED) {
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
controller = getController();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Failed to handle %s event", event.getPropertyName()), ex); //NON-NLS
return;
}
String eventType = event.getPropertyName();
switch (IngestManager.IngestJobEvent.valueOf(eventType)) {
case DATA_SOURCE_ANALYSIS_STARTED:
if (((AutopsyEvent) event).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
if (controller.isListeningEnabled()) {
DataSourceAnalysisStartedEvent dataSourceAnalysisStartedEvent = (DataSourceAnalysisStartedEvent) evt;
DataSourceAnalysisStartedEvent dataSourceAnalysisStartedEvent = (DataSourceAnalysisStartedEvent) event;
Content dataSource = dataSourceAnalysisStartedEvent.getDataSource();
controller.getDatabase().insertOrUpdateDataSource(dataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS);
controller.getDatabase().insertOrUpdateDataSource(dataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS);
}
}
} else if (eventType == IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED) {
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
break;
case DATA_SOURCE_ANALYSIS_COMPLETED:
if (((AutopsyEvent) event).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
/*
* This node just completed analysis of a data source.
* Set the state of the local drawables database.
*/
if (controller.isListeningEnabled()) {
DataSourceAnalysisCompletedEvent dataSourceAnalysisCompletedEvent = (DataSourceAnalysisCompletedEvent) evt;
DataSourceAnalysisCompletedEvent dataSourceAnalysisCompletedEvent = (DataSourceAnalysisCompletedEvent) event;
Content dataSource = dataSourceAnalysisCompletedEvent.getDataSource();
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus =
controller.hasFilesWithNoMimetype(dataSource) ?
DrawableDB.DrawableDbBuildStatusEnum.DEFAULT :
DrawableDB.DrawableDbBuildStatusEnum.COMPLETE;
try {
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus
= controller.hasFilesWithNoMimetype(dataSource)
? DrawableDB.DrawableDbBuildStatusEnum.DEFAULT
: DrawableDB.DrawableDbBuildStatusEnum.COMPLETE;
controller.getDatabase().insertOrUpdateDataSource(dataSource.getId(), datasourceDrawableDBStatus);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Failed to query case database to determine drawables database state", ex);
}
}
return;
}
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.REMOTE) {
// A remote node added a new data source and just finished ingest on it.
//drawable db is stale, and if ImageGallery is open, ask user what to do
} else {
/*
* A remote node just completed analysis of a data
* source. The local drawables database is therefore
* stale. If the image gallery top component is open,
* give the user an opportunity to update the drawables
* database now.
*/
controller.setStale(true);
if (controller.isListeningEnabled()) {
SwingUtilities.invokeLater(() -> {
@ -384,12 +412,9 @@ public class ImageGalleryModule {
});
}
}
}
}
catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS
break;
default:
break;
}
}
}

View File

@ -212,7 +212,7 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
} catch (NoCurrentCaseException ex) {
// It's not an error if there's no case open
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS
logger.log(Level.SEVERE, "Failed to get image gallery controller", ex); //NON-NLS
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2018 Basis Technology Corp.
* Copyright 2015-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -49,6 +49,7 @@ import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Modality;
import javax.annotation.concurrent.GuardedBy;
import javax.swing.SwingUtilities;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import static org.apache.commons.lang3.ObjectUtils.notEqual;
@ -61,7 +62,6 @@ import org.openide.windows.Mode;
import org.openide.windows.RetainLocation;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
@ -81,16 +81,11 @@ import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Top component which displays ImageGallery interface.
*
* Although ImageGallery doesn't currently use the explorer manager, this
* TopComponent provides one through the getExplorerManager method. However,
* this does not seem to function correctly unless a Netbeans provided explorer
* view is present in the TopComponenet, even if it is invisible/ zero sized
* The singleton Image Gallery top component.
*/
@TopComponent.Description(
preferredID = "ImageGalleryTopComponent",
//iconBase = "org/sleuthkit/autopsy/imagegallery/images/lightbulb.png" use this to put icon in window title area,
//iconBase = "org/sleuthkit/autopsy/imagegallery/images/lightbulb.png", /*use this to put icon in window title area*/
persistenceType = TopComponent.PERSISTENCE_NEVER)
@RetainLocation("ImageGallery")
@TopComponent.Registration(mode = "ImageGallery", openAtStartup = false)
@ -101,13 +96,15 @@ import org.sleuthkit.datamodel.TskCoreException;
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public final class ImageGalleryTopComponent extends TopComponent implements ExplorerManager.Provider, Lookup.Provider {
public final static String PREFERRED_ID = "ImageGalleryTopComponent"; // NON-NLS
private static final long serialVersionUID = 1L;
public final static String PREFERRED_ID = "ImageGalleryTopComponent"; // NON-NLS // RJCTODO: This should not be public, clients should call getTopComponent instead
private static final Logger logger = Logger.getLogger(ImageGalleryTopComponent.class.getName());
private static volatile boolean topComponentInitialized = false;
private final ExplorerManager em = new ExplorerManager();
private final Lookup lookup = (ExplorerUtils.createLookup(em, getActionMap()));
private final Object controllerLock = new Object();
@GuardedBy("controllerLock")
private ImageGalleryController controller;
private SplitPane splitPane;
@ -125,148 +122,86 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
private final Region infoOverLayBackground = new TranslucentRegion();
/**
* Returns whether the ImageGallery window is open or not.
* Queries whether the singleton Image Gallery top component's window is
* open. Note that calling this method will cause the top component to be
* constructed if it does not already exist.
*
* @return true, if Image gallery is opened, false otherwise
* @return True or false.
*/
public static boolean isImageGalleryOpen() {
final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (topComponent != null) {
return topComponent.isOpened();
}
return false;
return getTopComponent().isOpened();
}
/**
* Returns the top component window.
* Gets the singleton Image Gallery top component. Note that calling this
* method will cause the top component to be constructed if it does not
* already exist.
*
* @return Image gallery top component window, null if it's not open
* @return The top component.
*/
public static TopComponent getTopComponent() {
return WindowManager.getDefault().findTopComponent(PREFERRED_ID);
public static ImageGalleryTopComponent getTopComponent() {
return (ImageGalleryTopComponent) WindowManager.getDefault().findTopComponent(PREFERRED_ID);
}
/**
* Open the ImageGalleryTopComponent.
* Creates the Image Gallery top component if it does not already exist and
* opens its window.
*
* @throws NoCurrentCaseException If there is no case open.
* @throws TskCoreException If there is a problem accessing the case
* db.
* @throws TskCoreException If there is a problem opening the top component.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
@Messages({
"ImageGalleryTopComponent.chooseDataSourceDialog.headerText=Choose a data source to view.",
"ImageGalleryTopComponent.chooseDataSourceDialog.contentText=Data source:",
"ImageGalleryTopComponent.chooseDataSourceDialog.all=All",
"ImageGalleryTopComponent.chooseDataSourceDialog.titleText=Image Gallery",})
public static void openTopComponent() throws NoCurrentCaseException, TskCoreException {
// This creates the top component and adds the UI widgets (via the constructor) if it has not yet been opened
final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (topComponent == null) {
return;
}
public static void openTopComponent() throws TskCoreException {
final ImageGalleryTopComponent topComponent = getTopComponent();
if (topComponent.isOpened()) {
showTopComponent(topComponent);
return;
showTopComponent();
} else {
topComponent.getCurrentControllerAndOpen();
}
// Wait until the FX UI has been created. This way, we can always
// show the gray progress screen
// TODO: do this in a more elegant way.
while (topComponentInitialized == false) {
}
ImageGalleryController controller = ImageGalleryModule.getController();
ImageGalleryTopComponent igTopComponent = (ImageGalleryTopComponent) topComponent;
igTopComponent.setController(controller);
//gather information about datasources and the groupmanager in a bg thread.
new Thread(new Task<Void>() {
@Override
protected Void call() throws Exception {
Map<DataSource, Boolean> dataSourcesTooManyFiles = new HashMap<>();
List<DataSource> dataSources = controller.getSleuthKitCase().getDataSources();
/*
* If there is only one datasource or the grouping is already
* set to something other than path , don't bother to ask for
* datasource.
*/
if (dataSources.size() <= 1
|| controller.getGroupManager().getGroupBy() != DrawableAttribute.PATH) {
// null represents all datasources, which is only one in this case.
dataSourcesTooManyFiles.put(null, controller.hasTooManyFiles(null));
igTopComponent.showDataSource(null, dataSourcesTooManyFiles);
return null;
}
/*
* Else there is more than one data source and the grouping is
* PATH (the default): open a dialog prompting the user to pick
* a datasource.
*/
dataSources.add(0, null); //null represents all datasources
//first, while still on background thread, gather viewability info for the datasources.
for (DataSource dataSource : dataSources) {
dataSourcesTooManyFiles.put(dataSource, controller.hasTooManyFiles(dataSource));
}
Platform.runLater(() -> {
//configure the dialog
List<Optional<DataSource>> dataSourceOptionals = dataSources.stream().map(Optional::ofNullable).collect(Collectors.toList());
ChoiceDialog<Optional<DataSource>> datasourceDialog = new ChoiceDialog<>(null, dataSourceOptionals);
datasourceDialog.setTitle(Bundle.ImageGalleryTopComponent_chooseDataSourceDialog_titleText());
datasourceDialog.setHeaderText(Bundle.ImageGalleryTopComponent_chooseDataSourceDialog_headerText());
datasourceDialog.setContentText(Bundle.ImageGalleryTopComponent_chooseDataSourceDialog_contentText());
datasourceDialog.initModality(Modality.APPLICATION_MODAL);
GuiUtils.setDialogIcons(datasourceDialog);
//get the combobox by its css class... this is hacky but should be safe.
@SuppressWarnings(value = "unchecked")
ComboBox<Optional<DataSource>> comboBox = (ComboBox<Optional<DataSource>>) datasourceDialog.getDialogPane().lookup(".combo-box");
//set custom cell renderer
comboBox.setCellFactory((ListView<Optional<DataSource>> param) -> new DataSourceCell(dataSourcesTooManyFiles, controller.getAllDataSourcesDrawableDBStatus()));
comboBox.setButtonCell(new DataSourceCell(dataSourcesTooManyFiles, controller.getAllDataSourcesDrawableDBStatus()));
DataSource dataSource = datasourceDialog.showAndWait().orElse(Optional.empty()).orElse(null);
try {
igTopComponent.showDataSource(dataSource, dataSourcesTooManyFiles);
} catch (TskCoreException ex) {
if (dataSource != null) {
logger.log(Level.SEVERE, "Error showing data source " + dataSource.getName() + ":" + dataSource.getId() + " in Image Gallery", ex);
} else {
logger.log(Level.SEVERE, "Error showing all data sources in Image Gallery.", ex);
}
}
});
return null;
}
}).start();
}
synchronized private void showDataSource(DataSource datasource, Map<DataSource, Boolean> dataSourcesTooManyFiles) throws TskCoreException {
if (dataSourcesTooManyFiles.get(datasource)) {
/**
* Configures the groups manager for the selected data source(s) and opens
* this top component's window.
*
* @param selectedDataSource The data source selected, null if all data
* sources are selected.
* @param dataSourcesTooManyFiles A map of data sources to flags indicating
* whether or not the data source has to many
* files to actually be displayed.
*/
private void openWithSelectedDataSources(DataSource selectedDataSource, Map<DataSource, Boolean> dataSourcesTooManyFiles) {
if (dataSourcesTooManyFiles.get(selectedDataSource)) {
Platform.runLater(ImageGalleryTopComponent::showTooManyFiles);
return;
}
// Display the UI so that they can see the progress screen
SwingUtilities.invokeLater(() -> showTopComponent(this));
GroupManager groupManager = controller.getGroupManager();
synchronized (groupManager) {
groupManager.regroup(datasource, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true);
} else {
/*
* Open the top component's window before configuring the groups
* manager so that the wait cursor animation over the empty, gray
* window will be displayed if the operations takes awhile.
*/
SwingUtilities.invokeLater(() -> showTopComponent());
synchronized (controllerLock) {
GroupManager groupManager = controller.getGroupManager();
// RJCTODO: Why are there potentially hazardous nested synchronized
// blocks here (note: method used ot be synchronized)? Why is
// the groups manager not taking responsibility for its own thread
// safety policy?
synchronized (groupManager) {
groupManager.regroup(selectedDataSource, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true);
}
}
}
}
/**
* Displays a dialog box informing the user that the data source(s) selected
* to have their images displayed have too many image files and will not be
* displayed.
*/
@NbBundle.Messages({"ImageGallery.dialogTitle=Image Gallery",
"ImageGallery.showTooManyFiles.contentText=There are too many files in the selected datasource(s) to ensure reasonable performance.",
"ImageGallery.showTooManyFiles.headerText="})
public static void showTooManyFiles() {
Alert dialog = new Alert(Alert.AlertType.INFORMATION,
Bundle.ImageGallery_showTooManyFiles_contentText(), ButtonType.OK);
private static void showTooManyFiles() {
Alert dialog = new Alert(Alert.AlertType.INFORMATION, Bundle.ImageGallery_showTooManyFiles_contentText(), ButtonType.OK);
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setTitle(Bundle.ImageGallery_dialogTitle());
GuiUtils.setDialogIcons(dialog);
@ -274,8 +209,14 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
dialog.showAndWait();
}
/**
* Opens the singleton top component's window, brings it to the front and
* gives it focus. Note that calling this method will cause the top
* component to be constructed if it does not already exist.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public static void showTopComponent(TopComponent topComponent) {
private static void showTopComponent() {
final ImageGalleryTopComponent topComponent = getTopComponent();
if (topComponent.isOpened() == false) {
topComponent.open();
}
@ -283,76 +224,170 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
topComponent.requestActive();
}
/*
* Closes the singleton Image Gallery top component. Note that calling this
* method will cause the top component to be constructed if it does not
* already exist.
*/
public static void closeTopComponent() {
if (topComponentInitialized) {
final TopComponent etc = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (etc != null) {
try {
etc.close();
} catch (Exception e) {
logger.log(Level.SEVERE, "failed to close " + PREFERRED_ID, e); // NON-NLS
}
}
}
getTopComponent().close();
}
/**
* Contructs the singleton Image Gallery top component. Called by the
* NetBeans WindowManager.
*/
public ImageGalleryTopComponent() {
setName(Bundle.CTL_ImageGalleryTopComponent());
initComponents();
try {
setController(ImageGalleryModule.getController());
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS
}
}
synchronized private void setController(ImageGalleryController controller) {
if (notEqual(this.controller, controller)) {
if (this.controller != null) {
this.controller.reset();
}
this.controller = controller;
Platform.runLater(new Runnable() {
@Override
public void run() {
//initialize jfx ui
fullUIStack = new StackPane(); //this is passed into controller
myScene = new Scene(fullUIStack);
jfxPanel.setScene(myScene);
groupPane = new GroupPane(controller);
centralStack = new StackPane(groupPane); //this is passed into controller
fullUIStack.getChildren().add(borderPane);
splitPane = new SplitPane();
borderPane.setCenter(splitPane);
Toolbar toolbar = new Toolbar(controller);
borderPane.setTop(toolbar);
borderPane.setBottom(new StatusBar(controller));
metaDataTable = new MetaDataPane(controller);
groupTree = new GroupTree(controller);
hashHitList = new HashHitGroupList(controller);
TabPane tabPane = new TabPane(groupTree, hashHitList);
tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE);
tabPane.setMinWidth(TabPane.USE_PREF_SIZE);
VBox.setVgrow(tabPane, Priority.ALWAYS);
leftPane = new VBox(tabPane, new SummaryTablePane(controller));
SplitPane.setResizableWithParent(leftPane, Boolean.FALSE);
SplitPane.setResizableWithParent(groupPane, Boolean.TRUE);
SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE);
splitPane.getItems().addAll(leftPane, centralStack, metaDataTable);
splitPane.setDividerPositions(0.1, 1.0);
/**
* Gets the current controller, allows the user to select the data sources
* for which images are to be displayed and opens the top component's
* window.
*
* @throws TskCoreException If there is an error getting the current
* controller.
*/
@Messages({
"ImageGalleryTopComponent.chooseDataSourceDialog.headerText=Choose a data source to view.",
"ImageGalleryTopComponent.chooseDataSourceDialog.contentText=Data source:",
"ImageGalleryTopComponent.chooseDataSourceDialog.all=All",
"ImageGalleryTopComponent.chooseDataSourceDialog.titleText=Image Gallery",})
private void getCurrentControllerAndOpen() throws TskCoreException {
ImageGalleryController currentController = ImageGalleryModule.getController();
/*
* First, dispatch a task to run in the JavaFX thread. This task will
* swap the new controller, if there is one, into this top component and
* its child UI components. It also queues another JavaFX thread task to
* check for analyzed groups, which has the side effect of managing the
* spinner(s) that take the place of a wait cursor.
*/
Platform.runLater(new Runnable() {
@Override
public void run() {
synchronized (controllerLock) {
if (notEqual(controller, currentController)) {
controller = currentController;
/*
* Create or re-create the top component's child UI
* components. This is currently done every time a new
* controller is created (i.e., a new case is opened).
* It could be done by resetting the controller in the
* child UI components instead.
*/
fullUIStack = new StackPane();
myScene = new Scene(fullUIStack);
jfxPanel.setScene(myScene);
groupPane = new GroupPane(controller);
centralStack = new StackPane(groupPane);
fullUIStack.getChildren().add(borderPane);
splitPane = new SplitPane();
borderPane.setCenter(splitPane);
Toolbar toolbar = new Toolbar(controller);
borderPane.setTop(toolbar);
borderPane.setBottom(new StatusBar(controller));
metaDataTable = new MetaDataPane(controller);
groupTree = new GroupTree(controller);
hashHitList = new HashHitGroupList(controller);
TabPane tabPane = new TabPane(groupTree, hashHitList);
tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE);
tabPane.setMinWidth(TabPane.USE_PREF_SIZE);
VBox.setVgrow(tabPane, Priority.ALWAYS);
leftPane = new VBox(tabPane, new SummaryTablePane(controller));
SplitPane.setResizableWithParent(leftPane, Boolean.FALSE);
SplitPane.setResizableWithParent(groupPane, Boolean.TRUE);
SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE);
splitPane.getItems().addAll(leftPane, centralStack, metaDataTable);
splitPane.setDividerPositions(0.1, 1.0);
controller.regroupDisabledProperty().addListener((Observable observable) -> checkForGroups());
controller.getGroupManager().getAnalyzedGroups().addListener((Observable observable) -> Platform.runLater(() -> checkForGroups()));
/*
* Set up for a call to checkForGroups to happen
* whenever the controller's regrouping disabled
* property or the group manager's analyzed groups
* property changes.
*/
// RJCTODO: Why was the first lambda not set up to happen on
// the JavaFX thread? I am using that thread confinement and
// a volatile controller reference for simplified thread safety,
// is there a problem with this? It seems like this is a bug,
// since why would we want this code to execute both in the
// JavaFX thread and elsewhere?
controller.regroupDisabledProperty().addListener((Observable unused) -> Platform.runLater(() -> checkForAnalyzedGroups()));
controller.getGroupManager().getAnalyzedGroups().addListener((Observable unused) -> Platform.runLater(() -> checkForAnalyzedGroups()));
topComponentInitialized = true;
/*
* Dispatch a later task to call check for groups. Note
* that this method displays one or more spinner(s) that
* take the place of a wait cursor if there are no
* analyzed groups yet, ingest is running, etc.
*/
// RJCTODO: Is there a race condition here, since this task will be
// executed before the task to actually open the top component window?
// It seems like this might be a sort of a hack and I am wondering
// why this can't be done in openWithSelectedDataSources instead.
Platform.runLater(() -> checkForAnalyzedGroups());
}
// This will cause the UI to show the progress dialog
Platform.runLater(() -> checkForGroups());
/*
* Kick off a background task to query the case database for
* data sources. This task may queue another task for the
* JavaFX thread to allow the user to select which data
* sources for which to display images. Ultimately, a task
* will be queued for the AWT EDT that will show the top
* component window.
*/
new Thread(new Task<Void>() {
@Override
protected Void call() throws Exception {
synchronized (controllerLock) {
/*
* If there is only one datasource or the
* grouping criterion is already set to
* something other than by path (the default),
* proceed to open this top component.
* Otherwise, do a dialog to allow the user to
* select the data sources for which images are
* to be displayed, then open the top component.
*/
List<DataSource> dataSources = controller.getSleuthKitCase().getDataSources();
Map<DataSource, Boolean> dataSourcesWithTooManyFiles = new HashMap<>();
// RJCTODO: At least some of this designation of "all data sources" with null seems uneccessary;
// in any case, the use of nulls and zeros here is
// very confusing and should be reworked. Why was this done?
if (dataSources.size() <= 1
|| controller.getGroupManager().getGroupBy() != DrawableAttribute.PATH) {
dataSourcesWithTooManyFiles.put(null, controller.hasTooManyFiles(null));
openWithSelectedDataSources(null, dataSourcesWithTooManyFiles);
} else {
dataSources.add(0, null);
for (DataSource dataSource : dataSources) {
dataSourcesWithTooManyFiles.put(dataSource, controller.hasTooManyFiles(dataSource));
}
Platform.runLater(() -> {
List<Optional<DataSource>> dataSourceOptionals = dataSources.stream().map(Optional::ofNullable).collect(Collectors.toList());
ChoiceDialog<Optional<DataSource>> datasourceDialog = new ChoiceDialog<>(null, dataSourceOptionals);
datasourceDialog.setTitle(Bundle.ImageGalleryTopComponent_chooseDataSourceDialog_titleText());
datasourceDialog.setHeaderText(Bundle.ImageGalleryTopComponent_chooseDataSourceDialog_headerText());
datasourceDialog.setContentText(Bundle.ImageGalleryTopComponent_chooseDataSourceDialog_contentText());
datasourceDialog.initModality(Modality.APPLICATION_MODAL);
GuiUtils.setDialogIcons(datasourceDialog);
@SuppressWarnings(value = "unchecked")
ComboBox<Optional<DataSource>> comboBox = (ComboBox<Optional<DataSource>>) datasourceDialog.getDialogPane().lookup(".combo-box");
comboBox.setCellFactory((ListView<Optional<DataSource>> param) -> new DataSourceCell(dataSourcesWithTooManyFiles, controller.getAllDataSourcesDrawableDBStatus()));
comboBox.setButtonCell(new DataSourceCell(dataSourcesWithTooManyFiles, controller.getAllDataSourcesDrawableDBStatus()));
DataSource dataSource = datasourceDialog.showAndWait().orElse(Optional.empty()).orElse(null);
openWithSelectedDataSources(dataSource, dataSourcesWithTooManyFiles);
});
}
return null;
}
}
}).start();
}
});
}
}
});
}
/**
@ -400,6 +435,14 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
@Override
public ExplorerManager getExplorerManager() {
/*
* Although ImageGallery doesn't currently use the explorer manager,
* this TopComponent provides one through the getExplorerManager method.
* However, this does not seem to function correctly unless a Netbeans
* provided explorer view is present in the TopComponenet, even if it is
* invisible/ zero sized
*/
// RJCTODO: Why is this here?
return em;
}
@ -409,11 +452,10 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
}
/**
* Check if there are any fully analyzed groups available from the
* GroupManager and remove blocking progress spinners if there are. If there
* aren't, add a blocking progress spinner with appropriate message.
*
* This gets called when any group becomes analyzed and when started.
* Checks if there are any fully analyzed groups available from the groups
* manager and removes the blocking progress spinner if there are analyzed
* groups; otherwise adds a blocking progress spinner with an appropriate
* message.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@NbBundle.Messages({
@ -427,57 +469,59 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
"ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:"
+ " the current Group By setting resulted in no groups, "
+ "or no groups are fully analyzed but ingest is not running."})
private void checkForGroups() {
GroupManager groupManager = controller.getGroupManager();
private void checkForAnalyzedGroups() {
synchronized (controllerLock) {
GroupManager groupManager = controller.getGroupManager();
// if there are groups to display, then display them
// @@@ Need to check timing on this and make sure we have only groups for the selected DS. Seems like rebuild can cause groups to be created for a DS that is not later selected...
if (isNotEmpty(groupManager.getAnalyzedGroups())) {
clearNotification();
return;
}
// display a message based on if ingest is running and/or listening
if (IngestManager.getInstance().isIngestRunning()) {
if (controller.isListeningEnabled()) {
replaceNotification(centralStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(),
new ProgressIndicator()));
} else {
replaceNotification(fullUIStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1()));
// if there are groups to display, then display them
// @@@ Need to check timing on this and make sure we have only groups for the selected DS. Seems like rebuild can cause groups to be created for a DS that is not later selected...
if (isNotEmpty(groupManager.getAnalyzedGroups())) {
clearNotification();
return;
}
return;
}
// display a message about stuff still being in the queue
if (controller.getDBTasksQueueSizeProperty().get() > 0) {
replaceNotification(fullUIStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(),
new ProgressIndicator()));
return;
}
// are there are files in the DB?
try {
if (controller.getDatabase().countAllFiles() <= 0) {
// there are no files in db
// display a message based on if ingest is running and/or listening
if (IngestManager.getInstance().isIngestRunning()) {
if (controller.isListeningEnabled()) {
replaceNotification(fullUIStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5()));
replaceNotification(centralStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(),
new ProgressIndicator()));
} else {
replaceNotification(fullUIStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4()));
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1()));
}
return;
}
} catch (TskCoreException tskCoreException) {
logger.log(Level.SEVERE, "Error counting files in the database.", tskCoreException);
}
if (false == groupManager.isRegrouping()) {
replaceNotification(centralStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6()));
// display a message about stuff still being in the queue
if (controller.getDBTasksQueueSizeProperty().get() > 0) {
replaceNotification(fullUIStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(),
new ProgressIndicator()));
return;
}
// are there are files in the DB?
try {
if (controller.getDatabase().countAllFiles() <= 0) {
// there are no files in db
if (controller.isListeningEnabled()) {
replaceNotification(fullUIStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5()));
} else {
replaceNotification(fullUIStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4()));
}
return;
}
} catch (TskCoreException tskCoreException) {
logger.log(Level.SEVERE, "Error counting files in the database.", tskCoreException);
}
if (false == groupManager.isRegrouping()) {
replaceNotification(centralStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6()));
}
}
}
@ -491,17 +535,17 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
}
/**
* Removes the spinner(s).
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void clearNotification() {
//remove the ingest spinner
fullUIStack.getChildren().remove(infoOverlay);
//remove the ingest spinner
centralStack.getChildren().remove(infoOverlay);
}
/**
* Region with partialy opacity used to block out parts of the UI behind a
* A partially opaque region used to block out parts of the UI behind a
* pseudo dialog.
*/
static final private class TranslucentRegion extends Region {

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015-2018 Basis Technology Corp.
* Copyright 2013-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -70,7 +70,7 @@ import org.sleuthkit.datamodel.TskCoreException;
+ "Do you want to update and listen for further ingest results?\n"
+ "Choosing 'yes' will update the database and enable listening to future ingests.",
"OpenAction.notAnalyzedDlg.msg=No image/video files available to display yet.\n"
+ "Please run FileType and EXIF ingest modules.",
+ "Please run FileType and EXIF ingest modules.",
"OpenAction.stale.confDlg.title=Image Gallery"})
public final class OpenAction extends CallableSystemAction {
@ -147,19 +147,20 @@ public final class OpenAction extends CallableSystemAction {
try {
currentCase = Case.getCurrentCaseThrows();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex);
return;
}
ImageGalleryController controller;
try {
controller = ImageGalleryModule.getController();
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting ImageGalleryController for current case.", ex);
logger.log(Level.SEVERE, "No current case", ex);
return;
}
Platform.runLater(() -> {
ImageGalleryController controller;
try {
controller = ImageGalleryModule.getController();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Failed to get ImageGalleryController", ex);
return;
}
if (currentCase.getCaseType() == Case.CaseType.MULTI_USER_CASE
&& ImageGalleryPreferences.isMultiUserCaseInfoDialogDisabled() == false) {
&& ImageGalleryPreferences.isMultiUserCaseInfoDialogDisabled() == false) {
Alert dialog = new Alert(Alert.AlertType.INFORMATION);
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setResizable(true);
@ -185,32 +186,32 @@ public final class OpenAction extends CallableSystemAction {
}
private void checkDBStale(ImageGalleryController controller) {
ListenableFuture<Map<Long, DrawableDB.DrawableDbBuildStatusEnum>> dataSourceStatusMapFuture = TaskUtils.getExecutorForClass(OpenAction.class)
ListenableFuture<Map<Long, DrawableDB.DrawableDbBuildStatusEnum>> dataSourceStatusMapFuture = TaskUtils.getExecutorForClass(OpenAction.class)
.submit(controller::getAllDataSourcesDrawableDBStatus);
addFXCallback(dataSourceStatusMapFuture,
dataSourceStatusMap -> {
boolean dbIsStale = false;
for (Map.Entry<Long, DrawableDbBuildStatusEnum> entry : dataSourceStatusMap.entrySet()) {
DrawableDbBuildStatusEnum status = entry.getValue();
if (DrawableDbBuildStatusEnum.COMPLETE != status) {
dbIsStale = true;
dbIsStale = true;
}
}
}
//back on fx thread.
if (false == dbIsStale) {
//drawable db is not stale, just open it
openTopComponent();
} else {
// If there is only one datasource and it's in DEFAULT State -
// ingest modules need to be run on the data source
if (dataSourceStatusMap.size()== 1) {
if (dataSourceStatusMap.size() == 1) {
Map.Entry<Long, DrawableDB.DrawableDbBuildStatusEnum> entry = dataSourceStatusMap.entrySet().iterator().next();
if (entry.getValue() == DrawableDbBuildStatusEnum.DEFAULT ) {
if (entry.getValue() == DrawableDbBuildStatusEnum.DEFAULT) {
Alert alert = new Alert(Alert.AlertType.WARNING, Bundle.OpenAction_notAnalyzedDlg_msg(), ButtonType.OK);
alert.setTitle(Bundle.OpenAction_stale_confDlg_title());
alert.initModality(Modality.APPLICATION_MODAL);
@ -218,8 +219,8 @@ public final class OpenAction extends CallableSystemAction {
alert.showAndWait();
return;
}
}
}
//drawable db is stale,
//ask what to do
Alert alert = new Alert(Alert.AlertType.WARNING,
@ -235,7 +236,8 @@ public final class OpenAction extends CallableSystemAction {
openTopComponent();
} else if (answer == ButtonType.YES) {
if (controller.getAutopsyCase().getCaseType() == Case.CaseType.SINGLE_USER_CASE) {
/* For a single-user case, we favor user
/*
* For a single-user case, we favor user
* experience, and rebuild the database as soon
* as Image Gallery is enabled for the case.
*
@ -267,10 +269,9 @@ public final class OpenAction extends CallableSystemAction {
SwingUtilities.invokeLater(() -> {
try {
ImageGalleryTopComponent.openTopComponent();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex);//NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS}
logger.log(Level.SEVERE, "Failed to open Image Gallery top component", ex); //NON-NLS}
// RJCTODO: Give the user some feedback here
}
});
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-18 Basis Technology Corp.
* Copyright 2013-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -31,9 +31,7 @@ import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyLongProperty;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
@ -120,10 +118,8 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
.map(ImageGalleryModule.getController().getHashSetManager()::isInAnyHashSet)
.filter(Boolean::booleanValue)
.count());
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS
logger.log(Level.SEVERE, "Failed to get image gallery controller", ex); //NON-NLS
}
}
return hashSetHitsCount.get();
@ -138,12 +134,10 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
if (uncatCount.get() < 0) {
try {
uncatCount.set(ImageGalleryModule.getController().getDatabase().getUncategorizedCount(fileIDs));
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Failed to get image gallery controller", ex); //NON-NLS
}
}
return uncatCount.get();
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-18 Basis Technology Corp.
* Copyright 2013-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-18 Basis Technology Corp.
* Copyright 2013-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -20,9 +20,7 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import static com.google.common.collect.Lists.transform;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.ArrayList;
import java.util.Arrays;
@ -35,7 +33,6 @@ import java.util.Map;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.IntStream;
import javafx.animation.Interpolator;
@ -135,18 +132,15 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
import static org.sleuthkit.autopsy.imagegallery.gui.GuiUtils.createAutoAssigningMenuItem;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A GroupPane displays the contents of a DrawableGroup. It supports both
* GridView and SlideShowView modes by swapping out its internal components.
*
*
* TODO: Extract the The GridView instance to a separate class analogous to the
* SlideShow.
*