mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-14 17:06:16 +00:00
Merge pull request #4317 from rcordovano/image-gallery-lifecycle-management
4288 Image gallery life cycle management and thread safety improvements
This commit is contained in:
commit
c2b9dbc941
@ -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");
|
||||
@ -37,7 +37,6 @@ import javafx.beans.Observable;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
@ -51,7 +50,6 @@ import javax.annotation.Nonnull;
|
||||
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.util.Cancellable;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.Case.CaseType;
|
||||
@ -71,7 +69,6 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
|
||||
@ -117,7 +114,7 @@ public final class ImageGalleryController {
|
||||
private final CategoryManager categoryManager;
|
||||
private final DrawableTagsManager tagsManager;
|
||||
|
||||
private ListeningExecutorService dbExecutor;
|
||||
private final ListeningExecutorService dbExecutor;
|
||||
|
||||
private final Case autopsyCase;
|
||||
private final SleuthkitCase sleuthKitCase;
|
||||
@ -168,7 +165,7 @@ public final class ImageGalleryController {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param b True if any data source in the case is stale
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
||||
@ -183,7 +180,7 @@ public final class ImageGalleryController {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return true if any data source in the case is stale
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
@ -192,7 +189,6 @@ public final class ImageGalleryController {
|
||||
}
|
||||
|
||||
ImageGalleryController(@Nonnull Case newCase) throws TskCoreException {
|
||||
|
||||
this.autopsyCase = Objects.requireNonNull(newCase);
|
||||
this.sleuthKitCase = newCase.getSleuthkitCase();
|
||||
|
||||
@ -325,19 +321,17 @@ public final class ImageGalleryController {
|
||||
}
|
||||
|
||||
/**
|
||||
* reset the state of the controller (eg if the case is closed)
|
||||
* Shuts down this per case singleton image gallery controller.
|
||||
*/
|
||||
public synchronized void reset() {
|
||||
logger.info("Closing ImageGalleryControler for case."); //NON-NLS
|
||||
|
||||
public synchronized void shutDown() {
|
||||
logger.log(Level.INFO, String.format("Shutting down image gallery controller for case %s (%s)", autopsyCase.getDisplayName(), autopsyCase.getName()));
|
||||
selectionModel.clearSelection();
|
||||
thumbnailCache.clearCache();
|
||||
historyManager.clear();
|
||||
groupManager.reset();
|
||||
|
||||
shutDownDBExecutor();
|
||||
drawableDB.close();
|
||||
dbExecutor = getNewDBExecutor();
|
||||
logger.log(Level.INFO, String.format("Completed shut down of image gallery controller for case %s (%s)", autopsyCase.getDisplayName(), autopsyCase.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -353,8 +347,8 @@ public final class ImageGalleryController {
|
||||
* Returns a set of data source object ids that are stale.
|
||||
*
|
||||
* This includes any data sources already in the table, that are not in
|
||||
* COMPLETE or IN_PROGRESS status, or any data sources that might have been added to the
|
||||
* case, but are not in the datasources table.
|
||||
* COMPLETE or IN_PROGRESS status, or any data sources that might have been
|
||||
* added to the case, but are not in the datasources table.
|
||||
*
|
||||
* @return list of data source object ids that are stale.
|
||||
*/
|
||||
@ -491,7 +485,7 @@ public final class ImageGalleryController {
|
||||
|
||||
return sleuthKitCase.countFilesWhere(whereClause) > 0;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasFilesWithMimeType(long dataSourceId) throws TskCoreException {
|
||||
|
||||
String whereClause = "data_source_obj_id = " + dataSourceId
|
||||
@ -502,13 +496,11 @@ public final class ImageGalleryController {
|
||||
}
|
||||
|
||||
synchronized private void shutDownDBExecutor() {
|
||||
if (dbExecutor != null) {
|
||||
dbExecutor.shutdownNow();
|
||||
try {
|
||||
dbExecutor.awaitTermination(30, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException ex) {
|
||||
logger.log(Level.WARNING, "Image Gallery failed to shutdown DB Task Executor in a timely fashion.", ex);
|
||||
}
|
||||
dbExecutor.shutdownNow();
|
||||
try {
|
||||
dbExecutor.awaitTermination(30, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException ex) {
|
||||
logger.log(Level.WARNING, "Image Gallery failed to shutdown DB Task Executor in a timely fashion.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -523,12 +515,10 @@ public final class ImageGalleryController {
|
||||
* @param bgTask
|
||||
*/
|
||||
public synchronized void queueDBTask(BackgroundTask bgTask) {
|
||||
if (dbExecutor == null || dbExecutor.isShutdown()) {
|
||||
dbExecutor = getNewDBExecutor();
|
||||
if (!dbExecutor.isShutdown()) {
|
||||
incrementQueueSize();
|
||||
dbExecutor.submit(bgTask).addListener(this::decrementQueueSize, MoreExecutors.directExecutor());
|
||||
}
|
||||
incrementQueueSize();
|
||||
dbExecutor.submit(bgTask).addListener(this::decrementQueueSize, MoreExecutors.directExecutor());
|
||||
|
||||
}
|
||||
|
||||
private void incrementQueueSize() {
|
||||
@ -634,7 +624,6 @@ public final class ImageGalleryController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* task that updates one file in database with results from ingest
|
||||
*/
|
||||
@ -650,7 +639,7 @@ public final class ImageGalleryController {
|
||||
public AbstractFile getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
super();
|
||||
this.file = f;
|
||||
@ -671,7 +660,6 @@ public final class ImageGalleryController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base abstract class for various methods of copying image files data, for
|
||||
* a given data source, into the Image gallery DB.
|
||||
@ -680,6 +668,7 @@ public final class ImageGalleryController {
|
||||
"BulkTask.stopCopy.status=Stopping copy to drawable db task.",
|
||||
"BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."})
|
||||
abstract static class BulkTransferTask extends BackgroundTask {
|
||||
|
||||
static private final String MIMETYPE_CLAUSE
|
||||
= "(mime_type LIKE '" //NON-NLS
|
||||
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
|
||||
@ -742,11 +731,11 @@ public final class ImageGalleryController {
|
||||
CaseDbTransaction caseDbTransaction = null;
|
||||
boolean hasFilesWithNoMime = true;
|
||||
boolean endedEarly = false;
|
||||
|
||||
|
||||
try {
|
||||
// See if there are any files in the DS w/out a MIME TYPE
|
||||
hasFilesWithNoMime = controller.hasFilesWithNoMimeType(dataSourceObjId);
|
||||
|
||||
|
||||
//grab all files with detected mime types
|
||||
final List<AbstractFile> files = getFiles();
|
||||
progressHandle.switchToDeterminate(files.size());
|
||||
@ -756,7 +745,6 @@ public final class ImageGalleryController {
|
||||
updateProgress(0.0);
|
||||
int workDone = 0;
|
||||
|
||||
|
||||
// Cycle through all of the files returned and call processFile on each
|
||||
//do in transaction
|
||||
drawableDbTransaction = taskDB.beginTransaction();
|
||||
@ -776,7 +764,6 @@ public final class ImageGalleryController {
|
||||
logger.log(Level.WARNING, "Task cancelled or interrupted: not all contents may be transfered to drawable database."); //NON-NLS
|
||||
endedEarly = true;
|
||||
progressHandle.finish();
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -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,16 @@ 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.Nonnull;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
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,16 +39,13 @@ 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;
|
||||
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
|
||||
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisStartedEvent;
|
||||
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisEvent;
|
||||
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
@ -53,262 +54,266 @@ 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("Failed to get ", 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);
|
||||
return isNotBlank(enabledforCaseProp) ? Boolean.valueOf(enabledforCaseProp) : ImageGalleryPreferences.isEnabledByDefault();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
static boolean isEnabledforCase(@Nonnull Case theCase) {
|
||||
String enabledforCaseProp = new PerCaseProperties(theCase).getConfigSetting(ImageGalleryModule.MODULE_NAME, PerCaseProperties.ENABLED);
|
||||
return isNotBlank(enabledforCaseProp) ? Boolean.valueOf(enabledforCaseProp) : ImageGalleryPreferences.isEnabledByDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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);
|
||||
public void propertyChange(PropertyChangeEvent event) {
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
// RJCTODO: DO we need to handle any events at all on an auot ingest node?
|
||||
if (((AutopsyEvent) event).getSourceType() != AutopsyEvent.SourceType.LOCAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* only process individual files in realtime on the node that is
|
||||
* running the ingest. on a remote node, image files are processed
|
||||
* enblock when ingest is complete */
|
||||
if (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bail out if the case is closed
|
||||
ImageGalleryController currentController;
|
||||
try {
|
||||
if (controller == null || Case.getCurrentCaseThrows() == null) {
|
||||
return;
|
||||
}
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
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()) {
|
||||
try {
|
||||
// Update the entry if it is a picture and not in NSRL
|
||||
if (isDrawableAndNotKnown(file)) {
|
||||
con.queueDBTask(new ImageGalleryController.UpdateFileTask(file, controller.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.");
|
||||
}
|
||||
}
|
||||
} 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for case events.
|
||||
*/
|
||||
static private class CaseEventListener implements PropertyChangeListener {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if (RuntimeProperties.runningWithGUI() == false) {
|
||||
/*
|
||||
* Running in "headless" mode, no need to process any events.
|
||||
* This cannot be done earlier because the switch to core
|
||||
* components inactive may not have been made at start up.
|
||||
*/
|
||||
Case.removePropertyChangeListener(this);
|
||||
return;
|
||||
}
|
||||
ImageGalleryController con;
|
||||
try {
|
||||
con = getController();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
|
||||
return;
|
||||
currentController = getController();
|
||||
// RJCTODO: If a closed controller had a method that could be
|
||||
// queried to determine whether it was shut down, we could
|
||||
// bail out here. The older code that used to try to check for
|
||||
// a current case was flawed; there was no guarantee the current
|
||||
// case was the same case associated with the event.
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, String.format("Failed to handle %s event", event.getPropertyName()), 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);
|
||||
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 {
|
||||
if (isDrawableAndNotKnown(file)) {
|
||||
currentController.queueDBTask(new ImageGalleryController.UpdateFileTask(file, currentController.getDatabase()));
|
||||
}
|
||||
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
|
||||
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
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DATA_ADDED:
|
||||
ModuleDataEvent artifactAddedEvent = (ModuleDataEvent) event.getOldValue();
|
||||
if (CollectionUtils.isNotEmpty(artifactAddedEvent.getArtifacts())) {
|
||||
DrawableDB drawableDB = currentController.getDatabase();
|
||||
for (BlackboardArtifact art : artifactAddedEvent.getArtifacts()) {
|
||||
if (artifactAddedEvent.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID()) {
|
||||
drawableDB.addExifCache(art.getObjectID());
|
||||
} else if (artifactAddedEvent.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) {
|
||||
drawableDB.addHashSetCache(art.getObjectID());
|
||||
}
|
||||
}
|
||||
}
|
||||
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.UNKNOWN);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for Ingest Job events.
|
||||
* A listener for case application events.
|
||||
*/
|
||||
// RJCTODO: This code would be easier to read if there were two case event
|
||||
// listeners, one that handled CURRENT_CASE events and one that handled
|
||||
// the other events. Or event better, move the handling of Case events other
|
||||
// than CURRENT_CASE into ImageGalleryController.
|
||||
static private class CaseEventListener implements PropertyChangeListener {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent event) {
|
||||
// RJCTODO: DO we need to handle any events at all on an auot ingest node?
|
||||
Case.Events eventType = Case.Events.valueOf(event.getPropertyName());
|
||||
if (eventType == Case.Events.CURRENT_CASE) {
|
||||
synchronized (controllerLock) {
|
||||
if (event.getNewValue() != null) {
|
||||
/*
|
||||
* CURRENT_CASE(_OPENED) event.
|
||||
*/
|
||||
Case newCase = (Case) event.getNewValue();
|
||||
try {
|
||||
controller = new ImageGalleryController(newCase);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to construct controller for new case %s (%s)", newCase.getDisplayName(), newCase.getName()), ex);
|
||||
}
|
||||
} else if (event.getOldValue() != null) {
|
||||
/*
|
||||
* CURRENT_CASE(_CLOSED) event.
|
||||
*/
|
||||
SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent);
|
||||
controller.shutDown();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ImageGalleryController currentController;
|
||||
try {
|
||||
currentController = getController();
|
||||
// RJCTODO: I think it would be best to move handling of these
|
||||
// case events into the controller class and have the controller
|
||||
// instance register/unregister as a listener when it is
|
||||
// contructed and shuts down. This will improve the encapsulation
|
||||
// of ImageGalleryController and allow it to check its own open/closed
|
||||
// state before handling an event.
|
||||
} 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.UNKNOWN);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CONTENT_TAG_ADDED:
|
||||
final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) event;
|
||||
long objId = tagAddedEvent.getAddedTag().getContent().getId();
|
||||
DrawableDB drawableDB = currentController.getDatabase();
|
||||
drawableDB.addTagCache(objId); // RJCTODO: Why add the tag to the cache before doing the in DB check?
|
||||
if (drawableDB.isInDB(objId)) {
|
||||
currentController.getTagsManager().fireTagAddedEvent(tagAddedEvent);
|
||||
}
|
||||
break;
|
||||
case CONTENT_TAG_DELETED:
|
||||
final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) event;
|
||||
if (currentController.getDatabase().isInDB(tagDeletedEvent.getDeletedTagInfo().getContentID())) {
|
||||
currentController.getTagsManager().fireTagDeletedEvent(tagDeletedEvent);
|
||||
} // RJCTODO: Why not remove the tag from the cache?
|
||||
break;
|
||||
default:
|
||||
logger.log(Level.SEVERE, String.format("Received %s event with no subscription", event.getPropertyName())); //NON-NLS
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener for ingest job application events.
|
||||
*/
|
||||
static private class IngestJobEventListener implements PropertyChangeListener {
|
||||
|
||||
@ -319,80 +324,105 @@ public class ImageGalleryModule {
|
||||
"ImageGalleryController.dataSourceAnalyzed.confDlg.title=Image Gallery"
|
||||
})
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
IngestJobEvent eventType = IngestJobEvent.valueOf(evt.getPropertyName());
|
||||
|
||||
try {
|
||||
ImageGalleryController controller = getController();
|
||||
|
||||
if (eventType == IngestJobEvent.DATA_SOURCE_ANALYSIS_STARTED) {
|
||||
|
||||
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
|
||||
if (controller.isListeningEnabled()) {
|
||||
DataSourceAnalysisStartedEvent dataSourceAnalysisStartedEvent = (DataSourceAnalysisStartedEvent) evt;
|
||||
Content dataSource = dataSourceAnalysisStartedEvent.getDataSource();
|
||||
|
||||
DrawableDB drawableDb = controller.getDatabase();
|
||||
// Don't update status if it is is already marked as COMPLETE
|
||||
if (drawableDb.getDataSourceDbBuildStatus(dataSource.getId()) != DrawableDB.DrawableDbBuildStatusEnum.COMPLETE) {
|
||||
drawableDb.insertOrUpdateDataSource(dataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (eventType == IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED) {
|
||||
|
||||
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
|
||||
if (controller.isListeningEnabled()) {
|
||||
DataSourceAnalysisCompletedEvent dataSourceAnalysisCompletedEvent = (DataSourceAnalysisCompletedEvent) evt;
|
||||
Content dataSource = dataSourceAnalysisCompletedEvent.getDataSource();
|
||||
|
||||
DrawableDB drawableDb = controller.getDatabase();
|
||||
if (drawableDb.getDataSourceDbBuildStatus(dataSource.getId()) == DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS) {
|
||||
|
||||
// If at least one file in CaseDB has mime type, then set to COMPLETE
|
||||
// Otherwise, back to UNKNOWN since we assume file type module was not run
|
||||
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus =
|
||||
controller.hasFilesWithMimeType(dataSource.getId()) ?
|
||||
DrawableDB.DrawableDbBuildStatusEnum.COMPLETE :
|
||||
DrawableDB.DrawableDbBuildStatusEnum.UNKNOWN;
|
||||
|
||||
controller.getDatabase().insertOrUpdateDataSource(dataSource.getId(), datasourceDrawableDBStatus);
|
||||
}
|
||||
}
|
||||
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
|
||||
controller.setCaseStale(true);
|
||||
if (controller.isListeningEnabled()) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (ImageGalleryTopComponent.isImageGalleryOpen()) {
|
||||
int showAnswer = JOptionPane.showConfirmDialog(ImageGalleryTopComponent.getTopComponent(),
|
||||
Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_msg(),
|
||||
Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_title(),
|
||||
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||
|
||||
switch (showAnswer) {
|
||||
case JOptionPane.YES_OPTION:
|
||||
controller.rebuildDB();
|
||||
break;
|
||||
case JOptionPane.NO_OPTION:
|
||||
case JOptionPane.CANCEL_OPTION:
|
||||
default:
|
||||
break; //do nothing
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
public void propertyChange(PropertyChangeEvent event) {
|
||||
/*
|
||||
* Only handling data source analysis events.
|
||||
*/
|
||||
// RJCTODO: Do we need to handle any events at all on an auto ingest node?
|
||||
// RJCTODO: This would be less messy if IngestManager supported
|
||||
// subscribing for a subset of events the way case does, and it the
|
||||
// conditional blocks became method calls.
|
||||
if (!(event instanceof DataSourceAnalysisEvent)) {
|
||||
return;
|
||||
}
|
||||
catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
|
||||
|
||||
ImageGalleryController controller;
|
||||
try {
|
||||
controller = getController();
|
||||
// RJCTODO: I think it would be best to move handling of these
|
||||
// case events into the controller class and have the controller
|
||||
// instance register/unregister as a listener when it is
|
||||
// contructed and shuts down. This will improve the encapsulation
|
||||
// of ImageGalleryController and allow it to check its own open/closed
|
||||
// state before handling an event.
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, String.format("Failed to handle %s event", event.getPropertyName()), ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
|
||||
DataSourceAnalysisEvent dataSourceEvent = (DataSourceAnalysisEvent) event;
|
||||
Content dataSource = dataSourceEvent.getDataSource();
|
||||
long dataSourceObjId = dataSource.getId();
|
||||
String eventType = dataSourceEvent.getPropertyName();
|
||||
try {
|
||||
switch (IngestManager.IngestJobEvent.valueOf(eventType)) {
|
||||
case DATA_SOURCE_ANALYSIS_STARTED:
|
||||
if (((AutopsyEvent) event).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
|
||||
if (controller.isListeningEnabled()) {
|
||||
DrawableDB drawableDb = controller.getDatabase();
|
||||
// Don't update status if it is is already marked as COMPLETE
|
||||
if (drawableDb.getDataSourceDbBuildStatus(dataSourceObjId) != DrawableDB.DrawableDbBuildStatusEnum.COMPLETE) {
|
||||
drawableDb.insertOrUpdateDataSource(dataSource.getId(), DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS);
|
||||
}
|
||||
}
|
||||
}
|
||||
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()) {
|
||||
DrawableDB drawableDb = controller.getDatabase();
|
||||
if (drawableDb.getDataSourceDbBuildStatus(dataSourceObjId) == DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS) {
|
||||
|
||||
// If at least one file in CaseDB has mime type, then set to COMPLETE
|
||||
// Otherwise, back to UNKNOWN since we assume file type module was not run
|
||||
DrawableDB.DrawableDbBuildStatusEnum datasourceDrawableDBStatus
|
||||
= controller.hasFilesWithMimeType(dataSourceObjId)
|
||||
? DrawableDB.DrawableDbBuildStatusEnum.COMPLETE
|
||||
: DrawableDB.DrawableDbBuildStatusEnum.UNKNOWN;
|
||||
|
||||
controller.getDatabase().insertOrUpdateDataSource(dataSource.getId(), datasourceDrawableDBStatus);
|
||||
}
|
||||
}
|
||||
} else if (((AutopsyEvent) event).getSourceType() == AutopsyEvent.SourceType.REMOTE) {
|
||||
/*
|
||||
* 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.setCaseStale(true);
|
||||
if (controller.isListeningEnabled()) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (ImageGalleryTopComponent.isImageGalleryOpen()) {
|
||||
int showAnswer = JOptionPane.showConfirmDialog(ImageGalleryTopComponent.getTopComponent(),
|
||||
Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_msg(),
|
||||
Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_title(),
|
||||
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||
switch (showAnswer) {
|
||||
case JOptionPane.YES_OPTION:
|
||||
controller.rebuildDB();
|
||||
break;
|
||||
case JOptionPane.NO_OPTION:
|
||||
case JOptionPane.CANCEL_OPTION:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, String.format("Failed to handle %s event for %s (objId=%d)", dataSourceEvent.getPropertyName(), dataSource.getName(), dataSourceObjId), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -199,7 +199,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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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");
|
||||
@ -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,82 @@ 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 {
|
||||
SwingUtilities.invokeLater(() -> showTopComponent());
|
||||
synchronized (controllerLock) {
|
||||
GroupManager groupManager = controller.getGroupManager();
|
||||
// RJCTODO: Why are there potentially hazardous nested synchronized
|
||||
// blocks here (note: method used to be synchronized, my
|
||||
// dedicated controllerLock lock just makes the nesting more obvious)?
|
||||
// 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 +205,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 +220,177 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
// RJCTODO: Could add the flag that used to be used for the busy wait on
|
||||
// the initial JavaFX thread task to avoid superfluous construction here.
|
||||
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();
|
||||
/*
|
||||
* 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. This task also queues another JavaFX thread task
|
||||
* to check for analyzed groups, which has the side effect of starting
|
||||
* the spinner(s) that take the place of a wait cursor. Finally, this
|
||||
* task starts a background thread to query the case database. This
|
||||
* background task may dispatch a JavaFX thread task to do a data source
|
||||
* selection dialog. Ultimately, there is a final task that either opens
|
||||
* the window in the AWT EDT or displays a "too many files" dialog in
|
||||
* the JFX thread.
|
||||
*/
|
||||
// RJCTODO: Verify the side effect remark above.
|
||||
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.
|
||||
*/
|
||||
// RJCTODO: Construction of these components can perhaps
|
||||
// be separated from opening the window again so that
|
||||
// a setController implementation could be called from
|
||||
// the case opened event handler in the ImageGalleryModule
|
||||
// object.
|
||||
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.
|
||||
*/
|
||||
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 could 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.
|
||||
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>> unused) -> 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -387,7 +425,6 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
||||
* to indicate this method is effectively deprecated. A break point
|
||||
* placed here was never hit.
|
||||
*/
|
||||
|
||||
return modes.stream().filter(mode -> mode.getName().equals("timeline") || mode.getName().equals("ImageGallery"))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
@ -409,11 +446,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 +463,60 @@ 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...
|
||||
// RJCTODO: Get Brian's TODO resolved.
|
||||
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 +530,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 {
|
||||
|
@ -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");
|
||||
@ -71,7 +71,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 {
|
||||
|
||||
@ -148,19 +148,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);
|
||||
@ -186,13 +187,12 @@ 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 -> {
|
||||
|
||||
dataSourceStatusMap -> {
|
||||
int numStale = 0;
|
||||
int numNoAnalysis = 0;
|
||||
// NOTE: There is some overlapping code here with Controller.getStaleDataSourceIds(). We could possibly just use
|
||||
@ -239,7 +239,8 @@ public final class OpenAction extends CallableSystemAction {
|
||||
// NOTE: There could be no data....
|
||||
} 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.
|
||||
*
|
||||
@ -285,10 +286,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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -1,5 +1,5 @@
|
||||
#Updated by build script
|
||||
#Tue, 13 Nov 2018 17:30:09 -0500
|
||||
#Thu, 29 Nov 2018 12:23:03 -0500
|
||||
LBL_splash_window_title=Starting Autopsy
|
||||
SPLASH_HEIGHT=314
|
||||
SPLASH_WIDTH=538
|
||||
|
@ -1,4 +1,4 @@
|
||||
#Updated by build script
|
||||
#Tue, 13 Nov 2018 17:30:09 -0500
|
||||
#Thu, 29 Nov 2018 12:23:03 -0500
|
||||
CTL_MainWindow_Title=Autopsy 4.9.1
|
||||
CTL_MainWindow_Title_No_Project=Autopsy 4.9.1
|
||||
|
Loading…
x
Reference in New Issue
Block a user