Merge branch 'release-4.1.0' into bb_user_defs_in_reports

This commit is contained in:
Oliver Spohngellert 2016-03-01 13:37:09 -05:00
commit d36fb5b913
107 changed files with 2867 additions and 2954 deletions

View File

@ -5,11 +5,7 @@
<project name="org.sleuthkit.autopsy.core" default="netbeans" basedir="."> <project name="org.sleuthkit.autopsy.core" default="netbeans" basedir=".">
<description>Builds, tests, and runs the project org.sleuthkit.autopsy.core</description> <description>Builds, tests, and runs the project org.sleuthkit.autopsy.core</description>
<import file="nbproject/build-impl.xml"/> <import file="nbproject/build-impl.xml"/>
<!-- Verify that the TSK_HOME env variable is set --> <!-- Verify that the TSK_HOME env variable is set -->
<target name="findTSK"> <target name="findTSK">
<property environment="env"/> <property environment="env"/>
@ -30,7 +26,6 @@
<copy file="${env.TSK_HOME}/bindings/java/lib/sqlite-jdbc-3.8.11.jar" tofile="${basedir}/release/modules/ext/sqlite-jdbc-3.8.11.jar"/> <copy file="${env.TSK_HOME}/bindings/java/lib/sqlite-jdbc-3.8.11.jar" tofile="${basedir}/release/modules/ext/sqlite-jdbc-3.8.11.jar"/>
</target> </target>
<target name="init" depends="basic-init,files-init,build-init,-javac-init"> <target name="init" depends="basic-init,files-init,build-init,-javac-init">
<!-- get additional deps --> <!-- get additional deps -->
<antcall target="getTSKJars" /> <antcall target="getTSKJars" />

View File

@ -19,15 +19,13 @@
package org.sleuthkit.autopsy.casemodule; package org.sleuthkit.autopsy.casemodule;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.SleuthkitJNI; import org.sleuthkit.datamodel.SleuthkitJNI;
@ -35,66 +33,220 @@ import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskDataException; import org.sleuthkit.datamodel.TskDataException;
/* /*
* A background task that adds the given image to database using the Sleuthkit * A runnable that adds an image data source to the case database.
* JNI interface.
*
* It updates the given ProgressMonitor as it works through adding the image,
* and et the end, calls the specified Callback.
*/ */
class AddImageTask implements Runnable { class AddImageTask implements Runnable {
private final Logger logger = Logger.getLogger(AddImageTask.class.getName()); private final Logger logger = Logger.getLogger(AddImageTask.class.getName());
private final String deviceId;
private final Case currentCase;
// true if the process was requested to cancel
private final Object lock = new Object(); // synchronization object for cancelRequested
private volatile boolean cancelRequested = false;
//true if revert has been invoked.
private boolean reverted = false;
// true if there was a critical error in adding the data source
private boolean hasCritError = false;
private final List<String> errorList = new ArrayList<>();
private final DataSourceProcessorProgressMonitor progressMonitor;
private final DataSourceProcessorCallback callbackObj;
private final List<Content> newContents = Collections.synchronizedList(new ArrayList<Content>());
private SleuthkitJNI.CaseDbHandle.AddImageProcess addImageProcess;
private Thread dirFetcher;
private final String imagePath; private final String imagePath;
String timeZone; private final String timeZone;
boolean noFatOrphans; private final boolean ignoreFatOrphanFiles;
private final DataSourceProcessorProgressMonitor progressMonitor;
private final String dataSourceId; private final DataSourceProcessorCallback callback;
private boolean criticalErrorOccurred;
/* /*
* A thread that updates the progressMonitor with the name of the directory * The cancellation requested flag and SleuthKit add image process are
* currently being processed by the AddImageTask * guarded by a monitor (called a lock here to avoid confusion with the
* progress monitor) to synchronize cancelling the process (setting the flag
* and calling its stop method) and calling either its commit or revert
* method. The built-in monitor of the add image process can't be used for
* this because it is already used to synchronize its run (init part),
* commit, revert, and currentDirectory methods.
*
* TODO (AUT-2021): Merge SleuthkitJNI.AddImageProcess and AddImageTask
*/ */
private class CurrentDirectoryFetcher implements Runnable { private final Object tskAddImageProcessLock;
private boolean tskAddImageProcessStopped;
private SleuthkitJNI.CaseDbHandle.AddImageProcess tskAddImageProcess;
DataSourceProcessorProgressMonitor progressMonitor; /**
SleuthkitJNI.CaseDbHandle.AddImageProcess process; * Constructs a runnable task that adds an image to the case database.
*
* @param deviceId An ASCII-printable identifier for the device
* associated with the data source that is
* intended to be unique across multiple cases
* (e.g., a UUID).
* @param imagePath Path to the image file.
* @param timeZone The time zone to use when processing dates
* and times for the image, obtained from
* java.util.TimeZone.getID.
* @param ignoreFatOrphanFiles Whether to parse orphans if the image has a
* FAT filesystem.
* @param progressMonitor Progress monitor to report progress during
* processing.
* @param callback Callback to call when processing is done.
*/
AddImageTask(String deviceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
this.deviceId = deviceId;
this.imagePath = imagePath;
this.timeZone = timeZone;
this.ignoreFatOrphanFiles = ignoreFatOrphanFiles;
this.callback = callback;
this.progressMonitor = progressMonitor;
tskAddImageProcessLock = new Object();
}
CurrentDirectoryFetcher(DataSourceProcessorProgressMonitor aProgressMonitor, SleuthkitJNI.CaseDbHandle.AddImageProcess proc) { /**
this.progressMonitor = aProgressMonitor; * Adds the image to the case database.
this.process = proc; */
@Override
public void run() {
progressMonitor.setIndeterminate(true);
progressMonitor.setProgress(0);
Case currentCase = Case.getCurrentCase();
List<String> errorMessages = new ArrayList<>();
List<Content> newDataSources = new ArrayList<>();
try {
currentCase.getSleuthkitCase().acquireExclusiveLock();
synchronized (tskAddImageProcessLock) {
tskAddImageProcess = currentCase.makeAddImageProcess(timeZone, true, ignoreFatOrphanFiles);
}
Thread progressUpdateThread = new Thread(new ProgressUpdater(progressMonitor, tskAddImageProcess));
progressUpdateThread.start();
runAddImageProcess(errorMessages);
if (null != progressUpdateThread) {
progressUpdateThread.interrupt();
}
commitOrRevertAddImageProcess(currentCase, errorMessages, newDataSources);
progressMonitor.setProgress(100);
} finally {
currentCase.getSleuthkitCase().releaseExclusiveLock();
DataSourceProcessorCallback.DataSourceProcessorResult result;
if (criticalErrorOccurred) {
result = DataSourceProcessorResult.CRITICAL_ERRORS;
} else if (!errorMessages.isEmpty()) {
result = DataSourceProcessorResult.NONCRITICAL_ERRORS;
} else {
result = DataSourceProcessorResult.NO_ERRORS;
}
callback.done(result, errorMessages, newDataSources);
}
}
/*
* Attempts to cancel adding the image to the case database.
*/
public void cancelTask() {
synchronized (tskAddImageProcessLock) {
if (null != tskAddImageProcess) {
try {
/*
* All this does is set a flag that will make the TSK add
* image process exit when the flag is checked between
* processing steps. The state of the flag is not
* accessible, so record it here so that it is known that
* the revert method of the process object needs to be
* called.
*/
tskAddImageProcess.stop();
tskAddImageProcessStopped = true;
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error cancelling adding image %s to the case database", imagePath), ex); //NON-NLS
}
}
}
}
/**
* Runs the TSK add image process.
*
* @param errorMessages Error messages, if any, are added to this list for
* eventual return via the callback.
*/
private void runAddImageProcess(List<String> errorMessages) {
try {
tskAddImageProcess.run(deviceId, new String[]{imagePath});
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Critical error occurred adding image %s", imagePath), ex); //NON-NLS
criticalErrorOccurred = true;
errorMessages.add(ex.getMessage());
} catch (TskDataException ex) {
logger.log(Level.WARNING, String.format("Non-critical error occurred adding image %s", imagePath), ex); //NON-NLS
errorMessages.add(ex.getMessage());
}
}
/**
* Commits or reverts the results of the TSK add image process. If the
* process was stopped before it completed or there was a critical error the
* results are reverted, otherwise they are committed.
*
* @param currentCase The current case.
* @param errorMessages Error messages, if any, are added to this list for
* eventual return via the callback.
* @param newDataSources If the new image is successfully committed, it is
* added to this list for eventual return via the
* callback.
*
* @return
*/
private void commitOrRevertAddImageProcess(Case currentCase, List<String> errorMessages, List<Content> newDataSources) {
synchronized (tskAddImageProcessLock) {
if (tskAddImageProcessStopped || criticalErrorOccurred) {
try {
tskAddImageProcess.revert();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error reverting adding image %s to the case database", imagePath), ex); //NON-NLS
errorMessages.add(ex.getMessage());
criticalErrorOccurred = true;
}
} else {
try {
long imageId = tskAddImageProcess.commit();
if (imageId != 0) {
Image newImage = currentCase.getSleuthkitCase().getImageById(imageId);
String verificationError = newImage.verifyImageSize();
if (!verificationError.isEmpty()) {
errorMessages.add(verificationError);
}
newDataSources.add(newImage);
} else {
String errorMessage = String.format("Error commiting adding image %s to the case database, no object id returned", imagePath); //NON-NLS
logger.log(Level.SEVERE, errorMessage);
errorMessages.add(errorMessage);
criticalErrorOccurred = true;
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format("Error committing adding image %s to the case database", imagePath), ex); //NON-NLS
errorMessages.add(ex.getMessage());
criticalErrorOccurred = true;
}
}
}
}
/**
* A Runnable that updates the progress monitor with the name of the
* directory currently being processed by the SleuthKit add image process.
*/
private class ProgressUpdater implements Runnable {
private final DataSourceProcessorProgressMonitor progressMonitor;
private final SleuthkitJNI.CaseDbHandle.AddImageProcess tskAddImageProcess;
/**
* Constructs a Runnable that updates the progress monitor with the name
* of the directory currently being processed by the SleuthKit.
*
* @param progressMonitor
* @param tskAddImageProcess
*/
ProgressUpdater(DataSourceProcessorProgressMonitor progressMonitor, SleuthkitJNI.CaseDbHandle.AddImageProcess tskAddImageProcess) {
this.progressMonitor = progressMonitor;
this.tskAddImageProcess = tskAddImageProcess;
} }
/** /**
* @return the currently processing directory * Updates the progress monitor with the name of the directory currently
* being processed by the SleuthKit add image process.
*/ */
@Override @Override
public void run() { public void run() {
try { try {
while (!Thread.currentThread().isInterrupted()) { while (!Thread.currentThread().isInterrupted()) {
String currDir = process.currentDirectory(); String currDir = tskAddImageProcess.currentDirectory();
if (currDir != null) { if (currDir != null) {
if (!currDir.isEmpty()) { if (!currDir.isEmpty()) {
progressMonitor.setProgressText( progressMonitor.setProgressText(
@ -102,227 +254,20 @@ class AddImageTask implements Runnable {
currDir)); currDir));
} }
} }
// this sleep here prevents the UI from locking up /*
// due to too frequent updates to the progressMonitor above * The sleep here throttles the UI updates and provides a
* non-standard mechanism for completing this task by
* interrupting the thread in which it is running.
*
* TODO (AUT-1870): Replace this with giving the task to a
* java.util.concurrent.ScheduledThreadPoolExecutor that is
* shut down when the main task completes.
*/
Thread.sleep(500); Thread.sleep(500);
} }
} catch (InterruptedException ie) { } catch (InterruptedException expected) {
// nothing to do, thread was interrupted externally
// signaling the end of AddImageProcess
} }
} }
} }
/**
* Constructs a runnable task that adds an image to the case database.
*
* @param dataSourceId An ASCII-printable identifier for the data
* source that is intended to be unique across
* multiple cases (e.g., a UUID).
* @param imagePath Path to the image file.
* @param timeZone The time zone to use when processing dates
* and times for the image, obtained from
* java.util.TimeZone.getID.
* @param ignoreFatOrphanFiles Whether to parse orphans if the image has a
* FAT filesystem.
* @param monitor Progress monitor to report progress during
* processing.
* @param cbObj Callback to call when processing is done.
*/
AddImageTask(String dataSourceId, String imagePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor monitor, DataSourceProcessorCallback cbObj) {
currentCase = Case.getCurrentCase();
this.dataSourceId = dataSourceId;
this.imagePath = imagePath;
this.timeZone = timeZone;
this.noFatOrphans = ignoreFatOrphanFiles;
this.callbackObj = cbObj;
this.progressMonitor = monitor;
}
/**
* Starts the addImage process, but does not commit the results.
*
* @return
*
* @throws Exception
*/
@Override
public void run() {
errorList.clear();
try {
currentCase.getSleuthkitCase().acquireExclusiveLock();
addImageProcess = currentCase.makeAddImageProcess(timeZone, true, noFatOrphans);
dirFetcher = new Thread(new CurrentDirectoryFetcher(progressMonitor, addImageProcess));
try {
progressMonitor.setIndeterminate(true);
progressMonitor.setProgress(0);
dirFetcher.start();
addImageProcess.run(dataSourceId, new String[]{imagePath});
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Core errors occurred while running add image on " + imagePath, ex); //NON-NLS
hasCritError = true;
errorList.add(ex.getMessage());
} catch (TskDataException ex) {
logger.log(Level.WARNING, "Data errors occurred while running add image " + imagePath, ex); //NON-NLS
errorList.add(ex.getMessage());
}
postProcess();
} finally {
currentCase.getSleuthkitCase().releaseExclusiveLock();
}
}
/**
* Commit the newly added image to DB
*
*
* @throws Exception if commit or adding the image to the case failed
*/
private void commitImage() throws Exception {
long imageId = 0;
try {
imageId = addImageProcess.commit();
} catch (TskCoreException e) {
logger.log(Level.WARNING, "Errors occurred while committing the image " + imagePath, e); //NON-NLS
errorList.add(e.getMessage());
} finally {
if (imageId != 0) {
// get the newly added Image so we can return to caller
Image newImage = currentCase.getSleuthkitCase().getImageById(imageId);
//while we have the image, verify the size of its contents
String verificationErrors = newImage.verifyImageSize();
if (verificationErrors.equals("") == false) {
//data error (non-critical)
errorList.add(verificationErrors);
}
// Add the image to the list of new content
newContents.add(newImage);
}
logger.log(Level.INFO, "Image committed, imageId: {0}", imageId); //NON-NLS
logger.log(Level.INFO, PlatformUtil.getAllMemUsageInfo());
}
}
/**
* Post processing after the addImageProcess is done.
*
*/
private void postProcess() {
// cancel the directory fetcher
dirFetcher.interrupt();
if (cancelRequested() || hasCritError) {
logger.log(Level.WARNING, "Critical errors or interruption in add image process on {0}. Image will not be committed.", imagePath); //NON-NLS
revert();
}
if (!errorList.isEmpty()) {
logger.log(Level.INFO, "There were errors that occurred in add image process for {0}", imagePath); //NON-NLS
}
// When everything happens without an error:
if (!(cancelRequested() || hasCritError)) {
try {
if (addImageProcess != null) {
// commit image
try {
commitImage();
} catch (Exception ex) {
errorList.add(ex.getMessage());
// Log error/display warning
logger.log(Level.SEVERE, "Error adding image " + imagePath + " to case.", ex); //NON-NLS
}
} else {
logger.log(Level.SEVERE, "Missing image process object"); //NON-NLS
}
// Tell the progress monitor we're done
progressMonitor.setProgress(100);
} catch (Exception ex) {
//handle unchecked exceptions post image add
errorList.add(ex.getMessage());
logger.log(Level.WARNING, "Unexpected errors occurred while running post add image cleanup for " + imagePath, ex); //NON-NLS
logger.log(Level.SEVERE, "Error adding image " + imagePath + " to case", ex); //NON-NLS
}
}
// invoke the callBack, unless the caller cancelled
if (!cancelRequested()) {
doCallBack();
}
}
/*
* Call the callback with results, new content, and errors, if any
*/
private void doCallBack() {
DataSourceProcessorCallback.DataSourceProcessorResult result;
if (hasCritError) {
result = DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS;
} else if (!errorList.isEmpty()) {
result = DataSourceProcessorCallback.DataSourceProcessorResult.NONCRITICAL_ERRORS;
} else {
result = DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS;
}
// invoke the callback, passing it the result, list of new contents, and list of errors
callbackObj.done(result, errorList, newContents);
}
/*
* cancel the image addition, if possible
*/
public void cancelTask() {
synchronized (lock) {
cancelRequested = true;
try {
interrupt();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Failed to interrupt the add image task..."); //NON-NLS
}
}
}
/*
* Interrupt the add image process if it is still running
*/
private void interrupt() throws Exception {
try {
logger.log(Level.INFO, "interrupt() add image process"); //NON-NLS
addImageProcess.stop(); //it might take time to truly stop processing and writing to db
} catch (TskCoreException ex) {
throw new Exception(NbBundle.getMessage(this.getClass(), "AddImageTask.interrupt.exception.msg"), ex);
}
}
/*
* Revert - if image has already been added but not committed yet
*/
private void revert() {
if (!reverted) {
logger.log(Level.INFO, "Revert after add image process"); //NON-NLS
try {
addImageProcess.revert();
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Error reverting add image process", ex); //NON-NLS
}
reverted = true;
}
}
private boolean cancelRequested() {
synchronized (lock) {
return cancelRequested;
}
}
} }

View File

@ -146,6 +146,7 @@ final class AddImageWizardChooseDataSourceVisual extends JPanel {
* *
* @param panel instance of ImageTypePanel to change to * @param panel instance of ImageTypePanel to change to
*/ */
@SuppressWarnings("deprecation")
private void updateCurrentPanel(JPanel panel) { private void updateCurrentPanel(JPanel panel) {
currentPanel = panel; currentPanel = panel;
typePanel.removeAll(); typePanel.removeAll();

View File

@ -68,6 +68,7 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel<WizardDe
private final AddImageWizardChooseDataSourcePanel dataSourcePanel; private final AddImageWizardChooseDataSourcePanel dataSourcePanel;
private DataSourceProcessor dsProcessor; private DataSourceProcessor dsProcessor;
private boolean cancelled;
AddImageWizardIngestConfigPanel(AddImageWizardChooseDataSourcePanel dsPanel, AddImageAction action, AddImageWizardAddingProgressPanel proPanel) { AddImageWizardIngestConfigPanel(AddImageWizardChooseDataSourcePanel dsPanel, AddImageAction action, AddImageWizardAddingProgressPanel proPanel) {
this.addImageAction = action; this.addImageAction = action;
@ -228,6 +229,7 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel<WizardDe
@Override @Override
void cleanup() throws Exception { void cleanup() throws Exception {
cancelDataSourceProcessing(dataSourceId); cancelDataSourceProcessing(dataSourceId);
cancelled = true;
} }
}; };
@ -244,7 +246,6 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel<WizardDe
public void doneEDT(DataSourceProcessorCallback.DataSourceProcessorResult result, List<String> errList, List<Content> contents) { public void doneEDT(DataSourceProcessorCallback.DataSourceProcessorResult result, List<String> errList, List<Content> contents) {
dataSourceProcessorDone(dataSourceId, result, errList, contents); dataSourceProcessorDone(dataSourceId, result, errList, contents);
} }
}; };
progressPanel.setStateStarted(); progressPanel.setStateStarted();
@ -258,9 +259,6 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel<WizardDe
* Cancels the data source processing - in case the users presses 'Cancel' * Cancels the data source processing - in case the users presses 'Cancel'
*/ */
private void cancelDataSourceProcessing(UUID dataSourceId) { private void cancelDataSourceProcessing(UUID dataSourceId) {
new Thread(() -> {
Case.getCurrentCase().notifyFailedAddingDataSource(dataSourceId);
}).start();
dsProcessor.cancel(); dsProcessor.cancel();
} }
@ -303,21 +301,23 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel<WizardDe
progressPanel.addErrors(err, critErr); progressPanel.addErrors(err, critErr);
} }
newContents.clear();
newContents.addAll(contents);
//notify the UI of the new content added to the case //notify the UI of the new content added to the case
new Thread(() -> { new Thread(() -> {
if (!newContents.isEmpty()) { if (!contents.isEmpty()) {
Case.getCurrentCase().notifyDataSourceAdded(newContents.get(0), dataSourceId); Case.getCurrentCase().notifyDataSourceAdded(contents.get(0), dataSourceId);
} else { } else {
Case.getCurrentCase().notifyFailedAddingDataSource(dataSourceId); Case.getCurrentCase().notifyFailedAddingDataSource(dataSourceId);
} }
}).start(); }).start();
// Start ingest if we can if (!cancelled) {
progressPanel.setStateStarted(); newContents.clear();
startIngest(); newContents.addAll(contents);
progressPanel.setStateStarted();
startIngest();
} else {
cancelled = false;
}
} }
} }

View File

@ -219,11 +219,11 @@ public class Case implements SleuthkitCase.ErrorObserver {
* multi-user (using PostgreSql) * multi-user (using PostgreSql)
*/ */
@NbBundle.Messages({"Case_caseType_singleUser=Single-user case", @NbBundle.Messages({"Case_caseType_singleUser=Single-user case",
"Case_caseType_multiUser=Multi-user case"}) "Case_caseType_multiUser=Multi-user case"})
public enum CaseType { public enum CaseType {
SINGLE_USER_CASE(Bundle.Case_caseType_singleUser()), SINGLE_USER_CASE("Single-user case"), //NON-NLS
MULTI_USER_CASE(Bundle.Case_caseType_multiUser()); MULTI_USER_CASE("Multi-user case"); //NON-NLS
private final String caseType; private final String caseType;
@ -250,6 +250,14 @@ public class Case implements SleuthkitCase.ErrorObserver {
public String toString() { public String toString() {
return caseType; return caseType;
} }
String getLocalizedDisplayName() {
if (fromString(caseType) == SINGLE_USER_CASE) {
return Bundle.Case_caseType_singleUser();
} else {
return Bundle.Case_caseType_multiUser();
}
}
}; };
private String name; private String name;

View File

@ -92,7 +92,7 @@ class CasePropertiesForm extends javax.swing.JPanel {
CaseMetadata caseMetadata = new CaseMetadata(Paths.get(currentCase.getConfigFilePath())); CaseMetadata caseMetadata = new CaseMetadata(Paths.get(currentCase.getConfigFilePath()));
tbDbName.setText(caseMetadata.getCaseDatabaseName()); tbDbName.setText(caseMetadata.getCaseDatabaseName());
Case.CaseType caseType = caseMetadata.getCaseType(); Case.CaseType caseType = caseMetadata.getCaseType();
tbDbType.setText(caseType.toString()); tbDbType.setText(caseType.getLocalizedDisplayName());
if (caseType == Case.CaseType.SINGLE_USER_CASE) { if (caseType == Case.CaseType.SINGLE_USER_CASE) {
deleteCaseButton.setEnabled(true); deleteCaseButton.setEnabled(true);
} else { } else {

View File

@ -466,7 +466,11 @@ final class CollaborationMonitor {
*/ */
@Override @Override
public void run() { public void run() {
eventPublisher.publishRemotely(new CollaborationEvent(hostName, localTasksManager.getCurrentTasks())); try {
eventPublisher.publishRemotely(new CollaborationEvent(hostName, localTasksManager.getCurrentTasks()));
} catch (Exception ex) {
logger.log(Level.SEVERE, "Unexpected exception in HeartbeatTask", ex); //NON-NLS
}
} }
} }
@ -482,7 +486,11 @@ final class CollaborationMonitor {
*/ */
@Override @Override
public void run() { public void run() {
remoteTasksManager.finishStaleTasks(); try {
remoteTasksManager.finishStaleTasks();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Unexpected exception in StaleTaskDetectionTask", ex); //NON-NLS
}
} }
} }

View File

@ -31,7 +31,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor;
/** /**
* An image file data source processor that implements the DataSourceProcessor * A image file data source processor that implements the DataSourceProcessor
* service provider interface to allow integration with the add data source * service provider interface to allow integration with the add data source
* wizard. It also provides a run method overload to allow it to be used * wizard. It also provides a run method overload to allow it to be used
* independently of the wizard. * independently of the wizard.
@ -81,10 +81,10 @@ public class ImageDSProcessor implements DataSourceProcessor {
/** /**
* Gets a string that describes the type of data sources this processor is * Gets a string that describes the type of data sources this processor is
* able to process. * able to add to the case database. The string is suitable for display in a
* type selection UI component (e.g., a combo box).
* *
* @return A string suitable for display in a data source processor * @return A data source type display string for this data source processor.
* selection UI component (e.g., a combo box).
*/ */
public static String getType() { public static String getType() {
return DATA_SOURCE_TYPE; return DATA_SOURCE_TYPE;
@ -92,21 +92,23 @@ public class ImageDSProcessor implements DataSourceProcessor {
/** /**
* Gets a string that describes the type of data sources this processor is * Gets a string that describes the type of data sources this processor is
* able to process. * able to add to the case database. The string is suitable for display in a
* type selection UI component (e.g., a combo box).
* *
* @return A string suitable for display in a data source processor * @return A data source type display string for this data source processor.
* selection UI component (e.g., a combo box).
*/ */
@Override @Override
public String getDataSourceType() { public String getDataSourceType() {
return DATA_SOURCE_TYPE; return getType();
} }
/** /**
* Gets the panel that allows a user to select a data source and do any * Gets the panel that allows a user to select a data source and do any
* configuration the data source processor may require. * configuration required by the data source. The panel is less than 544
* pixels wide and less than 173 pixels high.
* *
* @return A JPanel less than 544 pixels wide and 173 pixels high. * @return A selection and configuration panel for this data source
* processor.
*/ */
@Override @Override
public JPanel getPanel() { public JPanel getPanel() {
@ -116,10 +118,11 @@ public class ImageDSProcessor implements DataSourceProcessor {
} }
/** /**
* Indicates whether the settings in the panel are valid and complete. * Indicates whether the settings in the selection and configuration panel
* are valid and complete.
* *
* @return True if the settings are valid and complete and the processor is * @return True if the settings are valid and complete and the processor is
* ready to have its run method called; false otherwise. * ready to have its run method called, false otherwise.
*/ */
@Override @Override
public boolean isPanelValid() { public boolean isPanelValid() {
@ -127,16 +130,18 @@ public class ImageDSProcessor implements DataSourceProcessor {
} }
/** /**
* Adds a data source to the case database using a separate thread and the * Adds a data source to the case database using a background task in a
* settings provided by the panel. Returns as soon as the background task is * separate thread and the settings provided by the selection and
* started and uses the callback object to signal task completion and return * configuration panel. Returns as soon as the background task is started.
* results. * The background task uses a callback object to signal task completion and
* return results.
* *
* NOTE: This method should not be called unless isPanelValid returns true. * This method should not be called unless isPanelValid returns true.
* *
* @param progressMonitor Progress monitor for reporting progress during * @param progressMonitor Progress monitor that will be used by the
* processing. * background task to report progress.
* @param callback Callback to call when processing is done. * @param callback Callback that will be used by the background task
* to return results.
*/ */
@Override @Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
@ -151,10 +156,11 @@ public class ImageDSProcessor implements DataSourceProcessor {
} }
/** /**
* Adds a data source to the case database using a separate thread and the * Adds a data source to the case database using a background task in a
* given settings instead of those provided by the panel. Returns as soon as * separate thread and the given settings instead of those provided by the
* the background task is started and uses the callback object to signal * selection and configuration panel. Returns as soon as the background task
* task completion and return results. * is started and uses the callback object to signal task completion and
* return results.
* *
* @param deviceId An ASCII-printable identifier for the device * @param deviceId An ASCII-printable identifier for the device
* associated with the data source that is * associated with the data source that is
@ -176,8 +182,11 @@ public class ImageDSProcessor implements DataSourceProcessor {
} }
/** /**
* Requests cancellation of the data source processing task after it is * Requests cancellation of the background task that adds a data source to
* started using the run method. Cancellation is not guaranteed. * the case database, after the task is started using the run method. This
* is a "best effort" cancellation, with no guarantees that the case
* database will be unchanged. If cancellation succeeded, the list of new
* data sources returned by the background task will be empty.
*/ */
@Override @Override
public void cancel() { public void cancel() {
@ -185,7 +194,8 @@ public class ImageDSProcessor implements DataSourceProcessor {
} }
/** /**
* Resets the panel. * Resets the selection and configuration panel for this data source
* processor.
*/ */
@Override @Override
public void reset() { public void reset() {
@ -199,7 +209,7 @@ public class ImageDSProcessor implements DataSourceProcessor {
/** /**
* Sets the configuration of the data source processor without using the * Sets the configuration of the data source processor without using the
* configuration panel. * selection and configuration panel.
* *
* @param imagePath Path to the image file. * @param imagePath Path to the image file.
* @param timeZone The time zone to use when processing dates * @param timeZone The time zone to use when processing dates

View File

@ -190,7 +190,7 @@ public class ImageFilePanel extends JPanel implements DocumentListener {
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
); );
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
@SuppressWarnings("deprecation")
private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
String oldText = pathTextField.getText(); String oldText = pathTextField.getText();
// set the current directory of the FileChooser if the ImagePath Field is valid // set the current directory of the FileChooser if the ImagePath Field is valid

View File

@ -61,10 +61,10 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
/** /**
* Gets a string that describes the type of data sources this processor is * Gets a string that describes the type of data sources this processor is
* able to process. * able to add to the case database. The string is suitable for display in a
* type selection UI component (e.g., a combo box).
* *
* @return A string suitable for display in a data source processor * @return A data source type display string for this data source processor.
* selection UI component (e.g., a combo box).
*/ */
public static String getType() { public static String getType() {
return DATA_SOURCE_TYPE; return DATA_SOURCE_TYPE;
@ -72,10 +72,10 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
/** /**
* Gets a string that describes the type of data sources this processor is * Gets a string that describes the type of data sources this processor is
* able to process. * able to add to the case database. The string is suitable for display in a
* type selection UI component (e.g., a combo box).
* *
* @return A string suitable for display in a data source processor * @return A data source type display string for this data source processor.
* selection UI component (e.g., a combo box).
*/ */
@Override @Override
public String getDataSourceType() { public String getDataSourceType() {
@ -83,10 +83,12 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
} }
/** /**
* Gets the JPanel that allows a user to select a data source and do any * Gets the panel that allows a user to select a data source and do any
* configuration the data source processor may require. * configuration required by the data source. The panel is less than 544
* pixels wide and less than 173 pixels high.
* *
* @return A JPanel less than 544 pixels wide and 173 pixels high. * @return A selection and configuration panel for this data source
* processor.
*/ */
@Override @Override
public JPanel getPanel() { public JPanel getPanel() {
@ -95,10 +97,11 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
} }
/** /**
* Indicates whether the settings in the panel are valid and complete. * Indicates whether the settings in the selection and configuration panel
* are valid and complete.
* *
* @return True if the settings are valid and complete and the processor is * @return True if the settings are valid and complete and the processor is
* ready to have its run method called; false otherwise. * ready to have its run method called, false otherwise.
*/ */
@Override @Override
public boolean isPanelValid() { public boolean isPanelValid() {
@ -106,16 +109,18 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
} }
/** /**
* Adds a data source to the case database using a separate thread and the * Adds a data source to the case database using a background task in a
* settings provided by the panel. Returns as soon as the background task is * separate thread and the settings provided by the selection and
* started and uses the callback object to signal task completion and return * configuration panel. Returns as soon as the background task is started.
* results. * The background task uses a callback object to signal task completion and
* return results.
* *
* NOTE: This method should not be called unless isPanelValid returns true. * This method should not be called unless isPanelValid returns true.
* *
* @param progressMonitor Progress monitor for reporting progress during * @param progressMonitor Progress monitor that will be used by the
* processing. * background task to report progress.
* @param callback Callback to call when processing is done. * @param callback Callback that will be used by the background task
* to return results.
*/ */
@Override @Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
@ -130,10 +135,11 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
} }
/** /**
* Adds a data source to the case database using a separate thread and the * Adds a data source to the case database using a background task in a
* given settings instead of those provided by the panel. Returns as soon as * separate thread and the given settings instead of those provided by the
* the background task is started and uses the callback object to signal * selection and configuration panel. Returns as soon as the background task
* task completion and return results. * is started and uses the callback object to signal task completion and
* return results.
* *
* @param deviceId An ASCII-printable identifier for the device * @param deviceId An ASCII-printable identifier for the device
* associated with the data source that is * associated with the data source that is
@ -155,8 +161,11 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
} }
/** /**
* Requests cancellation of the data source processing task after it is * Requests cancellation of the background task that adds a data source to
* started using the run method. Cancellation is not guaranteed. * the case database, after the task is started using the run method. This
* is a "best effort" cancellation, with no guarantees that the case
* database will be unchanged. If cancellation succeeded, the list of new
* data sources returned by the background task will be empty.
*/ */
@Override @Override
public void cancel() { public void cancel() {
@ -164,7 +173,8 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
} }
/** /**
* Resets the panel. * Resets the selection and configuration panel for this data source
* processor.
*/ */
@Override @Override
public void reset() { public void reset() {

View File

@ -59,10 +59,10 @@ public class LocalFilesDSProcessor implements DataSourceProcessor {
/** /**
* Gets a string that describes the type of data sources this processor is * Gets a string that describes the type of data sources this processor is
* able to process. * able to add to the case database. The string is suitable for display in a
* type selection UI component (e.g., a combo box).
* *
* @return A string suitable for display in a data source processor * @return A data source type display string for this data source processor.
* selection UI component (e.g., a combo box).
*/ */
public static String getType() { public static String getType() {
return DATA_SOURCE_TYPE; return DATA_SOURCE_TYPE;
@ -70,10 +70,10 @@ public class LocalFilesDSProcessor implements DataSourceProcessor {
/** /**
* Gets a string that describes the type of data sources this processor is * Gets a string that describes the type of data sources this processor is
* able to process. * able to add to the case database. The string is suitable for display in a
* type selection UI component (e.g., a combo box).
* *
* @return A string suitable for display in a data source processor * @return A data source type display string for this data source processor.
* selection UI component (e.g., a combo box).
*/ */
@Override @Override
public String getDataSourceType() { public String getDataSourceType() {
@ -82,9 +82,11 @@ public class LocalFilesDSProcessor implements DataSourceProcessor {
/** /**
* Gets the panel that allows a user to select a data source and do any * Gets the panel that allows a user to select a data source and do any
* configuration the data source processor may require. * configuration required by the data source. The panel is less than 544
* pixels wide and less than 173 pixels high.
* *
* @return A JPanel less than 544 pixels wide and 173 pixels high. * @return A selection and configuration panel for this data source
* processor.
*/ */
@Override @Override
public JPanel getPanel() { public JPanel getPanel() {
@ -93,10 +95,11 @@ public class LocalFilesDSProcessor implements DataSourceProcessor {
} }
/** /**
* Indicates whether the settings in the panel are valid and complete. * Indicates whether the settings in the selection and configuration panel
* are valid and complete.
* *
* @return True if the settings are valid and complete and the processor is * @return True if the settings are valid and complete and the processor is
* ready to have its run method called; false otherwise. * ready to have its run method called, false otherwise.
*/ */
@Override @Override
public boolean isPanelValid() { public boolean isPanelValid() {
@ -104,16 +107,18 @@ public class LocalFilesDSProcessor implements DataSourceProcessor {
} }
/** /**
* Adds a data source to the case database using a separate thread and the * Adds a data source to the case database using a background task in a
* settings provided by the panel. Returns as soon as the background task is * separate thread and the settings provided by the selection and
* started and uses the callback object to signal task completion and return * configuration panel. Returns as soon as the background task is started.
* results. * The background task uses a callback object to signal task completion and
* return results.
* *
* NOTE: This method should not be called unless isPanelValid returns true. * This method should not be called unless isPanelValid returns true.
* *
* @param progressMonitor Progress monitor for reporting progress during * @param progressMonitor Progress monitor that will be used by the
* processing. * background task to report progress.
* @param callback Callback to call when processing is done. * @param callback Callback that will be used by the background task
* to return results.
*/ */
@Override @Override
public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) {
@ -125,10 +130,11 @@ public class LocalFilesDSProcessor implements DataSourceProcessor {
} }
/** /**
* Adds a data source to the case database using a separate thread and the * Adds a data source to the case database using a background task in a
* given settings instead of those provided by the panel. Returns as soon as * separate thread and the given settings instead of those provided by the
* the background task is started and uses the callback object to signal * selection and configuration panel. Returns as soon as the background task
* task completion and return results. * is started and uses the callback object to signal task completion and
* return results.
* *
* @param deviceId An ASCII-printable identifier for the * @param deviceId An ASCII-printable identifier for the
* device associated with the data source * device associated with the data source
@ -151,18 +157,22 @@ public class LocalFilesDSProcessor implements DataSourceProcessor {
} }
/** /**
* Requests cancellation of the data source processing task after it is * Requests cancellation of the background task that adds a data source to
* started using the run method. Cancellation is not guaranteed. * the case database, after the task is started using the run method. This
* is a "best effort" cancellation, with no guarantees that the case
* database will be unchanged. If cancellation succeeded, the list of new
* data sources returned by the background task will be empty.
*
* TODO (AUT-1907): Implement cancellation by deleting rows added to the
* case database.
*/ */
@Override @Override
public void cancel() { public void cancel() {
/*
* Cancellation is not currently supported.
*/
} }
/** /**
* Resets the panel. * Resets the selection and configuration panel for this data source
* processor.
*/ */
@Override @Override
public void reset() { public void reset() {

View File

@ -239,7 +239,7 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel<WizardDesc
Object res2 = DialogDisplayer.getDefault().notify(d2); Object res2 = DialogDisplayer.getDefault().notify(d2);
if (res2 != null && res2 == DialogDescriptor.YES_OPTION) { if (res2 != null && res2 == DialogDescriptor.YES_OPTION) {
// if user say yes // if user says yes
try { try {
createDirectory(caseDirPath, getComponent().getCaseType()); createDirectory(caseDirPath, getComponent().getCaseType());
} catch (Exception ex) { } catch (Exception ex) {
@ -251,7 +251,7 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel<WizardDesc
} }
} }
if (res2 != null && res2 == DialogDescriptor.NO_OPTION) { if (res2 != null && res2 == DialogDescriptor.NO_OPTION) {
// if user say no // if user says no
validationError(NbBundle.getMessage(this.getClass(), validationError(NbBundle.getMessage(this.getClass(),
"NewCaseWizardPanel1.validate.errMsg.prevCreateBaseDir.msg", "NewCaseWizardPanel1.validate.errMsg.prevCreateBaseDir.msg",
caseDirPath)); caseDirPath));

View File

@ -389,7 +389,11 @@ public class ServicesMonitor {
*/ */
@Override @Override
public void run() { public void run() {
checkAllServices(); try {
checkAllServices();
} catch (Exception ex) {
logger.log(Level.SEVERE, "Unexpected exception in CrashDetectionTask", ex); //NON-NLS
}
} }
} }

View File

@ -22,18 +22,24 @@ import javax.swing.JPanel;
/** /**
* Interface implemented by classes that add data sources of a particular type * Interface implemented by classes that add data sources of a particular type
* (e.g., images, local disks, virtual directories of local/logical files, etc.) * (e.g., images, local disks, virtual directories of local/logical files) to a
* to a case database. A data source processor is NOT responsible for analyzing * case database. A data source processor is NOT responsible for analyzing the
* the data source (running ingest modules on the data source and its contents). * data source, i.e., running ingest modules on the data source and its
* contents.
* *
* Data source processors plug in to the add data source wizard and should * Data source processors plug in to the add data source wizard and should
* provide a JPanel to allow a user to select a data source and do any * provide a UI panel to allow a user to select a data source and do any
* configuration the data source processor may require. The panel should support * configuration required by the data source processor. The selection and
* addition of the add data source wizard as a property change listener and * configuration panel should support addition of the add data source wizard as
* should fire DSP_PANEL_EVENT property changes to communicate with the wizard. * a property change listener and should fire DSP_PANEL_EVENT property change
* events to communicate with the wizard.
* *
* Data source processors should perform all processing on a separate thread, * Data source processors should perform all processing in a background task in
* reporting results using a callback object. * a separate thread, reporting results using a callback object.
*
* It is recommended that implementers provide an overload of the run method
* that allows the data source processor to be run independently of the
* selection and configuration panel.
*/ */
public interface DataSourceProcessor { public interface DataSourceProcessor {
@ -48,64 +54,76 @@ public interface DataSourceProcessor {
enum DSP_PANEL_EVENT { enum DSP_PANEL_EVENT {
/** /**
* Fire this event when the user changes something in the panel to * This event is fired when the user changes something in the selection
* notify the add data source wizard that it should call isPanelValid. * and configuration panel. It notifies the add data source wizard that
* it should call isPanelValid.
*/ */
UPDATE_UI, UPDATE_UI,
/** /**
* Fire this event to make the add data source wizard move focus to the * This event is fired to make the add data source wizard move focus to
* next button. * the wizard's next button.
* @deprecated Use UPDATE_UI.
*/ */
@Deprecated
FOCUS_NEXT FOCUS_NEXT
}; };
/** /**
* Gets a string that describes the type of data sources this processor is * Gets a string that describes the type of data sources this processor is
* able to process. * able to add to the case database. The string is suitable for display in a
* type selection UI component (e.g., a combo box).
* *
* @return A string suitable for display in a data source processor * @return A data source type display string for this data source processor.
* selection UI component (e.g., a combo box).
*/ */
String getDataSourceType(); String getDataSourceType();
/** /**
* Gets the panel that allows a user to select a data source and do any * Gets the panel that allows a user to select a data source and do any
* configuration the data source processor may require. * configuration required by the data source. The panel is less than 544
* pixels wide and less than 173 pixels high.
* *
* @return A JPanel less than 544 pixels wide and 173 pixels high. * @return A selection and configuration panel for this data source
* processor.
*/ */
JPanel getPanel(); JPanel getPanel();
/** /**
* Indicates whether the settings in the panel are valid and complete. * Indicates whether the settings in the selection and configuration panel
* are valid and complete.
* *
* @return True if the settings are valid and complete and the processor is * @return True if the settings are valid and complete and the processor is
* ready to have its run method called; false otherwise. * ready to have its run method called, false otherwise.
*/ */
boolean isPanelValid(); boolean isPanelValid();
/** /**
* Adds a data source to the case database using a separate thread and the * Adds a data source to the case database using a background task in a
* settings provided by the panel. Returns as soon as the background task is * separate thread and the settings provided by the selection and
* started and uses the callback object to signal task completion and return * configuration panel. Returns as soon as the background task is started.
* results. * The background task uses a callback object to signal task completion and
* return results.
* *
* NOTE: This method should not be called unless isPanelValid returns true. * This method should not be called unless isPanelValid returns true.
* *
* @param progressMonitor Progress monitor for reporting progress during * @param progressMonitor Progress monitor that will be used by the
* processing. * background task to report progress.
* @param callback Callback to call when processing is done. * @param callback Callback that will be used by the background task
* to return results.
*/ */
void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback); void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback);
/** /**
* Requests cancellation of the data source processing task after it is * Requests cancellation of the background task that adds a data source to
* started using the run method. Cancellation is not guaranteed. * the case database, after the task is started using the run method. This
* is a "best effort" cancellation, with no guarantees that the case
* database will be unchanged. If cancellation succeeded, the list of new
* data sources returned by the background task will be empty.
*/ */
void cancel(); void cancel();
/** /**
* Resets the panel. * Resets the selection and configuration panel for this data source
* processor.
*/ */
void reset(); void reset();
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-2014 Basis Technology Corp. * Copyright 2013-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -23,56 +23,72 @@ import java.util.List;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
/** /**
* Abstract class for a callback for a DataSourceProcessor. * An abstract base class for callback objects to be given to data source
* * processors for use by the background tasks that add data sources to a case
* Ensures that DSP invokes the caller overridden method, doneEDT(), in the EDT * database. The callback objects are used to signal task completion and return
* thread. * results.
* *
* Concrete implementations of DataSourceProcessorCallback should override
* either the done method or the doneEDT method, but not both.
*/ */
public abstract class DataSourceProcessorCallback { public abstract class DataSourceProcessorCallback {
public enum DataSourceProcessorResult { public enum DataSourceProcessorResult {
NO_ERRORS, ///< No errors were encountered while ading the data source /**
CRITICAL_ERRORS, ///< No data was added to the database. There were fundamental errors processing the data (such as no data or system failure). * No errors occurred while ading the data source to the case database.
NONCRITICAL_ERRORS, ///< There was data added to the database, but there were errors from data corruption or a small number of minor issues. */
NO_ERRORS,
/**
* Critical errors occurred while ading the data source to the case
* database. The data source was not added to the case database.
*/
CRITICAL_ERRORS,
/**
* Non-critical errors occurred while adding the data source to the case
* database. The data source was added to the database, but the data
* source may have been corrupted in some way.
*/
NONCRITICAL_ERRORS
}; };
/** /**
* Called by a DSP implementation when it is done adding a data source to * Called by a data source processor when it is done adding a data source to
* the database. Users of the DSP can override this method if they do not * the case database, this method adds a task to call the doneEDT method to
* want to be notified on the EDT. Otherwise, this method will call * the EDT task queue.
* doneEDT() with the same arguments.
* *
* @param result Code for status * IMPORTANT: Concrete implementations of DataSourceProcessorCallback should
* @param errList List of error strings * override this method if the callback SHOULD NOT be done in the EDT.
* @param newContents List of root Content objects that were added to *
* database. Typically only one is given. * @param result Result code.
* @param errList List of error messages, possibly empty.
* @param newDataSources A list of the data sources added, empty if critical
* errors occurred or processing was successfully
* cancelled.
*/ */
public void done(DataSourceProcessorResult result, List<String> errList, List<Content> newContents) { public void done(DataSourceProcessorResult result, List<String> errList, List<Content> newDataSources) {
final DataSourceProcessorResult resultf = result; final DataSourceProcessorResult resultf = result;
final List<String> errListf = errList; final List<String> errListf = errList;
final List<Content> newContentsf = newContents; final List<Content> newContentsf = newDataSources;
EventQueue.invokeLater(() -> {
// Invoke doneEDT() that runs on the EDT . doneEDT(resultf, errListf, newContentsf);
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
doneEDT(resultf, errListf, newContentsf);
}
}); });
} }
/** /**
* Called by done() if the default implementation is used. Users of DSPs * Called by a data source processor when it is done adding a data source to
* that have UI updates to do after the DSP is finished adding the DS can * the case database, if the default done method has not been overridden.
* implement this method to receive the updates on the EDT.
* *
* @param result Code for status * IMPORTANT: Concrete implementations of DataSourceProcessorCallback should
* @param errList List of error strings * override the done method and provide an implementation of this method
* @param newContents List of root Content objects that were added to * that throws an UnsupportedOperationException if the callback SHOULD NOT
* database. Typically only one is given. * be done in the EDT.
*
* @param result Result code.
* @param errList List of error messages, possibly empty.
* @param newDataSources A list of the data sources added, empty if critical
* errors occurred or processing was successfully
* cancelled.
*/ */
public abstract void doneEDT(DataSourceProcessorResult result, List<String> errList, List<Content> newContents); abstract public void doneEDT(DataSourceProcessorResult result, List<String> errList, List<Content> newDataSources);
}; };

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -47,16 +47,22 @@ import org.sleuthkit.datamodel.SleuthkitJNI;
*/ */
public final class AboutWindowPanel extends JPanel implements HyperlinkListener { public final class AboutWindowPanel extends JPanel implements HyperlinkListener {
private static final Logger Logger = org.sleuthkit.autopsy.coreutils.Logger.getLogger(AboutWindowPanel.class.getName()); private static final long serialVersionUID = 1L;
private URL url = null; private URL url = null;
private final Icon about;
private Icon about;
private boolean verboseLogging; private boolean verboseLogging;
public AboutWindowPanel() {
about = new ImageIcon(ImageUtilities.loadImage("org/sleuthkit/autopsy/images/splash.png"));
init();
}
public AboutWindowPanel(String pathToBrandingImage) { public AboutWindowPanel(String pathToBrandingImage) {
about = new ImageIcon(ImageUtilities.loadImage(pathToBrandingImage)); about = new ImageIcon(ImageUtilities.loadImage(pathToBrandingImage));
init();
}
private void init() {
initComponents(); initComponents();
logoLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); logoLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
description.setText(org.openide.util.NbBundle.getMessage(AboutWindowPanel.class, description.setText(org.openide.util.NbBundle.getMessage(AboutWindowPanel.class,
@ -67,8 +73,7 @@ public final class AboutWindowPanel extends JPanel implements HyperlinkListener
copyright.setBackground(getBackground()); copyright.setBackground(getBackground());
if (verboseLoggingIsSet()) { if (verboseLoggingIsSet()) {
disableVerboseLoggingButton(); disableVerboseLoggingButton();
} }
} }
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents

View File

@ -148,7 +148,7 @@ FXVideoPanel.progress.bufferingCancelled=media buffering was canceled
FXVideoPanel.progress.bufferingInterrupted=media buffering was interrupted FXVideoPanel.progress.bufferingInterrupted=media buffering was interrupted
FXVideoPanel.progress.errorWritingVideoToDisk=Error writing video to disk FXVideoPanel.progress.errorWritingVideoToDisk=Error writing video to disk
OptionsCategory_Name_Multi_User_Settings=Multi-user OptionsCategory_Name_Multi_User_Settings=Multi-user
OptionsCategory_Keywords_Multi_User_Options=Multi-user Options OptionsCategory_Keywords_Multi_User_Options=Multi-user Settings
MultiUserSettingsPanel.lbSolrSettings.text=Solr Settings MultiUserSettingsPanel.lbSolrSettings.text=Solr Settings
MultiUserSettingsPanel.cbEnableMultiUser.text=Enable Multi-user cases MultiUserSettingsPanel.cbEnableMultiUser.text=Enable Multi-user cases
MultiUserSettingsPanel.lbDatabaseSettings.text=Database Settings MultiUserSettingsPanel.lbDatabaseSettings.text=Database Settings

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -23,38 +23,67 @@ import org.openide.nodes.FilterNode;
import org.openide.nodes.Node; import org.openide.nodes.Node;
/** /**
* Complementary class to TableFilterNode. * A children (child factory) implementation for a TableFilterNode. A
* TableFilterNode creates at most one layer of child nodes for the node it
* wraps. It is designed to be used for nodes displayed in Autopsy table views.
*/ */
class TableFilterChildren extends FilterNode.Children { class TableFilterChildren extends FilterNode.Children {
/** /**
* the constructor * Constructs a children (child factory) implementation for a
* TableFilterNode. A TableFilterNode creates at most one layer of child
* nodes for the node it wraps. It is designed to be used for nodes
* displayed in Autopsy table views.
*
* @param wrappedNode The node wrapped by the TableFilterNode.
*/ */
TableFilterChildren(Node arg) { TableFilterChildren(Node wrappedNode) {
super(arg); super(wrappedNode);
}
@Override
protected Node copyNode(Node arg0) {
return new TableFilterNode(arg0, false);
}
@Override
protected Node[] createNodes(Node arg0) {
// filter out the children
return new Node[]{this.copyNode(arg0)};
} }
/** /**
* Converts the given FsContent into "Children". * Copies a TableFilterNode, with the create children (child factory) flag
* set to false.
* *
* @param fs * @param nodeToCopy The TableFilterNode to copy.
* *
* @return children * @return A copy of a TableFilterNode.
*/ */
public static Children createInstance(Node arg, boolean createChild) { @Override
if (createChild) { protected Node copyNode(Node nodeToCopy) {
return new TableFilterChildren(arg); return new TableFilterNode(nodeToCopy, false);
}
/**
* Creates the child nodes represented by this children (child factory)
* object.
*
* @param key The key, i.e., the node, for which to create the child nodes.
*
* @return
*/
@Override
protected Node[] createNodes(Node key) {
return new Node[]{this.copyNode(key)};
}
/**
* Creates a children (child factory) object for a node wrapped in a
* TableFilterNode. A TableFilterNode creates at most one layer of child
* nodes for the node it wraps. It is designed to be used for nodes
* displayed in Autopsy table views.
*
*
* @param wrappedNode The node wrapped by the TableFilterNode.
* @param createChildren True if a children (child factory) object should be
* created for the wrapped node.
*
* @return A children (child factory) object for a node wrapped by a
* TableFilterNode.
*/
public static Children createInstance(Node wrappedNode, boolean createChildren) {
if (createChildren) {
return new TableFilterChildren(wrappedNode);
} else { } else {
return Children.LEAF; return Children.LEAF;
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -23,48 +23,40 @@ import org.openide.nodes.Node;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
/** /**
* This class is used to filter the nodes that we want to show on the * A filter node that creates at most one layer of child nodes for the node it
* "TreeTableView". So basically we just want to show one layer of nodes from * wraps. It is designed to be used for nodes displayed in Autopsy table views.
* it's parent.
*
* @author jantonius
*/ */
public class TableFilterNode extends FilterNode { public class TableFilterNode extends FilterNode {
private boolean createChild; private final boolean createChildren;
private String itemType;
/** /**
* the constructor * Constructs a filter node that creates at most one layer of child nodes
*/ * for the node it wraps. It is designed to be used for nodes displayed in
public TableFilterNode(Node arg, boolean crChild) { * Autopsy table views.
super(arg, TableFilterChildren.createInstance(arg, crChild));
this.createChild = crChild;
this.itemType = "";
}
public TableFilterNode(Node arg, boolean crChild, String itemType) {
super(arg, TableFilterChildren.createInstance(arg, crChild));
this.createChild = crChild;
this.itemType = itemType;
}
/**
* Override the display name / header for the first (tree) column on the
* "TreeTableView".
* *
* @return disName the display name for the first column * @param wrappedNode The node to wrap in the filter node.
* @param createChildren True if a children (child factory) object should be
* created for the wrapped node.
*/
public TableFilterNode(Node wrappedNode, boolean createChildren) {
super(wrappedNode, TableFilterChildren.createInstance(wrappedNode, createChildren));
this.createChildren = createChildren;
}
/**
* Returns a display name for the wrapped node, for use in the first column
* of an Autopsy table view.
*
* @return The display name.
*/ */
@Override @Override
public String getDisplayName() { public String getDisplayName() {
if (createChild) { if (createChildren) {
return NbBundle.getMessage(this.getClass(), "TableFilterNode.displayName.text"); return NbBundle.getMessage(this.getClass(), "TableFilterNode.displayName.text");
} else { } else {
return super.getDisplayName(); return super.getDisplayName();
} }
} }
public String getItemType() {
return itemType;
}
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011 Basis Technology Corp. * Copyright 2011-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -95,16 +95,18 @@ class ThumbnailViewNode extends FilterNode {
super.done(); super.done();
try { try {
iconCache = new SoftReference<>(super.get()); iconCache = new SoftReference<>(super.get());
progressHandle.finish();
fireIconChange(); fireIconChange();
} catch (InterruptedException | ExecutionException ex) {
Logger.getLogger(ThumbnailViewNode.class.getName()).log(Level.SEVERE, "Error getting thumbnail icon", ex); //NON-NLS
} finally {
progressHandle.finish();
if (timer != null) { if (timer != null) {
timer.stop(); timer.stop();
timer = null; timer = null;
} }
} catch (InterruptedException | ExecutionException ex) { swingWorker = null;
Logger.getLogger(ThumbnailViewNode.class.getName()).log(Level.SEVERE, "Error getting thumbnail icon", ex); //NON-NLS
} }
swingWorker = null;
} }
}; };
swingWorker.execute(); swingWorker.execute();

View File

@ -20,8 +20,4 @@ PlatformUtil.getPhysicalMemInfo.usageText=Physical memory usage (max, total, fre
PlatformUtil.getAllMemUsageInfo.usageText={0}\n\ PlatformUtil.getAllMemUsageInfo.usageText={0}\n\
{1}\n\ {1}\n\
Process Virtual Memory\: {2} Process Virtual Memory\: {2}
StringExtract.illegalStateException.cannotInit.msg=Unicode table not properly initialized, cannot instantiate StringExtract StringExtract.illegalStateException.cannotInit.msg=Unicode table not properly initialized, cannot instantiate StringExtract
ImageUtils.GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}
ImageUtils.GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}
ImageUtils.ReadImageTask.mesage.text=Reading image\: {0}
VideoUtils.genVideoThumb.progress.text=extracting temporary file {0}

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2012-15 Basis Technology Corp. * Copyright 2012-16 Basis Technology Corp.
* *
* Copyright 2012 42six Solutions. * Copyright 2012 42six Solutions.
* Contact: aebadirad <at> 42six <dot> com * Contact: aebadirad <at> 42six <dot> com
@ -30,6 +30,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
@ -74,10 +75,6 @@ public class ImageUtils {
private static final Logger LOGGER = Logger.getLogger(ImageUtils.class.getName()); private static final Logger LOGGER = Logger.getLogger(ImageUtils.class.getName());
private static final String COULD_NOT_WRITE_CACHE_THUMBNAIL = "Could not write cache thumbnail: "; //NOI18N NON-NLS
private static final String COULD_NOT_CREATE_IMAGE_INPUT_STREAM = "Could not create ImageInputStream."; //NOI18N NON-NLS
private static final String NO_IMAGE_READER_FOUND_FOR_ = "No ImageReader found for "; //NOI18N NON-NLS
/** /**
* save thumbnails to disk as this format * save thumbnails to disk as this format
*/ */
@ -594,7 +591,7 @@ public class ImageUtils {
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
if (input == null) { if (input == null) {
IIOException iioException = new IIOException(COULD_NOT_CREATE_IMAGE_INPUT_STREAM); IIOException iioException = new IIOException("Could not create ImageInputStream.");
LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file)); LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
throw iioException; throw iioException;
} }
@ -613,7 +610,7 @@ public class ImageUtils {
reader.dispose(); reader.dispose();
} }
} else { } else {
IIOException iioException = new IIOException(NO_IMAGE_READER_FOUND_FOR_ + getContentPathSafe(file)); IIOException iioException = new IIOException("No ImageReader found.");
LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file)); LOGGER.log(Level.WARNING, errorTemplate + iioException.toString(), getContentPathSafe(file));
throw iioException; throw iioException;
@ -646,18 +643,19 @@ public class ImageUtils {
*/ */
static private class GetThumbnailTask extends ReadImageTaskBase { static private class GetThumbnailTask extends ReadImageTaskBase {
private static final String FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION = "Failed to read image for thumbnail generation."; //NOI18N NON-NLS private static final String FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION = "Failed to read {0} for thumbnail generation."; //NOI18N NON-NLS
private final int iconSize; private final int iconSize;
private final File cacheFile; private final File cacheFile;
private final boolean defaultOnFailure; private final boolean defaultOnFailure;
// @NbBundle.Messages({"# {0} - file name", @NbBundle.Messages({"# {0} - file name",
// "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}", "# {0} - file name", "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}",
// "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"}) "# {0} - file name",
"GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) { private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
super(file); super(file);
updateMessage(NbBundle.getMessage(this.getClass(), "ImageUtils.GetOrGenerateThumbnailTask.loadingThumbnailFor", file.getName())); updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName()));
this.iconSize = iconSize; this.iconSize = iconSize;
this.defaultOnFailure = defaultOnFailure; this.defaultOnFailure = defaultOnFailure;
this.cacheFile = getCachedThumbnailLocation(file.getId()); this.cacheFile = getCachedThumbnailLocation(file.getId());
@ -678,36 +676,39 @@ public class ImageUtils {
if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) { if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) {
return SwingFXUtils.toFXImage(cachedThumbnail, null); return SwingFXUtils.toFXImage(cachedThumbnail, null);
} }
} catch (IOException ex) { } catch (Exception ex) {
LOGGER.log(Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS LOGGER.log(Level.WARNING, "ImageIO had a problem reading the cached thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
cacheFile.delete(); //since we can't read the file we might as well delete it.
} }
} }
if (isCancelled()) { if (isCancelled()) {
return null; return null;
} }
//There was no correctly-sized cached thumbnail so make one. //There was no correctly-sized cached thumbnail so make one.
BufferedImage thumbnail = null; BufferedImage thumbnail = null;
if (VideoUtils.isVideoThumbnailSupported(file)) { if (VideoUtils.isVideoThumbnailSupported(file)) {
if (openCVLoaded) { if (openCVLoaded) {
updateMessage(NbBundle.getMessage(this.getClass(), "ImageUtils.GetOrGenerateThumbnailTask.generatingPreviewFor", file.getName())); updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize); thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize);
} }
if (null == thumbnail) { if (null == thumbnail) {
if (defaultOnFailure) { if (defaultOnFailure) {
thumbnail = DEFAULT_THUMBNAIL; thumbnail = DEFAULT_THUMBNAIL;
} else { } else {
throw new IIOException("Failed to generate thumbnail for video file."); throw new IIOException("Failed to generate a thumbnail for " + getContentPathSafe(file));
} }
} }
} else { } else {
//read the image into a buffered image. //read the image into a buffered image.
//TODO: I don't like this, we just converted it from BufferedIamge to fx Image -jm
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(readImage(), null); BufferedImage bufferedImage = SwingFXUtils.fromFXImage(readImage(), null);
if (null == bufferedImage) { if (null == bufferedImage) {
LOGGER.log(Level.WARNING, FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION); String msg = MessageFormat.format(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION, getContentPathSafe(file));
throw new IIOException(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION); LOGGER.log(Level.WARNING, msg);
throw new IIOException(msg);
} }
updateProgress(-1, 1); updateProgress(-1, 1);
@ -716,23 +717,21 @@ public class ImageUtils {
thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize); thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize);
} catch (IllegalArgumentException | OutOfMemoryError e) { } catch (IllegalArgumentException | OutOfMemoryError e) {
// if resizing does not work due to extreme aspect ratio or oom, crop the image instead. // if resizing does not work due to extreme aspect ratio or oom, crop the image instead.
LOGGER.log(Level.WARNING, "Could not scale image {0}: " + e.toString() + ". Attemptying to crop {0} instead", ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS LOGGER.log(Level.WARNING, "Cropping {0}, because it could not be scaled: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
final int height = bufferedImage.getHeight(); final int height = bufferedImage.getHeight();
final int width = bufferedImage.getWidth(); final int width = bufferedImage.getWidth();
if (iconSize < height || iconSize < width) { if (iconSize < height || iconSize < width) {
final int cropHeight = Math.min(iconSize, height); final int cropHeight = Math.min(iconSize, height);
final int cropWidth = Math.min(iconSize, width); final int cropWidth = Math.min(iconSize, width);
try { try {
thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight); thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight);
} catch (Exception cropException) { } catch (Exception cropException) {
LOGGER.log(Level.WARNING, "Could not crop image {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS LOGGER.log(Level.WARNING, "Could not crop {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
throw cropException;
} }
} }
} catch (Exception e) { } catch (Exception e) {
LOGGER.log(Level.WARNING, "Could not scale image {0}: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS LOGGER.log(Level.WARNING, "Could not scale {0}: " + e.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
throw e; throw e;
} }
} }
@ -744,7 +743,7 @@ public class ImageUtils {
updateProgress(-1, 1); updateProgress(-1, 1);
//if we got a valid thumbnail save it //if we got a valid thumbnail save it
if ((cacheFile != null) && nonNull(thumbnail) && DEFAULT_THUMBNAIL != thumbnail) { if ((cacheFile != null) && thumbnail != null && DEFAULT_THUMBNAIL != thumbnail) {
saveThumbnail(thumbnail); saveThumbnail(thumbnail);
} }
@ -790,16 +789,16 @@ public class ImageUtils {
/** /**
* A task that reads the content of a AbstractFile as a javafx Image. * A task that reads the content of a AbstractFile as a javafx Image.
*/ */
@NbBundle.Messages({
"# {0} - file name",
"ReadImageTask.mesageText=Reading image: {0}"})
static private class ReadImageTask extends ReadImageTaskBase { static private class ReadImageTask extends ReadImageTaskBase {
ReadImageTask(AbstractFile file) { ReadImageTask(AbstractFile file) {
super(file); super(file);
updateMessage(NbBundle.getMessage(this.getClass(), "ImageUtils.ReadImageTask.mesage.text", file.getName())); updateMessage(Bundle.ReadImageTask_mesageText(file.getName()));
} }
// @NbBundle.Messages({
// "# {0} - file name",
// "LoadImageTask.mesageText=Reading image: {0}"})
@Override @Override
protected javafx.scene.image.Image call() throws Exception { protected javafx.scene.image.Image call() throws Exception {
return readImage(); return readImage();
@ -811,70 +810,55 @@ public class ImageUtils {
*/ */
static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener { static private abstract class ReadImageTaskBase extends Task<javafx.scene.image.Image> implements IIOReadProgressListener {
private static final String IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT = "ImageUtils could not read {0}. It may be unsupported or corrupt"; //NOI18N NON-NLS private static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT = "ImageIO could not read {0}. It may be unsupported or corrupt"; //NOI18N NON-NLS
final AbstractFile file; final AbstractFile file;
private ImageReader reader; // private ImageReader reader;
ReadImageTaskBase(AbstractFile file) { ReadImageTaskBase(AbstractFile file) {
this.file = file; this.file = file;
} }
protected javafx.scene.image.Image readImage() throws IOException { protected javafx.scene.image.Image readImage() throws IOException {
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) { if (ImageUtils.isGIF(file)) {
if (ImageUtils.isGIF(file)) { //use JavaFX to directly read GIF to preserve potential animation
//use JavaFX to directly read GIF to preserve potential animation, javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(new ReadContentInputStream(file)));
javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(inputStream)); if (image.isError() == false) {
if (image.isError() == false) { return image;
return image;
}
//fall through to default image reading code if there was an error
} }
if (isCancelled()) { //fall through to default image reading code if there was an error
return null; }
} if (isCancelled()) {
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) { return null;
if (input == null) { }
throw new IIOException(COULD_NOT_CREATE_IMAGE_INPUT_STREAM);
}
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
//we use the first ImageReader, is there any point to trying the others? return getImageProperty(file, "ImageIO could not read {0}: ",
if (readers.hasNext()) { imageReader -> {
reader = readers.next(); imageReader.addIIOReadProgressListener(ReadImageTaskBase.this);
reader.addIIOReadProgressListener(this);
reader.setInput(input);
/* /*
* This is the important part, get or create a * This is the important part, get or create a
* ImageReadParam, create a destination image to hold * ImageReadParam, create a destination image to hold
* the decoded result, then pass that image with the * the decoded result, then pass that image with the
* param. * param.
*/ */
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = imageReader.getDefaultReadParam();
BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0));
BufferedImage bufferedImage = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
param.setDestination(bufferedImage); param.setDestination(bufferedImage);
try { try {
bufferedImage = reader.read(0, param); //should always be same bufferedImage object bufferedImage = imageReader.read(0, param); //should always be same bufferedImage object
} catch (IOException iOException) { } catch (IOException iOException) {
// Ignore this exception or display a warning or similar, for exceptions happening during decoding LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N
LOGGER.log(Level.WARNING, IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N
} finally { } finally {
reader.removeIIOReadProgressListener(this); imageReader.removeIIOReadProgressListener(ReadImageTaskBase.this);
reader.dispose();
} }
if (isCancelled()) { if (isCancelled()) {
return null; return null;
} }
return SwingFXUtils.toFXImage(bufferedImage, null); return SwingFXUtils.toFXImage(bufferedImage, null);
} else { });
throw new IIOException(NO_IMAGE_READER_FOUND_FOR_ + ImageUtils.getContentPathSafe(file));
}
}
}
} }
@Override @Override
public void imageProgress(ImageReader source, float percentageDone) { public void imageProgress(ImageReader reader, float percentageDone) {
//update this task with the progress reported by ImageReader.read //update this task with the progress reported by ImageReader.read
updateProgress(percentageDone, 100); updateProgress(percentageDone, 100);
if (isCancelled()) { if (isCancelled()) {
@ -890,11 +874,11 @@ public class ImageUtils {
try { try {
javafx.scene.image.Image fxImage = get(); javafx.scene.image.Image fxImage = get();
if (fxImage == null) { if (fxImage == null) {
LOGGER.log(Level.WARNING, IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT, ImageUtils.getContentPathSafe(file)); LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT, ImageUtils.getContentPathSafe(file));
} else { } else {
if (fxImage.isError()) { if (fxImage.isError()) {
//if there was somekind of error, log it //if there was somekind of error, log it
LOGGER.log(Level.WARNING, IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file)); LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(fxImage.getException()), ImageUtils.getContentPathSafe(file));
} }
} }
} catch (InterruptedException | ExecutionException ex) { } catch (InterruptedException | ExecutionException ex) {
@ -905,7 +889,7 @@ public class ImageUtils {
@Override @Override
protected void failed() { protected void failed() {
super.failed(); super.failed();
LOGGER.log(Level.WARNING, IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(getException()), ImageUtils.getContentPathSafe(file)); LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + ObjectUtils.toString(getException()), ImageUtils.getContentPathSafe(file));
} }
@Override @Override
@ -950,7 +934,7 @@ public class ImageUtils {
* *
* @return * @return
*/ */
private static String getContentPathSafe(Content content) { static String getContentPathSafe(Content content) {
try { try {
return content.getUniquePath(); return content.getUniquePath();
} catch (TskCoreException tskCoreException) { } catch (TskCoreException tskCoreException) {

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2015 Basis Technology Corp. * Copyright 2015-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,6 +18,7 @@
*/ */
package org.sleuthkit.autopsy.coreutils; package org.sleuthkit.autopsy.coreutils;
import com.google.common.io.Files;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -91,33 +92,33 @@ public class VideoUtils {
return isMediaThumbnailSupported(file, SUPPORTED_VIDEO_MIME_TYPES, SUPPORTED_VIDEO_EXTENSIONS, CONDITIONAL_MIME_TYPES); return isMediaThumbnailSupported(file, SUPPORTED_VIDEO_MIME_TYPES, SUPPORTED_VIDEO_EXTENSIONS, CONDITIONAL_MIME_TYPES);
} }
@NbBundle.Messages({"# {0} - file name",
"VideoUtils.genVideoThumb.progress.text=extracting temporary file {0}"})
static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) { static BufferedImage generateVideoThumbnail(AbstractFile file, int iconSize) {
java.io.File tempFile = getTempVideoFile(file); java.io.File tempFile = getTempVideoFile(file);
if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
try { ProgressHandle progress = ProgressHandleFactory.createHandle(Bundle.VideoUtils_genVideoThumb_progress_text(file.getName()));
if (tempFile.exists() == false || tempFile.length() < file.getSize()) { progress.start(100);
com.google.common.io.Files.createParentDirs(tempFile); try {
ProgressHandle progress = ProgressHandleFactory.createHandle(NbBundle.getMessage(VideoUtils.class, "VideoUtils.genVideoThumb.progress.text", file.getName())); Files.createParentDirs(tempFile);
progress.start(100); ContentUtils.writeToFile(file, tempFile, progress, null, true);
try { } catch (IOException ex) {
ContentUtils.writeToFile(file, tempFile, progress, null, true); LOGGER.log(Level.WARNING, "Error extracting temporary file for " + ImageUtils.getContentPathSafe(file), ex); //NON-NLS
} catch (IOException ex) { } finally {
LOGGER.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
}
progress.finish(); progress.finish();
} }
} catch (IOException ex) {
return null;
} }
VideoCapture videoFile = new VideoCapture(); // will contain the video VideoCapture videoFile = new VideoCapture(); // will contain the video
if (!videoFile.open(tempFile.toString())) { if (!videoFile.open(tempFile.toString())) {
LOGGER.log(Level.WARNING, "Error opening {0} for preview generation.", ImageUtils.getContentPathSafe(file)); //NON-NLS
return null; return null;
} }
double fps = videoFile.get(CV_CAP_PROP_FPS); // gets frame per second double fps = videoFile.get(CV_CAP_PROP_FPS); // gets frame per second
double totalFrames = videoFile.get(CV_CAP_PROP_FRAME_COUNT); // gets total frames double totalFrames = videoFile.get(CV_CAP_PROP_FRAME_COUNT); // gets total frames
if (fps <= 0 || totalFrames <= 0) { if (fps <= 0 || totalFrames <= 0) {
LOGGER.log(Level.WARNING, "Error getting fps or total frames for {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS
return null; return null;
} }
double milliseconds = 1000 * (totalFrames / fps); //total milliseconds double milliseconds = 1000 * (totalFrames / fps); //total milliseconds
@ -132,10 +133,12 @@ public class VideoUtils {
for (int x = 0; x < THUMB_COLUMNS; x++) { for (int x = 0; x < THUMB_COLUMNS; x++) {
for (int y = 0; y < THUMB_ROWS; y++) { for (int y = 0; y < THUMB_ROWS; y++) {
if (!videoFile.set(CV_CAP_PROP_POS_MSEC, timestamp + x * framkeskip + y * framkeskip * THUMB_COLUMNS)) { if (!videoFile.set(CV_CAP_PROP_POS_MSEC, timestamp + x * framkeskip + y * framkeskip * THUMB_COLUMNS)) {
LOGGER.log(Level.WARNING, "Error seeking to " + timestamp + "ms in {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS
break; // if we can't set the time, return black for that frame break; // if we can't set the time, return black for that frame
} }
//read the frame into the image/matrix //read the frame into the image/matrix
if (!videoFile.read(imageMatrix)) { if (!videoFile.read(imageMatrix)) {
LOGGER.log(Level.WARNING, "Error reading frames at " + timestamp + "ms from {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS
break; //if the image for some reason is bad, return black for that frame break; //if the image for some reason is bad, return black for that frame
} }

View File

@ -40,7 +40,7 @@ public final class AutopsyEventPublisher {
* Composed of thread-safe objects. * Composed of thread-safe objects.
*/ */
private static final Logger logger = Logger.getLogger(AutopsyEventPublisher.class.getName()); private static final Logger logger = Logger.getLogger(AutopsyEventPublisher.class.getName());
private static final int MAX_REMOTE_EVENT_PUBLISH_TRIES = 3; private static final int MAX_REMOTE_EVENT_PUBLISH_TRIES = 1;
private final LocalEventPublisher localPublisher; private final LocalEventPublisher localPublisher;
private RemoteEventPublisher remotePublisher; private RemoteEventPublisher remotePublisher;
private String currentChannelName; private String currentChannelName;
@ -86,15 +86,8 @@ public final class AutopsyEventPublisher {
* events from other Autopsy nodes. * events from other Autopsy nodes.
*/ */
public void closeRemoteEventChannel() { public void closeRemoteEventChannel() {
stopRemotePublisher();
currentChannelName = null; currentChannelName = null;
if (null != remotePublisher) {
try {
remotePublisher.stop();
} catch (JMSException ex) {
logger.log(Level.SEVERE, "Error closing remote event channel", ex); //NON-NLS
}
remotePublisher = null;
}
} }
/** /**
@ -162,12 +155,10 @@ public final class AutopsyEventPublisher {
* @param event The event to publish. * @param event The event to publish.
*/ */
public void publishRemotely(AutopsyEvent event) { public void publishRemotely(AutopsyEvent event) {
/*
* This is a no-op if a remote channel has not been opened.
*/
if (null != currentChannelName) { if (null != currentChannelName) {
boolean published = false; boolean published = false;
int tryCount = 1; int tryCount = 1;
while (false == published && tryCount <= MAX_REMOTE_EVENT_PUBLISH_TRIES) { while (false == published && tryCount <= MAX_REMOTE_EVENT_PUBLISH_TRIES) {
try { try {
if (null == remotePublisher) { if (null == remotePublisher) {
@ -175,16 +166,28 @@ public final class AutopsyEventPublisher {
} }
remotePublisher.publish(event); remotePublisher.publish(event);
published = true; published = true;
} catch (JMSException ex) { } catch (AutopsyEventException | JMSException ex) {
logger.log(Level.SEVERE, String.format("Failed to publish %s using channel %s (tryCount = %s)", event.getPropertyName(), currentChannelName, tryCount), ex); //NON-NLS logger.log(Level.SEVERE, String.format("Failed to publish %s using channel %s (tryCount = %s)", event.getPropertyName(), currentChannelName, tryCount), ex); //NON-NLS
closeRemoteEventChannel(); stopRemotePublisher();
++tryCount;
} catch (AutopsyEventException ex) {
logger.log(Level.SEVERE, String.format("Failed to reopen channel %s to publish %s event (tryCount = %s)", currentChannelName, event.getPropertyName(), tryCount), ex); //NON-NLS
++tryCount; ++tryCount;
} }
} }
} }
}
/**
* Stops the remote event publisher, but does not reset the current channel
* name.
*/
private void stopRemotePublisher() {
if (null != remotePublisher) {
try {
remotePublisher.stop();
} catch (JMSException ex) {
logger.log(Level.SEVERE, String.format("Error closing remote event publisher for channel %s", currentChannelName), ex); //NON-NLS
}
remotePublisher = null;
}
} }
} }

View File

@ -115,5 +115,5 @@ IngestJob.cancelReason.notCancelled.text=Not cancelled
IngestJob.cancelReason.cancelledByUser.text=Cancelled by user IngestJob.cancelReason.cancelledByUser.text=Cancelled by user
IngestJob.cancelReason.ingestModStartFail.text=Ingest modules startup failed IngestJob.cancelReason.ingestModStartFail.text=Ingest modules startup failed
IngestJob.cancelReason.outOfDiskSpace.text=Out of disk space IngestJob.cancelReason.outOfDiskSpace.text=Out of disk space
IngestJob.cancelReason.servicesDown.text=Not cancelled IngestJob.cancelReason.servicesDown.text=Services Down
IngestJob.cancelReason.caseClosed.text=Case closed IngestJob.cancelReason.caseClosed.text=Case closed

View File

@ -1,116 +1,115 @@
CTL_IngestMessageTopComponent=\u30E1\u30C3\u30BB\u30FC\u30B8 CTL_IngestMessageTopComponent=\u30e1\u30c3\u30bb\u30fc\u30b8
HINT_IngestMessageTopComponent=\u30E1\u30C3\u30BB\u30FC\u30B8\u30A6\u30A3\u30F3\u30C9\u30A6 HINT_IngestMessageTopComponent=\u30e1\u30c3\u30bb\u30fc\u30b8\u30a6\u30a3\u30f3\u30c9\u30a6
IngestDialog.closeButton.title=\u9589\u3058\u308B IngestDialog.closeButton.title=\u9589\u3058\u308b
IngestDialog.startButton.title=\u958B\u59CB IngestDialog.startButton.title=\u958b\u59cb
IngestDialog.title.text=\u30E2\u30B8\u30E5\u30FC\u30EB\u3092\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8 IngestDialog.title.text=\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8
IngestJob.progress.cancelling={0}\uFF08\u30AD\u30E3\u30F3\u30BB\u30EB\u4E2D\u2026\uFF09 IngestJob.progress.cancelling={0}\uff08\u30ad\u30e3\u30f3\u30bb\u30eb\u4e2d\u2026\uff09
IngestJob.progress.dataSourceIngest.displayName={1}\u306E{0} IngestJob.progress.dataSourceIngest.displayName={1}\u306e{0}
IngestJob.progress.fileIngest.displayName={0}\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u89E3\u6790\u4E2D IngestJob.progress.fileIngest.displayName={0}\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u89e3\u6790\u4e2d
IngestManager.moduleErr=\u30E2\u30B8\u30E5\u30FC\u30EB\u30A8\u30E9\u30FC IngestManager.moduleErr=\u30e2\u30b8\u30e5\u30fc\u30eb\u30a8\u30e9\u30fc
IngestManager.moduleErr.errListenToUpdates.msg=Ingest Manager\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8\u3092\u78BA\u8A8D\u4E2D\u306B\u30E2\u30B8\u30E5\u30FC\u30EB\u304C\u30A8\u30E9\u30FC\u3092\u8D77\u3053\u3057\u307E\u3057\u305F\u3002\u3069\u306E\u30E2\u30B8\u30E5\u30FC\u30EB\u304B\u30ED\u30B0\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\u3002\u4E00\u90E8\u306E\u30C7\u30FC\u30BF\u304C\u4E0D\u5B8C\u5168\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002 IngestManager.moduleErr.errListenToUpdates.msg=Ingest Manager\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u3092\u78ba\u8a8d\u4e2d\u306b\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u30a8\u30e9\u30fc\u3092\u8d77\u3053\u3057\u307e\u3057\u305f\u3002\u3069\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u304b\u30ed\u30b0\u3092\u78ba\u8a8d\u3057\u3066\u4e0b\u3055\u3044\u3002\u4e00\u90e8\u306e\u30c7\u30fc\u30bf\u304c\u4e0d\u5b8c\u5168\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
IngestManager.StartIngestJobsTask.run.cancelling={0}\uFF08\u30AD\u30E3\u30F3\u30BB\u30EB\u4E2D\u2026\uFF09 IngestManager.StartIngestJobsTask.run.cancelling={0}\uff08\u30ad\u30e3\u30f3\u30bb\u30eb\u4e2d\u2026\uff09
IngestManager.StartIngestJobsTask.run.displayName=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u958B\u59CB\u4E2D IngestManager.StartIngestJobsTask.run.displayName=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u958b\u59cb\u4e2d
IngestMessage.exception.srcSubjDetailsDataNotNull.msg=\u30BD\u30FC\u30B9\u3001\u30B5\u30D6\u30B8\u30A7\u30AF\u30C8\u3001\u8A73\u7D30\u304A\u3088\u3073\u30C7\u30FC\u30BF\u306F\u30CC\u30EB\u3067\u306F\u3044\u3051\u307E\u305B\u3093 IngestMessage.exception.srcSubjDetailsDataNotNull.msg=\u30bd\u30fc\u30b9\u3001\u30b5\u30d6\u30b8\u30a7\u30af\u30c8\u3001\u8a73\u7d30\u304a\u3088\u3073\u30c7\u30fc\u30bf\u306f\u30cc\u30eb\u3067\u306f\u3044\u3051\u307e\u305b\u3093
IngestMessage.exception.srcSubjNotNull.msg=\u30BD\u30FC\u30B9\u304A\u3088\u3073\u30B5\u30D6\u30B8\u30A7\u30AF\u30C8\u306F\u30CC\u30EB\u3067\u306F\u3044\u3051\u307E\u305B\u3093 IngestMessage.exception.srcSubjNotNull.msg=\u30bd\u30fc\u30b9\u304a\u3088\u3073\u30b5\u30d6\u30b8\u30a7\u30af\u30c8\u306f\u30cc\u30eb\u3067\u306f\u3044\u3051\u307e\u305b\u3093
IngestMessage.exception.typeSrcSubjNotNull.msg=\u30E1\u30C3\u30BB\u30FC\u30B8\u30BF\u30A4\u30D7\u3001\u30BD\u30FC\u30B9\u304A\u3088\u3073\u30B5\u30D6\u30B8\u30A7\u30AF\u30C8\u306F\u30CC\u30EB\u3067\u306F\u3044\u3051\u307E\u305B\u3093 IngestMessage.exception.typeSrcSubjNotNull.msg=\u30e1\u30c3\u30bb\u30fc\u30b8\u30bf\u30a4\u30d7\u3001\u30bd\u30fc\u30b9\u304a\u3088\u3073\u30b5\u30d6\u30b8\u30a7\u30af\u30c8\u306f\u30cc\u30eb\u3067\u306f\u3044\u3051\u307e\u305b\u3093
IngestMessage.toString.data.text=\ \u30C7\u30FC\u30BF\uFF1A{0} IngestMessage.toString.data.text=\ \u30c7\u30fc\u30bf\uff1a{0}
IngestMessage.toString.date.text=\ \u65E5\u4ED8\uFF1A{0} IngestMessage.toString.date.text=\ \u65e5\u4ed8\uff1a{0}
IngestMessage.toString.details.text=\ \u8A73\u7D30\uFF1A{0} IngestMessage.toString.details.text=\ \u8a73\u7d30\uff1a{0}
IngestMessage.toString.subject.text=\ \u30B5\u30D6\u30B8\u30A7\u30AF\u30C8\uFF1A{0} IngestMessage.toString.subject.text=\ \u30b5\u30d6\u30b8\u30a7\u30af\u30c8\uff1a{0}
IngestMessage.toString.type.text=\u30BF\u30A4\u30D7\uFF1A{0} IngestMessage.toString.type.text=\u30bf\u30a4\u30d7\uff1a{0}
IngestMessageDetailsPanel.copyMenuItem.text=\u30B3\u30D4\u30FC IngestMessageDetailsPanel.copyMenuItem.text=\u30b3\u30d4\u30fc
IngestMessageDetailsPanel.messageDetailsPane.contentType=\u30C6\u30AD\u30B9\u30C8\uFF0Fhtml IngestMessageDetailsPanel.messageDetailsPane.contentType=\u30c6\u30ad\u30b9\u30c8\uff0fhtml
IngestMessageDetailsPanel.selectAllMenuItem.text=\u3059\u3079\u3066\u9078\u629E IngestMessageDetailsPanel.selectAllMenuItem.text=\u3059\u3079\u3066\u9078\u629e
IngestMessageDetailsPanel.viewArtifactButton.text=\u7D50\u679C\u3078\u79FB\u52D5 IngestMessageDetailsPanel.viewArtifactButton.text=\u7d50\u679c\u3078\u79fb\u52d5
IngestMessageDetailsPanel.viewContentButton.text=\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3078\u79FB\u52D5 IngestMessageDetailsPanel.viewContentButton.text=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3078\u79fb\u52d5
IngestMessagePanel.BooleanRenderer.exception.nonBoolVal.msg=\u30D6\u30FC\u30EB\u5024\u3067\u306F\u306A\u3044\u3082\u306E\u306BBooleanRenderer\u3092\u4F7F\u7528\u3057\u3088\u3046\u3068\u3057\u307E\u3057\u305F\u3002 IngestMessagePanel.BooleanRenderer.exception.nonBoolVal.msg=\u30d6\u30fc\u30eb\u5024\u3067\u306f\u306a\u3044\u3082\u306e\u306bBooleanRenderer\u3092\u4f7f\u7528\u3057\u3088\u3046\u3068\u3057\u307e\u3057\u305f\u3002
IngestMessagePanel.DateRenderer.exception.nonDateVal.text=\u65E5\u4ED8\u3067\u306F\u306A\u3044\u3082\u306E\u306BDateRenderer\u3092\u4F7F\u7528\u3057\u3088\u3046\u3068\u3057\u307E\u3057\u305F\u3002 IngestMessagePanel.DateRenderer.exception.nonDateVal.text=\u65e5\u4ed8\u3067\u306f\u306a\u3044\u3082\u306e\u306bDateRenderer\u3092\u4f7f\u7528\u3057\u3088\u3046\u3068\u3057\u307e\u3057\u305f\u3002
IngestMessagePanel.moduleErr=\u30E2\u30B8\u30E5\u30FC\u30EB\u30A8\u30E9\u30FC IngestMessagePanel.moduleErr=\u30e2\u30b8\u30e5\u30fc\u30eb\u30a8\u30e9\u30fc
IngestMessagePanel.moduleErr.errListenUpdates.text=IngestMessagePanel\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8\u3092\u78BA\u8A8D\u4E2D\u306B\u30E2\u30B8\u30E5\u30FC\u30EB\u304C\u30A8\u30E9\u30FC\u3092\u8D77\u3053\u3057\u307E\u3057\u305F\u3002\u3069\u306E\u30E2\u30B8\u30E5\u30FC\u30EB\u304B\u30ED\u30B0\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\u3002\u4E00\u90E8\u306E\u30C7\u30FC\u30BF\u304C\u4E0D\u5B8C\u5168\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002 IngestMessagePanel.moduleErr.errListenUpdates.text=IngestMessagePanel\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u3092\u78ba\u8a8d\u4e2d\u306b\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u30a8\u30e9\u30fc\u3092\u8d77\u3053\u3057\u307e\u3057\u305f\u3002\u3069\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u304b\u30ed\u30b0\u3092\u78ba\u8a8d\u3057\u3066\u4e0b\u3055\u3044\u3002\u4e00\u90e8\u306e\u30c7\u30fc\u30bf\u304c\u4e0d\u5b8c\u5168\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
IngestMessagePanel.MsgTableMod.colNames.module=\u30E2\u30B8\u30E5\u30FC\u30EB IngestMessagePanel.MsgTableMod.colNames.module=\u30e2\u30b8\u30e5\u30fc\u30eb
IngestMessagePanel.MsgTableMod.colNames.new=\u65B0\u898F\uFF1F IngestMessagePanel.MsgTableMod.colNames.new=\u65b0\u898f\uff1f
IngestMessagePanel.MsgTableMod.colNames.num=\u756A\u53F7 IngestMessagePanel.MsgTableMod.colNames.num=\u756a\u53f7
IngestMessagePanel.MsgTableMod.colNames.subject=\u30B5\u30D6\u30B8\u30A7\u30AF\u30C8 IngestMessagePanel.MsgTableMod.colNames.subject=\u30b5\u30d6\u30b8\u30a7\u30af\u30c8
IngestMessagePanel.MsgTableMod.colNames.timestamp=\u30BF\u30A4\u30E0\u30B9\u30BF\u30F3\u30D7 IngestMessagePanel.MsgTableMod.colNames.timestamp=\u30bf\u30a4\u30e0\u30b9\u30bf\u30f3\u30d7
IngestMessagePanel.sortByComboBox.model.priority=\u512A\u5148\u5EA6 IngestMessagePanel.sortByComboBox.model.priority=\u512a\u5148\u5ea6
IngestMessagePanel.sortByComboBox.model.time=\u6642\u9593 IngestMessagePanel.sortByComboBox.model.time=\u6642\u9593
IngestMessagePanel.sortByComboBox.toolTipText=\u6642\u9593\u9806\uFF08\u6642\u7CFB\u5217\uFF09\u307E\u305F\u306F\u30E1\u30C3\u30BB\u30FC\u30B8\u306E\u512A\u5148\u5EA6\u3067\u30BD\u30FC\u30C8 IngestMessagePanel.sortByComboBox.toolTipText=\u6642\u9593\u9806\uff08\u6642\u7cfb\u5217\uff09\u307e\u305f\u306f\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u512a\u5148\u5ea6\u3067\u30bd\u30fc\u30c8
IngestMessagePanel.sortByLabel.text=\u6B21\u3067\u30BD\u30FC\u30C8\uFF1A IngestMessagePanel.sortByLabel.text=\u6b21\u3067\u30bd\u30fc\u30c8\uff1a
IngestMessagePanel.totalMessagesNameLabel.text=\u5408\u8A08\uFF1A IngestMessagePanel.totalMessagesNameLabel.text=\u5408\u8a08\uff1a
IngestMessagePanel.totalMessagesNameVal.text=- IngestMessagePanel.totalMessagesNameVal.text=-
IngestMessagePanel.totalUniqueMessagesNameLabel.text=\u30E6\u30CB\u30FC\u30AF\uFF1A IngestMessagePanel.totalUniqueMessagesNameLabel.text=\u30e6\u30cb\u30fc\u30af\uff1a
IngestMessagePanel.totalUniqueMessagesNameVal.text=- IngestMessagePanel.totalUniqueMessagesNameVal.text=-
IngestMessagesToolbar.customizeButton.toolTipText=\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8 IngestMessagesToolbar.customizeButton.toolTipText=\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8
IngestMessageTopComponent.displayName=\u30A4\u30F3\u30DC\u30C3\u30AF\u30B9\u3092\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8 IngestMessageTopComponent.displayName=\u30a4\u30f3\u30dc\u30c3\u30af\u30b9\u3092\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8
IngestMessageTopComponent.displayReport.option.GenRpt=\u30EC\u30DD\u30FC\u30C8\u3092\u751F\u6210 IngestMessageTopComponent.displayReport.option.GenRpt=\u30ec\u30dd\u30fc\u30c8\u3092\u751f\u6210
IngestMessageTopComponent.displayReport.option.OK=OK IngestMessageTopComponent.displayReport.option.OK=OK
IngestMessageTopComponent.initComponents.name=\u30A4\u30F3\u30DC\u30C3\u30AF\u30B9\u3092\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8 IngestMessageTopComponent.initComponents.name=\u30a4\u30f3\u30dc\u30c3\u30af\u30b9\u3092\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8
IngestMessageTopComponent.msgDlg.ingestRpt.text=\u30EC\u30DD\u30FC\u30C8\u3092\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8 IngestMessageTopComponent.msgDlg.ingestRpt.text=\u30ec\u30dd\u30fc\u30c8\u3092\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8
IngestMonitor.mgrErrMsg.lowDiskSpace.msg=\u30C7\u30A3\u30B9\u30AF{0}\u306E\u30C7\u30A3\u30B9\u30AF\u9818\u57DF\u4E0D\u8DB3\u306E\u305F\u3081\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u3092\u4E2D\u6B62\u3057\u307E\u3059\u3002\n\u30B1\u30FC\u30B9\u30C9\u30E9\u30A4\u30D6\u306B\u6700\u4F4E1GB\u306E\u7A7A\u304D\u9818\u57DF\u304C\u3042\u308B\u306E\u3092\u78BA\u8A8D\u3057\u3001\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u3092\u518D\u5B9F\u884C\u3057\u3066\u4E0B\u3055\u3044\u3002 IngestMonitor.mgrErrMsg.lowDiskSpace.msg=\u30c7\u30a3\u30b9\u30af{0}\u306e\u30c7\u30a3\u30b9\u30af\u9818\u57df\u4e0d\u8db3\u306e\u305f\u3081\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3092\u4e2d\u6b62\u3057\u307e\u3059\u3002\n\u30b1\u30fc\u30b9\u30c9\u30e9\u30a4\u30d6\u306b\u6700\u4f4e1GB\u306e\u7a7a\u304d\u9818\u57df\u304c\u3042\u308b\u306e\u3092\u78ba\u8a8d\u3057\u3001\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3092\u518d\u5b9f\u884c\u3057\u3066\u4e0b\u3055\u3044\u3002
IngestMonitor.mgrErrMsg.lowDiskSpace.title=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u304C\u4E2D\u6B62\u3055\u308C\u307E\u3057\u305F\u30FC{0}\u306E\u30C7\u30A3\u30B9\u30AF\u9818\u57DF\u4E0D\u8DB3 IngestMonitor.mgrErrMsg.lowDiskSpace.title=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u4e2d\u6b62\u3055\u308c\u307e\u3057\u305f\u30fc{0}\u306e\u30c7\u30a3\u30b9\u30af\u9818\u57df\u4e0d\u8db3
OpenIDE-Module-Name=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8 OpenIDE-Module-Name=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8
IngestManager.StartIngestJobsTask.run.startupErr.dlgErrorList=\n\u30A8\u30E9\u30FC\uFF1A\n{0} IngestManager.StartIngestJobsTask.run.startupErr.dlgErrorList=\n\u30a8\u30e9\u30fc\uff1a\n{0}
IngestManager.StartIngestJobsTask.run.startupErr.dlgMsg=\u5358\u6570\u307E\u305F\u306F\u8907\u6570\u306E\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB\u3092\u30B9\u30BF\u30FC\u30C8\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30B8\u30E7\u30D6\u306F\u30AD\u30E3\u30F3\u30BB\u30EB\u3055\u308C\u307E\u3057\u305F\u3002 IngestManager.StartIngestJobsTask.run.startupErr.dlgMsg=\u5358\u6570\u307e\u305f\u306f\u8907\u6570\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u30b9\u30bf\u30fc\u30c8\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30b8\u30e7\u30d6\u306f\u30ad\u30e3\u30f3\u30bb\u30eb\u3055\u308c\u307e\u3057\u305f\u3002
IngestManager.StartIngestJobsTask.run.startupErr.dlgSolution=\u5931\u6557\u3057\u305F\u30E2\u30B8\u30E5\u30FC\u30EB\u3092\u7121\u52B9\u5316\u3059\u308B\u304B\u30A8\u30E9\u30FC\u3092\u89E3\u6C7A\u3057\u3001\u305D\u306E\u5F8C\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u3092\u53F3\u30AF\u30EA\u30C3\u30AF\u3057\u3001\n\u300C\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB\u5B9F\u884C\u300D\u3092\u9078\u629E\u3057\u3066\u3001\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u3092\u518D\u5B9F\u884C\u3057\u3066\u4E0B\u3055\u3044\u3002 IngestManager.StartIngestJobsTask.run.startupErr.dlgSolution=\u5931\u6557\u3057\u305f\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u7121\u52b9\u5316\u3059\u308b\u304b\u30a8\u30e9\u30fc\u3092\u89e3\u6c7a\u3057\u3001\u305d\u306e\u5f8c\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u3092\u53f3\u30af\u30ea\u30c3\u30af\u3057\u3001\n\u300c\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u5b9f\u884c\u300d\u3092\u9078\u629e\u3057\u3066\u3001\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3092\u518d\u5b9f\u884c\u3057\u3066\u4e0b\u3055\u3044\u3002
IngestManager.StartIngestJobsTask.run.startupErr.dlgTitle=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u5931\u6557 IngestManager.StartIngestJobsTask.run.startupErr.dlgTitle=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u5931\u6557
IngestJobSettings.createModuleSettingsFolder.warning=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB\u8A2D\u5B9A\u30D5\u30A9\u30EB\u30C0\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u8A2D\u5B9A\u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3002 IngestJobSettings.createModuleSettingsFolder.warning=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u8a2d\u5b9a\u30d5\u30a9\u30eb\u30c0\u306e\u4f5c\u6210\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3067\u304d\u307e\u305b\u3093\u3002
IngestJob.progress.dataSourceIngest.initialDisplayName={0}\u3092\u89E3\u6790\u4E2D IngestJob.progress.dataSourceIngest.initialDisplayName={0}\u3092\u89e3\u6790\u4e2d
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.dataSource=\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9 IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.dataSource=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.elapsedTime=\u7D4C\u904E\u6642\u9593\uFF08\u6642\uFF1A\u5206\uFF1A\u79D2\uFF09 IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.elapsedTime=\u7d4c\u904e\u6642\u9593\uff08\u6642\uff1a\u5206\uff1a\u79d2\uff09
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.file=\u30D5\u30A1\u30A4\u30EB IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.file=\u30d5\u30a1\u30a4\u30eb
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.startTime=\u958B\u59CB\u6642\u9593 IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.startTime=\u958b\u59cb\u6642\u9593
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.threadID=\u30B9\u30EC\u30C3\u30C9ID IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.threadID=\u30b9\u30ec\u30c3\u30c9ID
IngestManager.IngestMessage.ErrorMessageLimitReached.msg=\u6700\u5927\u6570({0})\u306E\u30A8\u30E9\u30FC\u304A\u3088\u3073\u307E\u305F\u306F\u8B66\u544A\u30E1\u30C3\u30BB\u30FC\u30B8\u304C\u63B2\u8F09\u3055\u308C\u307E\u3057\u305F\u3002\u3055\u3089\u306A\u308B\u30A8\u30E9\u30FC\uFF0F\u8B66\u544A\u306F\u30ED\u30B0\u3092\u78BA\u8A8D\u3057\u3066\u4E0B\u3055\u3044\uFF08\u30D8\u30EB\u30D7->\u30ED\u30B0\u30D5\u30A9\u30EB\u30C0\u30FC\u3092\u958B\u304F\uFF09 IngestManager.IngestMessage.ErrorMessageLimitReached.msg=\u6700\u5927\u6570({0})\u306e\u30a8\u30e9\u30fc\u304a\u3088\u3073\u307e\u305f\u306f\u8b66\u544a\u30e1\u30c3\u30bb\u30fc\u30b8\u304c\u63b2\u8f09\u3055\u308c\u307e\u3057\u305f\u3002\u3055\u3089\u306a\u308b\u30a8\u30e9\u30fc\uff0f\u8b66\u544a\u306f\u30ed\u30b0\u3092\u78ba\u8a8d\u3057\u3066\u4e0b\u3055\u3044\uff08\u30d8\u30eb\u30d7->\u30ed\u30b0\u30d5\u30a9\u30eb\u30c0\u30fc\u3092\u958b\u304f\uff09
IngestManager.IngestMessage.ErrorMessageLimitReached.subject=\u6700\u5927\u6570\u306E\u30A8\u30E9\u30FC\u304C\u63B2\u8F09\u3055\u308C\u307E\u3057\u305F IngestManager.IngestMessage.ErrorMessageLimitReached.subject=\u6700\u5927\u6570\u306e\u30a8\u30e9\u30fc\u304c\u63b2\u8f09\u3055\u308c\u307e\u3057\u305f
IngestManager.IngestMessage.ErrorMessageLimitReached.title=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30DE\u30CD\u30B8\u30E3\u30FC IngestManager.IngestMessage.ErrorMessageLimitReached.title=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30de\u30cd\u30b8\u30e3\u30fc
IngestManager.IngestThreadActivitySnapshot.idleThread=\u30A2\u30A4\u30C9\u30EB IngestManager.IngestThreadActivitySnapshot.idleThread=\u30a2\u30a4\u30c9\u30eb
IngestProgressSnapshotDialog.title.text=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30D7\u30ED\u30B0\u30EC\u30B9\u30FB\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8 IngestProgressSnapshotDialog.title.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30d7\u30ed\u30b0\u30ec\u30b9\u30fb\u30b9\u30ca\u30c3\u30d7\u30b7\u30e7\u30c3\u30c8
IngestProgressSnapshotPanel.closeButton.text=\u9589\u3058\u308B IngestProgressSnapshotPanel.closeButton.text=\u9589\u3058\u308b
IngestProgressSnapshotPanel.refreshButton.text=\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5 IngestProgressSnapshotPanel.refreshButton.text=\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.activity=\u30A2\u30AF\u30C6\u30A3\u30D3\u30C6\u30A3 IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.activity=\u30a2\u30af\u30c6\u30a3\u30d3\u30c6\u30a3
IngestJobSettingsPanel.advancedButton.actionCommand=\u30A2\u30C9\u30D0\u30F3\u30B9 IngestJobSettingsPanel.advancedButton.actionCommand=\u30a2\u30c9\u30d0\u30f3\u30b9
IngestJobSettingsPanel.advancedButton.text=\u30A2\u30C9\u30D0\u30F3\u30B9 IngestJobSettingsPanel.advancedButton.text=\u30a2\u30c9\u30d0\u30f3\u30b9
ModuleTableModel.colName.duration=\u6240\u8981\u6642\u9593 ModuleTableModel.colName.duration=\u6240\u8981\u6642\u9593
IngestJob.cancellationDialog.title=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u3092\u30AD\u30E3\u30F3\u30BB\u30EB IngestJob.cancellationDialog.title=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3092\u30ad\u30e3\u30f3\u30bb\u30eb
IngestJobSettings.missingModule.warning=\u4EE5\u524D\u306B\u8AAD\u307F\u8FBC\u3093\u3060{0}\u30E2\u30B8\u30E5\u30FC\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002 IngestJobSettings.missingModule.warning=\u4ee5\u524d\u306b\u8aad\u307f\u8fbc\u3093\u3060{0}\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002
DataSourceIngestCancellationPanel.cancelAllModulesRadioButton.text=\u5168\u3066\u306E\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB\u3092\u30AD\u30E3\u30F3\u30BB\u30EB DataSourceIngestCancellationPanel.cancelAllModulesRadioButton.text=\u5168\u3066\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u30ad\u30e3\u30f3\u30bb\u30eb
DataSourceIngestCancellationPanel.cancelCurrentModuleRadioButton.text=\u73FE\u5728\u306E\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u307F\u30AD\u30E3\u30F3\u30BB\u30EB DataSourceIngestCancellationPanel.cancelCurrentModuleRadioButton.text=\u73fe\u5728\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u306e\u307f\u30ad\u30e3\u30f3\u30bb\u30eb
FileIngestCancellationPanel.cancelFileIngestRadioButton.text=\u30D5\u30A1\u30A4\u30EB\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u306E\u307F\u30AD\u30E3\u30F3\u30BB\u30EB FileIngestCancellationPanel.cancelFileIngestRadioButton.text=\u30d5\u30a1\u30a4\u30eb\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u306e\u307f\u30ad\u30e3\u30f3\u30bb\u30eb
FileIngestCancellationPanel.cancelIngestJobRadioButton.text=\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u304A\u3088\u3073\u30D5\u30A1\u30A4\u30EB\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u3092\u30AD\u30E3\u30F3\u30BB\u30EB FileIngestCancellationPanel.cancelIngestJobRadioButton.text=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304a\u3088\u3073\u30d5\u30a1\u30a4\u30eb\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3092\u30ad\u30e3\u30f3\u30bb\u30eb
IngestJobSettings.moduleSettingsLoad.warning={1}\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u306E{0}\u30E2\u30B8\u30E5\u30FC\u30EB\u7528\u306E\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30B8\u30E7\u30D6\u8A2D\u5B9A\u3092\u8AAD\u307F\u8FBC\u307F\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u30C7\u30D5\u30A9\u30EB\u30C8\u8A2D\u5B9A\u3092\u4F7F\u7528\u3057\u3066\u3044\u307E\u3059\u3002 IngestJobSettings.moduleSettingsLoad.warning={1}\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e{0}\u30e2\u30b8\u30e5\u30fc\u30eb\u7528\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30b8\u30e7\u30d6\u8a2d\u5b9a\u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u30c7\u30d5\u30a9\u30eb\u30c8\u8a2d\u5b9a\u3092\u4f7f\u7528\u3057\u3066\u3044\u307e\u3059\u3002
IngestJobSettings.save.warning={0}\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30B8\u30E7\u30D6\u8A2D\u5B9A\u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002 IngestJobSettings.save.warning={0}\u30e2\u30b8\u30e5\u30fc\u30eb\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30b8\u30e7\u30d6\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
IngestJobTableModel.colName.dataSource=\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9 IngestJobTableModel.colName.dataSource=\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9
IngestJobTableModel.colName.dirQueued=\u30AD\u30E5\u30FC\u3055\u308C\u305F\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA IngestJobTableModel.colName.dirQueued=\u30ad\u30e5\u30fc\u3055\u308c\u305f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea
IngestJobTableModel.colName.filesPerSec=\u30D5\u30A1\u30A4\u30EB\uFF0F\u79D2 IngestJobTableModel.colName.filesPerSec=\u30d5\u30a1\u30a4\u30eb\uff0f\u79d2
IngestJobTableModel.colName.filesQueued=\u30AD\u30E5\u30FC\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB IngestJobTableModel.colName.filesQueued=\u30ad\u30e5\u30fc\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb
IngestJobTableModel.colName.inProgress=\u51E6\u7406\u4E2D IngestJobTableModel.colName.inProgress=\u51e6\u7406\u4e2d
IngestJobTableModel.colName.jobID=\u30B8\u30E7\u30D6ID IngestJobTableModel.colName.jobID=\u30b8\u30e7\u30d6ID
IngestJobTableModel.colName.numProcessed=\u51E6\u7406\u3055\u308C\u305F\u6570 IngestJobTableModel.colName.numProcessed=\u51e6\u7406\u3055\u308c\u305f\u6570
IngestJobTableModel.colName.rootQueued=\u30AD\u30E5\u30FC\u3055\u308C\u305F\u30EB\u30FC\u30C8 IngestJobTableModel.colName.rootQueued=\u30ad\u30e5\u30fc\u3055\u308c\u305f\u30eb\u30fc\u30c8
IngestJobTableModel.colName.start=\u30B9\u30BF\u30FC\u30C8 IngestJobTableModel.colName.start=\u30b9\u30bf\u30fc\u30c8
IngestModuleFactoryLoader.errorMessages.duplicateDisplayName=\u5225\u306E\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u540D\u524D\u3092\u91CD\u8907\u3059\u308B\u3001{0}\u306E\u540D\u524D\u3092\u6301\u3064\u30E2\u30B8\u30E5\u30FC\u30EB\u304C\u898B\u3064\u304B\u308A\u307E\u3057\u305F\u3002\u30E2\u30B8\u30E5\u30FC\u30EB\u306F\u4F7F\u7528\u3057\u307E\u305B\u3093\u3002 IngestModuleFactoryLoader.errorMessages.duplicateDisplayName=\u5225\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u306e\u540d\u524d\u3092\u91cd\u8907\u3059\u308b\u3001{0}\u306e\u540d\u524d\u3092\u6301\u3064\u30e2\u30b8\u30e5\u30fc\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f\u3002\u30e2\u30b8\u30e5\u30fc\u30eb\u306f\u4f7f\u7528\u3057\u307e\u305b\u3093\u3002
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.jobID=\u30B8\u30E7\u30D6ID IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.jobID=\u30b8\u30e7\u30d6ID
ModuleTableModel.colName.module=\u30E2\u30B8\u30E5\u30FC\u30EB ModuleTableModel.colName.module=\u30e2\u30b8\u30e5\u30fc\u30eb
IngestJobSettingsPanel.processUnallocCheckbox.toolTipText=\u524A\u9664\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB\u7B49\u306E\u672A\u5272\u308A\u5F53\u3066\u9818\u57DF\u3092\u51E6\u7406\u3002\u3088\u308A\u5B8C\u5168\u306A\u7D50\u679C\u304C\u51FA\u307E\u3059\u304C\u3001\u5927\u304D\u3044\u30A4\u30E1\u30FC\u30B8\u3067\u306F\u51E6\u7406\u6642\u9593\u304C\u9577\u304F\u306A\u308B\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002 IngestJobSettingsPanel.processUnallocCheckbox.toolTipText=\u524a\u9664\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb\u7b49\u306e\u672a\u5272\u308a\u5f53\u3066\u9818\u57df\u3092\u51e6\u7406\u3002\u3088\u308a\u5b8c\u5168\u306a\u7d50\u679c\u304c\u51fa\u307e\u3059\u304c\u3001\u5927\u304d\u3044\u30a4\u30e1\u30fc\u30b8\u3067\u306f\u51e6\u7406\u6642\u9593\u304c\u9577\u304f\u306a\u308b\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
IngestJobSettingsPanel.processUnallocCheckbox.text=\u672A\u5272\u308A\u5F53\u3066\u9818\u57DF\u306E\u51E6\u7406 IngestJobSettingsPanel.processUnallocCheckbox.text=\u672a\u5272\u308a\u5f53\u3066\u9818\u57df\u306e\u51e6\u7406
Menu/Tools/RunIngestModules=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB\u3092\u5B9F\u884C Menu/Tools/RunIngestModules=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u5b9f\u884c
IngestJob.progress.fileIngest.cancelMessage={1}\u306E{0}\u3092\u5F85\u3063\u3066\u3044\u307E\u3059 IngestJob.progress.fileIngest.cancelMessage={1}\u306e{0}\u3092\u5f85\u3063\u3066\u3044\u307e\u3059
IngestManager.OpenEventChannel.Fail.ErrMsg=\u3053\u306E\u30B1\u30FC\u30B9\u3067\u4F7F\u308F\u308C\u3066\u3044\u308B\u304B\u3082\u3057\u308C\u306A\u3044\u4ED6\u306E\u30CE\u30FC\u30C9\u306B\u89E3\u6790\u30D7\u30ED\u30BB\u30B9\u304C\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002 IngestManager.OpenEventChannel.Fail.ErrMsg=\u3053\u306e\u30b1\u30fc\u30b9\u3067\u4f7f\u308f\u308c\u3066\u3044\u308b\u304b\u3082\u3057\u308c\u306a\u3044\u4ed6\u306e\u30ce\u30fc\u30c9\u306b\u89e3\u6790\u30d7\u30ed\u30bb\u30b9\u304c\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
IngestManager.OpenEventChannel.Fail.Title=\u63A5\u7D9A\u5931\u6557 IngestManager.OpenEventChannel.Fail.Title=\u63a5\u7d9a\u5931\u6557
IngestJobSettings.moduleSettingsSave.warning={1}\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u306E{0}\u30E2\u30B8\u30E5\u30FC\u30EB\u7528\u306E\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30B8\u30E7\u30D6\u8A2D\u5B9A\u3092\u8AAD\u307F\u8FBC\u307F\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002 IngestJobSettings.moduleSettingsSave.warning={1}\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u306e{0}\u30e2\u30b8\u30e5\u30fc\u30eb\u7528\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30b8\u30e7\u30d6\u8a2d\u5b9a\u3092\u8aad\u307f\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002
IngestJobTableModel.colName.dsQueued=\u30AD\u30E5\u30FC\u3055\u308C\u305FDS IngestJobTableModel.colName.dsQueued=\u30ad\u30e5\u30fc\u3055\u308c\u305fDS
IngestJobSettingsPanel.jButtonSelectAll.text=\u5168\u3066\u9078\u629E IngestJobSettingsPanel.jButtonSelectAll.text=\u5168\u3066\u9078\u629e
IngestJobSettingsPanel.jButtonDeselectAll.text=\u5168\u3066\u9078\u629E\u89E3\u9664 IngestJobSettingsPanel.jButtonDeselectAll.text=\u5168\u3066\u9078\u629e\u89e3\u9664
IngestManager.cancellingIngest.msgDlg.text=\u73FE\u5728\u5B9F\u884C\u4E2D\u306E\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30B8\u30E7\u30D6\u3092\u30AD\u30E3\u30F3\u30BB\u30EB\u4E2D IngestManager.cancellingIngest.msgDlg.text=\u73fe\u5728\u5b9f\u884c\u4e2d\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30b8\u30e7\u30d6\u3092\u30ad\u30e3\u30f3\u30bb\u30eb\u4e2d
IngestManager.serviceIsDown.msgDlg.text={0}\u304C\u30C0\u30A6\u30F3\u3057\u3066\u3044\u307E\u3059 IngestManager.serviceIsDown.msgDlg.text={0}\u304c\u30c0\u30a6\u30f3\u3057\u3066\u3044\u307e\u3059
RunIngestSubMenu.menuItem.empty=\u30FC\u7A7A\u767D\u30FC RunIngestSubMenu.menuItem.empty=\u30fc\u7a7a\u767d\u30fc
RunIngestModulesMenu.getName.text=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB\u3092\u5B9F\u884C RunIngestModulesMenu.getName.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u5b9f\u884c
DataSourceIngestPipeline.moduleError.title.text={0}\u30A8\u30E9\u30FC DataSourceIngestPipeline.moduleError.title.text={0}\u30a8\u30e9\u30fc
FileIngestPipeline.moduleError.title.text={0}\u30A8\u30E9\u30FC FileIngestPipeline.moduleError.title.text={0}\u30a8\u30e9\u30fc
IngestJob.cancelReason.notCancelled.text=\u30AD\u30E3\u30F3\u30BB\u30EB\u3055\u308C\u3066\u3044\u307E\u305B\u3093 IngestJob.cancelReason.notCancelled.text=\u30ad\u30e3\u30f3\u30bb\u30eb\u3055\u308c\u3066\u3044\u307e\u305b\u3093
IngestJob.cancelReason.cancelledByUser.text=\u30E6\u30FC\u30B6\u30FC\u304C\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F IngestJob.cancelReason.cancelledByUser.text=\u30e6\u30fc\u30b6\u30fc\u304c\u30ad\u30e3\u30f3\u30bb\u30eb\u3057\u307e\u3057\u305f
IngestJob.cancelReason.ingestModStartFail.text=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB\u306E\u8D77\u52D5\u306B\u5931\u6557 IngestJob.cancelReason.ingestModStartFail.text=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u306e\u8d77\u52d5\u306b\u5931\u6557
IngestJob.cancelReason.outOfDiskSpace.text=\u30C7\u30A3\u30B9\u30AF\u30B9\u30DA\u30FC\u30B9\u304C\u8DB3\u308A\u307E\u305B\u3093 IngestJob.cancelReason.outOfDiskSpace.text=\u30c7\u30a3\u30b9\u30af\u30b9\u30da\u30fc\u30b9\u304c\u8db3\u308a\u307e\u305b\u3093
IngestJob.cancelReason.servicesDown.text=\u30AD\u30E3\u30F3\u30BB\u30EB\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F IngestJob.cancelReason.caseClosed.text=\u30b1\u30fc\u30b9\u3092\u9589\u3058\u307e\u3057\u305f
IngestJob.cancelReason.caseClosed.text=\u30B1\u30FC\u30B9\u3092\u9589\u3058\u307E\u3057\u305F

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2014 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -39,8 +39,10 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.python.util.PythonObjectInputStream; import org.python.util.PythonObjectInputStream;
/** /**
* Encapsulates the ingest job settings for a particular context such as the Add * Encapsulates the ingest job settings for a particular execution context.
* Data Source wizard or the Run Ingest Modules dialog. * Examples of execution contexts include the add data source wizard and the run
* ingest modules dialog. Different execution conterxts may have different
* ingest job settings.
*/ */
public class IngestJobSettings { public class IngestJobSettings {
@ -60,19 +62,30 @@ public class IngestJobSettings {
private boolean processUnallocatedSpace; private boolean processUnallocatedSpace;
private final List<String> warnings; private final List<String> warnings;
// Determines which modeules to run /**
* The type of ingest modules to run.
*/
public enum IngestType { public enum IngestType {
// Run all modules /**
* Run both data source level and file-level ingest modules.
*/
ALL_MODULES, ALL_MODULES,
// Run only data source modules /**
* Run only data source level ingest modules.
*/
DATA_SOURCE_ONLY, DATA_SOURCE_ONLY,
// Run only files modules /**
* Run only file level ingest modules.
*/
FILES_ONLY FILES_ONLY
} }
/** /**
* Constructs an ingest job settings object for a given context. * Constructs an ingest job settings object for a given execution context.
* Examples of execution contexts include the add data source wizard and the
* run ingest modules dialog. Different execution conterxts may have
* different ingest job settings.
* *
* @param executionContext The ingest execution context identifier. * @param executionContext The ingest execution context identifier.
*/ */
@ -87,7 +100,10 @@ public class IngestJobSettings {
} }
/** /**
* Constructs an ingest job settings object for a given context. * Constructs an ingest job settings object for a given context. Examples of
* execution contexts include the add data source wizard and the run ingest
* modules dialog. Different execution conterxts may have different ingest
* job settings.
* *
* @param context The context identifier string. * @param context The context identifier string.
* @param ingestType The type of modules ingest is running. * @param ingestType The type of modules ingest is running.
@ -116,9 +132,12 @@ public class IngestJobSettings {
} }
/** /**
* Get the ingest execution context identifier. * Gets the ingest execution context identifier. Examples of execution
* contexts include the add data source wizard and the run ingest modules
* dialog. Different execution conterxts may have different ingest job
* settings.
* *
* @return The context string. * @return The execution context identifier.
*/ */
String getExecutionContext() { String getExecutionContext() {
return this.executionContext; return this.executionContext;

View File

@ -71,28 +71,27 @@ public class IngestManager {
private static IngestManager instance; private static IngestManager instance;
private final Object ingestMessageBoxLock = new Object(); private final Object ingestMessageBoxLock = new Object();
/** /*
* The ingest manager maintains a mapping of ingest job IDs to running * The ingest manager maintains a mapping of ingest job ids to running
* ingest jobs. * ingest jobs.
*/ */
private final ConcurrentHashMap<Long, IngestJob> jobsById; private final Map<Long, IngestJob> jobsById;
/** /*
* Each runnable/callable task the ingest manager submits to its thread * Each runnable/callable task the ingest manager submits to its thread
* pools is given a unique thread/task ID. * pools is given a unique thread/task ID.
*/ */
private final AtomicLong nextThreadId; private final AtomicLong nextThreadId;
/** /*
* Ingest jobs may be queued to be started on a pool thread by ingest job * Ingest jobs may be queued to be started on a pool thread by start ingest
* starters. A mapping of thread/task IDs to the result objects associated * job tasks. A mapping of task ids to the Future objects for each task is
* with each ingest job starter is maintained to provide handles that can be * maintained to allow for task cancellation.
* used to cancel the ingest job starter.
*/ */
private final ConcurrentHashMap<Long, Future<Void>> ingestJobStarters; private final Map<Long, Future<Void>> startIngestJobTasks;
private final ExecutorService startIngestJobsThreadPool; private final ExecutorService startIngestJobsThreadPool;
/** /*
* Ingest jobs use an ingest task scheduler to break themselves down into * Ingest jobs use an ingest task scheduler to break themselves down into
* data source level and file level tasks. The ingest scheduler puts these * data source level and file level tasks. The ingest scheduler puts these
* ingest tasks into queues for execution on ingest manager pool threads by * ingest tasks into queues for execution on ingest manager pool threads by
@ -118,14 +117,14 @@ public class IngestManager {
private AutopsyEventPublisher moduleEventPublisher; private AutopsyEventPublisher moduleEventPublisher;
private final ExecutorService eventPublishingExecutor; private final ExecutorService eventPublishingExecutor;
/** /*
* The ingest manager uses an ingest monitor to determine when system * The ingest manager uses an ingest monitor to determine when system
* resources are under pressure. If the monitor detects such a situation, it * resources are under pressure. If the monitor detects such a situation, it
* calls back to the ingest manager to cancel all ingest jobs in progress. * calls back to the ingest manager to cancel all ingest jobs in progress.
*/ */
private final IngestMonitor ingestMonitor; private final IngestMonitor ingestMonitor;
/** /*
* The ingest manager provides access to a top component that is used by * The ingest manager provides access to a top component that is used by
* ingest module to post messages for the user. A count of the posts is used * ingest module to post messages for the user. A count of the posts is used
* as a cap to avoid bogging down the application. * as a cap to avoid bogging down the application.
@ -134,7 +133,7 @@ public class IngestManager {
private volatile IngestMessageTopComponent ingestMessageBox; private volatile IngestMessageTopComponent ingestMessageBox;
private final AtomicLong ingestErrorMessagePosts; private final AtomicLong ingestErrorMessagePosts;
/** /*
* The ingest manager supports reporting of ingest processing progress by * The ingest manager supports reporting of ingest processing progress by
* collecting snapshots of the activities of the ingest threads, ingest job * collecting snapshots of the activities of the ingest threads, ingest job
* progress, and ingest module run times. * progress, and ingest module run times.
@ -142,13 +141,13 @@ public class IngestManager {
private final ConcurrentHashMap<Long, IngestThreadActivitySnapshot> ingestThreadActivitySnapshots; private final ConcurrentHashMap<Long, IngestThreadActivitySnapshot> ingestThreadActivitySnapshots;
private final ConcurrentHashMap<String, Long> ingestModuleRunTimes; private final ConcurrentHashMap<String, Long> ingestModuleRunTimes;
/** /*
* The ingest job creation capability of the ingest manager can be turned on * The ingest job creation capability of the ingest manager can be turned on
* and off to support an orderly shut down of the application. * and off to support an orderly shut down of the application.
*/ */
private volatile boolean jobCreationIsEnabled; private volatile boolean jobCreationIsEnabled;
/** /*
* Ingest manager subscribes to service outage notifications. If key * Ingest manager subscribes to service outage notifications. If key
* services are down, ingest manager cancels all ingest jobs in progress. * services are down, ingest manager cancels all ingest jobs in progress.
*/ */
@ -264,8 +263,8 @@ public class IngestManager {
this.dataSourceIngestThreadPool = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IM-data-source-ingest-%d").build()); //NON-NLS this.dataSourceIngestThreadPool = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IM-data-source-ingest-%d").build()); //NON-NLS
this.startIngestJobsThreadPool = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IM-start-ingest-jobs-%d").build()); //NON-NLS this.startIngestJobsThreadPool = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IM-start-ingest-jobs-%d").build()); //NON-NLS
this.nextThreadId = new AtomicLong(0L); this.nextThreadId = new AtomicLong(0L);
this.jobsById = new ConcurrentHashMap<>(); this.jobsById = new HashMap<>();
this.ingestJobStarters = new ConcurrentHashMap<>(); this.startIngestJobTasks = new ConcurrentHashMap<>();
this.servicesMonitor = ServicesMonitor.getInstance(); this.servicesMonitor = ServicesMonitor.getInstance();
subscribeToServiceMonitorEvents(); subscribeToServiceMonitorEvents();
@ -329,7 +328,7 @@ public class IngestManager {
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
if (evt.getNewValue().equals(ServicesMonitor.ServiceStatus.DOWN.toString())) { if (evt.getNewValue().equals(ServicesMonitor.ServiceStatus.DOWN.toString())) {
// check whether a milti-user case is currently being processed // check whether a multi-user case is currently being processed
try { try {
if (!Case.isCaseOpen() || Case.getCurrentCase().getCaseType() != Case.CaseType.MULTI_USER_CASE) { if (!Case.isCaseOpen() || Case.getCurrentCase().getCaseType() != Case.CaseType.MULTI_USER_CASE) {
return; return;
@ -474,13 +473,13 @@ public class IngestManager {
* @param dataSources The data sources to process. * @param dataSources The data sources to process.
* @param settings The settings for the ingest job. * @param settings The settings for the ingest job.
*/ */
public synchronized void queueIngestJob(Collection<Content> dataSources, IngestJobSettings settings) { public void queueIngestJob(Collection<Content> dataSources, IngestJobSettings settings) {
if (this.jobCreationIsEnabled) { if (jobCreationIsEnabled) {
IngestJob job = new IngestJob(dataSources, settings, RuntimeProperties.coreComponentsAreActive()); IngestJob job = new IngestJob(dataSources, settings, RuntimeProperties.coreComponentsAreActive());
if (job.hasIngestPipeline()) { if (job.hasIngestPipeline()) {
long taskId = nextThreadId.incrementAndGet(); long taskId = nextThreadId.incrementAndGet();
Future<Void> task = startIngestJobsThreadPool.submit(new StartIngestJobTask(taskId, job)); Future<Void> task = startIngestJobsThreadPool.submit(new StartIngestJobTask(taskId, job));
ingestJobStarters.put(taskId, task); startIngestJobTasks.put(taskId, task);
} }
} }
} }
@ -544,20 +543,18 @@ public class IngestManager {
ingestMonitor.start(); ingestMonitor.start();
} }
/** synchronized (jobsById) {
* Add the job to the jobs map now so that isIngestRunning() will jobsById.put(job.getId(), job);
* return true while the modules read global settings during start }
* up. This works because the core global settings panels restrict
* changes while analysis is in progress.
*/
this.jobsById.put(job.getId(), job);
List<IngestModuleError> errors = job.start(); List<IngestModuleError> errors = job.start();
if (errors.isEmpty()) { if (errors.isEmpty()) {
this.fireIngestJobStarted(job.getId()); this.fireIngestJobStarted(job.getId());
IngestManager.logger.log(Level.INFO, "Ingest job {0} started", job.getId()); //NON-NLS IngestManager.logger.log(Level.INFO, "Ingest job {0} started", job.getId()); //NON-NLS
success = true; success = true;
} else { } else {
this.jobsById.remove(job.getId()); synchronized (jobsById) {
this.jobsById.remove(job.getId());
}
for (IngestModuleError error : errors) { for (IngestModuleError error : errors) {
logger.log(Level.SEVERE, String.format("Error starting %s ingest module", error.getModuleDisplayName()), error.getModuleError()); //NON-NLS logger.log(Level.SEVERE, String.format("Error starting %s ingest module", error.getModuleDisplayName()), error.getModuleError()); //NON-NLS
} }
@ -599,13 +596,15 @@ public class IngestManager {
synchronized void finishIngestJob(IngestJob job) { synchronized void finishIngestJob(IngestJob job) {
long jobId = job.getId(); long jobId = job.getId();
this.jobsById.remove(jobId); synchronized (jobsById) {
jobsById.remove(jobId);
}
if (!job.isCancelled()) { if (!job.isCancelled()) {
IngestManager.logger.log(Level.INFO, "Ingest job {0} completed", jobId); //NON-NLS IngestManager.logger.log(Level.INFO, "Ingest job {0} completed", jobId); //NON-NLS
this.fireIngestJobCompleted(jobId); fireIngestJobCompleted(jobId);
} else { } else {
IngestManager.logger.log(Level.INFO, "Ingest job {0} cancelled", jobId); //NON-NLS IngestManager.logger.log(Level.INFO, "Ingest job {0} cancelled", jobId); //NON-NLS
this.fireIngestJobCancelled(jobId); fireIngestJobCancelled(jobId);
} }
} }
@ -615,7 +614,9 @@ public class IngestManager {
* @return True or false. * @return True or false.
*/ */
public boolean isIngestRunning() { public boolean isIngestRunning() {
return !this.jobsById.isEmpty(); synchronized (jobsById) {
return !jobsById.isEmpty();
}
} }
/** /**
@ -625,7 +626,7 @@ public class IngestManager {
* instead. * instead.
*/ */
@Deprecated @Deprecated
public synchronized void cancelAllIngestJobs() { public void cancelAllIngestJobs() {
cancelAllIngestJobs(IngestJob.CancellationReason.USER_CANCELLED); cancelAllIngestJobs(IngestJob.CancellationReason.USER_CANCELLED);
} }
@ -634,15 +635,21 @@ public class IngestManager {
* *
* @param reason The cancellation reason. * @param reason The cancellation reason.
*/ */
public synchronized void cancelAllIngestJobs(IngestJob.CancellationReason reason) { public void cancelAllIngestJobs(IngestJob.CancellationReason reason) {
// Stop creating new ingest jobs. /*
for (Future<Void> handle : ingestJobStarters.values()) { * Cancel the start job tasks.
*/
for (Future<Void> handle : startIngestJobTasks.values()) {
handle.cancel(true); handle.cancel(true);
} }
// Cancel all the jobs already created. /*
for (IngestJob job : this.jobsById.values()) { * Cancel the jobs in progress.
job.cancel(reason); */
synchronized (jobsById) {
for (IngestJob job : this.jobsById.values()) {
job.cancel(reason);
}
} }
} }
@ -903,8 +910,10 @@ public class IngestManager {
*/ */
List<DataSourceIngestJob.Snapshot> getIngestJobSnapshots() { List<DataSourceIngestJob.Snapshot> getIngestJobSnapshots() {
List<DataSourceIngestJob.Snapshot> snapShots = new ArrayList<>(); List<DataSourceIngestJob.Snapshot> snapShots = new ArrayList<>();
for (IngestJob job : this.jobsById.values()) { synchronized (jobsById) {
snapShots.addAll(job.getDataSourceIngestJobSnapshots()); for (IngestJob job : jobsById.values()) {
snapShots.addAll(job.getDataSourceIngestJobSnapshots());
}
} }
return snapShots; return snapShots;
} }
@ -941,7 +950,9 @@ public class IngestManager {
public Void call() { public Void call() {
try { try {
if (Thread.currentThread().isInterrupted()) { if (Thread.currentThread().isInterrupted()) {
jobsById.remove(job.getId()); synchronized (jobsById) {
jobsById.remove(job.getId());
}
return null; return null;
} }
@ -953,7 +964,7 @@ public class IngestManager {
if (progress != null) { if (progress != null) {
progress.setDisplayName(NbBundle.getMessage(this.getClass(), "IngestManager.StartIngestJobsTask.run.cancelling", displayName)); progress.setDisplayName(NbBundle.getMessage(this.getClass(), "IngestManager.StartIngestJobsTask.run.cancelling", displayName));
} }
Future<?> handle = ingestJobStarters.remove(threadId); Future<?> handle = startIngestJobTasks.remove(threadId);
handle.cancel(true); handle.cancel(true);
return true; return true;
} }
@ -968,7 +979,7 @@ public class IngestManager {
if (null != progress) { if (null != progress) {
progress.finish(); progress.finish();
} }
ingestJobStarters.remove(threadId); startIngestJobTasks.remove(threadId);
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-2015 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -39,11 +39,14 @@ import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Directory; import org.sleuthkit.datamodel.Directory;
/** /**
* Dialog box that allows a user to configure and run an ingest job on one or *
* more data sources. * A dialog box that allows a user to configure and execute analysis of one or
* more data sources with ingest modules or analysis of the contents of a
* directory with file-level ingest modules.
*/ */
public final class RunIngestModulesDialog extends JDialog { public final class RunIngestModulesDialog extends JDialog {
private static final long serialVersionUID = 1L;
private static final String TITLE = NbBundle.getMessage(RunIngestModulesDialog.class, "IngestDialog.title.text"); private static final String TITLE = NbBundle.getMessage(RunIngestModulesDialog.class, "IngestDialog.title.text");
private final IngestType ingestType; private final IngestType ingestType;
private static Dimension DIMENSIONS = new Dimension(500, 300); private static Dimension DIMENSIONS = new Dimension(500, 300);
@ -51,13 +54,13 @@ public final class RunIngestModulesDialog extends JDialog {
private IngestJobSettingsPanel ingestJobSettingsPanel; private IngestJobSettingsPanel ingestJobSettingsPanel;
/** /**
* Construct a dialog box that allows a user to configure and run an ingest * Constructs a dialog box that allows a user to configure and execute
* job on one or more data sources. * analysis of one or more data sources with ingest modules.
* *
* @param frame The dialog parent window. * @param frame The dialog parent window.
* @param title The title for the dialog. * @param title The title for the dialog.
* @param modal True if the dialog should be modal, false otherwise. * @param modal True if the dialog should be modal, false otherwise.
* @param dataSources The data sources to be processed. * @param dataSources The data sources to be analyzed.
*/ */
public RunIngestModulesDialog(JFrame frame, String title, boolean modal, List<Content> dataSources) { public RunIngestModulesDialog(JFrame frame, String title, boolean modal, List<Content> dataSources) {
super(frame, title, modal); super(frame, title, modal);
@ -66,60 +69,26 @@ public final class RunIngestModulesDialog extends JDialog {
} }
/** /**
* Construct a dialog box that allows a user to configure and run an ingest * Constructs a dialog box that allows a user to configure and execute
* job on one or more data sources. * analysis of one or more data sources with ingest modules.
* *
* @param dataSources The data sources to be processed. * @param dataSources The data sources to be processed.
*/ */
public RunIngestModulesDialog(List<Content> dataSources) { public RunIngestModulesDialog(List<Content> dataSources) {
this(new JFrame(TITLE), TITLE, true, dataSources); this(new JFrame(TITLE), TITLE, true, dataSources);
} }
/**
* Constructs a dialog box that allows a user to configure and execute
* analysis of the contents of a directory with file-level ingest modules.
*
* @param dir
*/
public RunIngestModulesDialog(Directory dir) { public RunIngestModulesDialog(Directory dir) {
this.dataSources.add(dir); this.dataSources.add(dir);
this.ingestType = IngestType.FILES_ONLY; this.ingestType = IngestType.FILES_ONLY;
} }
/**
* Construct a dialog box that allows a user to configure and run an ingest
* job on one or more data sources.
*
* @param frame The dialog parent window.
* @param title The title for the dialog.
* @param modal True if the dialog should be modal, false otherwise.
*
* @deprecated
*/
@Deprecated
public RunIngestModulesDialog(JFrame frame, String title, boolean modal) {
super(frame, title, modal);
this.ingestType = IngestType.ALL_MODULES;
}
/**
* Construct a dialog box that allows a user to configure and run an ingest
* job on one or more data sources.
*
* @deprecated
*/
@Deprecated
public RunIngestModulesDialog() {
this(new JFrame(TITLE), TITLE, true);
}
/**
* Set the data sources to be processed.
*
* @param dataSources The data sources.
*
* @deprecated
*/
@Deprecated
public void setDataSources(List<Content> dataSources) {
this.dataSources.clear();
this.dataSources.addAll(dataSources);
}
/** /**
* Displays this dialog. * Displays this dialog.
*/ */
@ -139,7 +108,7 @@ public final class RunIngestModulesDialog extends JDialog {
* Get the default or saved ingest job settings for this context and use * Get the default or saved ingest job settings for this context and use
* them to create and add an ingest job settings panel. * them to create and add an ingest job settings panel.
*/ */
IngestJobSettings ingestJobSettings = new IngestJobSettings(RunIngestModulesDialog.class.getCanonicalName(), this.ingestType); IngestJobSettings ingestJobSettings = new IngestJobSettings(RunIngestModulesDialog.class.getCanonicalName(), this.ingestType);
RunIngestModulesDialog.showWarnings(ingestJobSettings); RunIngestModulesDialog.showWarnings(ingestJobSettings);
this.ingestJobSettingsPanel = new IngestJobSettingsPanel(ingestJobSettings); this.ingestJobSettingsPanel = new IngestJobSettingsPanel(ingestJobSettings);
add(this.ingestJobSettingsPanel, BorderLayout.PAGE_START); add(this.ingestJobSettingsPanel, BorderLayout.PAGE_START);
@ -227,4 +196,45 @@ public final class RunIngestModulesDialog extends JDialog {
JOptionPane.showMessageDialog(null, warningMessage.toString()); JOptionPane.showMessageDialog(null, warningMessage.toString());
} }
} }
/**
* Constructs a dialog box that allows a user to configure and execute
* analysis of one or more data sources with ingest modules.
*
* @param frame The dialog parent window.
* @param title The title for the dialog.
* @param modal True if the dialog should be modal, false otherwise.
*
* @deprecated
*/
@Deprecated
public RunIngestModulesDialog(JFrame frame, String title, boolean modal) {
super(frame, title, modal);
this.ingestType = IngestType.ALL_MODULES;
}
/**
* Constructs a dialog box that allows a user to configure and run an ingest
* job on one or more data sources.
*
* @deprecated
*/
@Deprecated
public RunIngestModulesDialog() {
this(new JFrame(TITLE), TITLE, true);
}
/**
* Sets the data sources to be processed.
*
* @param dataSources The data sources.
*
* @deprecated
*/
@Deprecated
public void setDataSources(List<Content> dataSources) {
this.dataSources.clear();
this.dataSources.addAll(dataSources);
}
} }

View File

@ -79,7 +79,7 @@ public final class EmbeddedFileExtractorIngestModule implements FileIngestModule
} catch (SecurityException ex) { } catch (SecurityException ex) {
logger.log(Level.SEVERE, "Error initializing output dir: " + moduleDirAbsolute, ex); //NON-NLS logger.log(Level.SEVERE, "Error initializing output dir: " + moduleDirAbsolute, ex); //NON-NLS
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), "Error initializing", "Error initializing output dir: " + moduleDirAbsolute)); //NON-NLS services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), "Error initializing", "Error initializing output dir: " + moduleDirAbsolute)); //NON-NLS
throw new IngestModuleException(ex.getMessage()); throw new IngestModuleException(ex.getMessage(), ex);
} }
} }
@ -87,7 +87,7 @@ public final class EmbeddedFileExtractorIngestModule implements FileIngestModule
try { try {
fileTypeDetector = new FileTypeDetector(); fileTypeDetector = new FileTypeDetector();
} catch (FileTypeDetector.FileTypeDetectorInitException ex) { } catch (FileTypeDetector.FileTypeDetectorInitException ex) {
throw new IngestModuleException(ex.getMessage()); throw new IngestModuleException(ex.getMessage(), ex);
} }
// initialize the extraction modules. // initialize the extraction modules.

View File

@ -133,7 +133,7 @@ class SevenZipExtractor {
String details = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errCantInitLib", String details = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errCantInitLib",
e.getMessage()); e.getMessage());
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details)); services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
throw new IngestModuleException(e.getMessage()); throw new IngestModuleException(e.getMessage(), e);
} }
} }
this.context = context; this.context = context;

View File

@ -91,7 +91,7 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
try { try {
fileTypeDetector = new FileTypeDetector(); fileTypeDetector = new FileTypeDetector();
} catch (FileTypeDetector.FileTypeDetectorInitException ex) { } catch (FileTypeDetector.FileTypeDetectorInitException ex) {
throw new IngestModuleException(NbBundle.getMessage(this.getClass(), "ExifParserFileIngestModule.startUp.fileTypeDetectorInitializationException.msg")); throw new IngestModuleException(NbBundle.getMessage(this.getClass(), "ExifParserFileIngestModule.startUp.fileTypeDetectorInitializationException.msg"), ex);
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011-2014 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,13 +18,11 @@
*/ */
package org.sleuthkit.autopsy.modules.fileextmismatch; package org.sleuthkit.autopsy.modules.fileextmismatch;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.Blackboard;
@ -40,7 +38,6 @@ import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskException; import org.sleuthkit.datamodel.TskException;
@ -58,6 +55,7 @@ public class FileExtMismatchIngestModule implements FileIngestModule {
private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs = new HashMap<>(); private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs = new HashMap<>();
private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
private static Blackboard blackboard; private static Blackboard blackboard;
private FileTypeDetector detector;
private static class IngestJobTotals { private static class IngestJobTotals {
@ -94,6 +92,11 @@ public class FileExtMismatchIngestModule implements FileIngestModule {
FileExtMismatchXML xmlLoader = FileExtMismatchXML.getDefault(); FileExtMismatchXML xmlLoader = FileExtMismatchXML.getDefault();
SigTypeToExtMap = xmlLoader.load(); SigTypeToExtMap = xmlLoader.load();
try {
this.detector = new FileTypeDetector();
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
throw new IngestModuleException("Could not create file type detector.", ex);
}
} }
@ -157,7 +160,7 @@ public class FileExtMismatchIngestModule implements FileIngestModule {
if (settings.skipFilesWithNoExtension() && currActualExt.isEmpty()) { if (settings.skipFilesWithNoExtension() && currActualExt.isEmpty()) {
return false; return false;
} }
String currActualSigType = abstractFile.getMIMEType(); String currActualSigType = detector.getFileType(abstractFile);
if (currActualSigType == null) { if (currActualSigType == null) {
return false; return false;
} }

View File

@ -35,10 +35,10 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskDataException;
/** /**
* Detects the type of a file by an inspection of its contents. * Detects the MIME type of a file by an inspection of its contents, using both
* user-defined type definitions and Tika.
*/ */
public class FileTypeDetector { public class FileTypeDetector {
@ -49,11 +49,14 @@ public class FileTypeDetector {
private static final Logger logger = Logger.getLogger(FileTypeDetector.class.getName()); private static final Logger logger = Logger.getLogger(FileTypeDetector.class.getName());
/** /**
* Constructs an object that detects the type of a file by an inspection of * Constructs an object that detects the MIME type of a file by an
* its contents. * inspection of its contents, using both user-defined type definitions and
* Tika.
* *
* @throws FileTypeDetector.FileTypeDetectorInitException if an * @throws FileTypeDetectorInitException if an initialization error occurs,
* initialization error occurs. * e.g., user-defined file type
* definitions exist but cannot be
* loaded.
*/ */
public FileTypeDetector() throws FileTypeDetectorInitException { public FileTypeDetector() throws FileTypeDetectorInitException {
try { try {
@ -64,12 +67,26 @@ public class FileTypeDetector {
} }
/** /**
* Determines whether or not a given MIME type is detectable by this * Gets the names of the user-defined MIME types.
* detector.
* *
* @param mimeType The MIME type name, e.g. "text/html", to look up. * @return A list of the user-defined MIME types.
*/
public List<String> getUserDefinedTypes() {
List<String> list = new ArrayList<>();
if (userDefinedFileTypes != null) {
for (FileType fileType : userDefinedFileTypes) {
list.add(fileType.getMimeType());
}
}
return list;
}
/**
* Determines whether or not a given MIME type is detectable.
* *
* @return True if MIME type is detectable. * @param mimeType The MIME type name (e.g., "text/html").
*
* @return True if the given MIME type is detectable.
*/ */
public boolean isDetectable(String mimeType) { public boolean isDetectable(String mimeType) {
return isDetectableAsUserDefinedType(mimeType) || isDetectableByTika(mimeType); return isDetectableAsUserDefinedType(mimeType) || isDetectableByTika(mimeType);
@ -77,11 +94,11 @@ public class FileTypeDetector {
/** /**
* Determines whether or not a given MIME type is detectable as a * Determines whether or not a given MIME type is detectable as a
* user-defined file type. * user-defined MIME type.
* *
* @param mimeType The MIME type name, e.g. "text/html", to look up. * @param mimeType The MIME type name (e.g., "text/html").
* *
* @return True if MIME type is detectable. * @return True if the given MIME type is detectable.
*/ */
private boolean isDetectableAsUserDefinedType(String mimeType) { private boolean isDetectableAsUserDefinedType(String mimeType) {
for (FileType fileType : userDefinedFileTypes) { for (FileType fileType : userDefinedFileTypes) {
@ -95,9 +112,9 @@ public class FileTypeDetector {
/** /**
* Determines whether or not a given MIME type is detectable by Tika. * Determines whether or not a given MIME type is detectable by Tika.
* *
* @param mimeType The MIME type name, e.g. "text/html", to look up. * @param mimeType The MIME type name (e.g., "text/html").
* *
* @return True if MIME type is detectable. * @return True if the given MIME type is detectable.
*/ */
private boolean isDetectableByTika(String mimeType) { private boolean isDetectableByTika(String mimeType) {
String[] split = mimeType.split("/"); String[] split = mimeType.split("/");
@ -112,59 +129,42 @@ public class FileTypeDetector {
} }
/** /**
* Look up the MIME type of a file using the blackboard. If it is not * Gets the MIME type of a file, detecting it if it is not already known. If
* already posted, detect the type of the file, posting it to the blackboard * detection is necessary, the result is added to the case database.
* if detection succeeds.
* *
* @param file The file to test. * @param file The file.
* *
* @return The MIME type name if detection was successful, null otherwise. * @return A MIME type name.
* *
* @throws TskCoreException * @throws TskCoreException if detection is required and there is a problem
*/ * writing the result to the case database.
public String getFileType(AbstractFile file) throws TskCoreException {
String fileType = file.getMIMEType();
if (null != fileType) {
return fileType;
}
return detectAndPostToBlackboard(file);
}
/**
* Detect the MIME type of a file, posting it to the blackboard if detection
* succeeds. Note that this method should currently be called at most once
* per file.
*
* @param file The file to test.
*
* @return The MIME type name id detection was successful, null otherwise.
*
* @throws TskCoreException
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public String detectAndPostToBlackboard(AbstractFile file) throws TskCoreException { public String getFileType(AbstractFile file) throws TskCoreException {
String mimeType = detect(file); String mimeType = file.getMIMEType();
if (null != mimeType) { if (null != mimeType) {
/** return mimeType;
* Add the file type attribute to the general info artifact. Note
* that no property change is fired for this blackboard posting
* because general info artifacts are different from other
* artifacts, e.g., they are not displayed in the results tree.
*/
try {
file.setMIMEType(mimeType);
BlackboardArtifact getInfoArt = file.getGenInfoArtifact();
BlackboardAttribute batt = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG, FileTypeIdModuleFactory.getModuleName(), mimeType);
getInfoArt.addAttribute(batt);
} catch (TskDataException ex) {
//Swallowing exception so that the logs aren't clogged in the case that ingest is run multiple times.
}
} }
mimeType = detect(file);
Case.getCurrentCase().getSleuthkitCase().setFileMIMEType(file, mimeType);
/*
* Add the file type attribute to the general info artifact. Note that
* no property change is fired for this blackboard posting because
* general info artifacts are different from other artifacts, e.g., they
* are not displayed in the results tree.
*/
BlackboardArtifact getInfoArt = file.getGenInfoArtifact();
BlackboardAttribute batt = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG, FileTypeIdModuleFactory.getModuleName(), mimeType);
getInfoArt.addAttribute(batt);
return mimeType; return mimeType;
} }
/** /**
* Detect the MIME type of a file. * Detects the MIME type of a file. The result is not added to the case
* database.
* *
* @param file The file to test. * @param file The file to test.
* *
@ -173,9 +173,12 @@ public class FileTypeDetector {
* @throws TskCoreException * @throws TskCoreException
*/ */
public String detect(AbstractFile file) throws TskCoreException { public String detect(AbstractFile file) throws TskCoreException {
// consistently mark non-regular files (refer TskData.TSK_FS_META_TYPE_ENUM), /*
// 0 sized files, unallocated, and unused blocks (refer TskData.TSK_DB_FILES_TYPE_ENUM) * Consistently mark non-regular files (refer to
// as octet-stream. * TskData.TSK_FS_META_TYPE_ENUM), zero-sized files, unallocated space,
* and unused blocks (refer to TskData.TSK_DB_FILES_TYPE_ENUM) as
* octet-stream.
*/
if (!file.isFile() || file.getSize() <= 0 if (!file.isFile() || file.getSize() <= 0
|| (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) || (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
|| (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) || (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)
@ -183,8 +186,8 @@ public class FileTypeDetector {
return MimeTypes.OCTET_STREAM; return MimeTypes.OCTET_STREAM;
} }
String fileType = detectUserDefinedType(file); String mimeType = detectUserDefinedType(file);
if (null == fileType) { if (null == mimeType) {
try { try {
byte buf[]; byte buf[];
int len = file.read(buffer, 0, BUFFER_SIZE); int len = file.read(buffer, 0, BUFFER_SIZE);
@ -195,23 +198,25 @@ public class FileTypeDetector {
buf = buffer; buf = buffer;
} }
String mimetype = tika.detect(buf, file.getName()); String tikaType = tika.detect(buf, file.getName());
/** /**
* Strip out any Tika enhancements to the MIME type name. * Strip out any Tika enhancements to the MIME type name.
*/ */
return mimetype.replace("tika-", ""); //NON-NLS mimeType = tikaType.replace("tika-", ""); //NON-NLS
} catch (Exception ignored) { } catch (Exception ignored) {
/** /*
* This exception is swallowed rather than propagated because * This exception is swallowed and not logged rather than
* files in images are not always consistent with their file * propagated because files in data sources are not always
* system meta data making for read errors, and Tika can be a * consistent with their file system metadata, making for read
* bit flaky at times, making this a best effort endeavor. * errors, and Tika can be a bit flaky at times, making this a
* best effort endeavor. Default to octet-stream.
*/ */
mimeType = MimeTypes.OCTET_STREAM;
} }
} }
return fileType; return mimeType;
} }
/** /**
@ -235,7 +240,7 @@ public class FileTypeDetector {
BlackboardAttribute setNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, FileTypeIdModuleFactory.getModuleName(), fileType.getFilesSetName()); BlackboardAttribute setNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, FileTypeIdModuleFactory.getModuleName(), fileType.getFilesSetName());
artifact.addAttribute(setNameAttribute); artifact.addAttribute(setNameAttribute);
/** /*
* Use the MIME type as the category, i.e., the rule that * Use the MIME type as the category, i.e., the rule that
* determined this file belongs to the interesting files * determined this file belongs to the interesting files
* set. * set.
@ -259,6 +264,7 @@ public class FileTypeDetector {
} }
public static class FileTypeDetectorInitException extends Exception { public static class FileTypeDetectorInitException extends Exception {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
FileTypeDetectorInitException(String message) { FileTypeDetectorInitException(String message) {
@ -271,17 +277,22 @@ public class FileTypeDetector {
} }
/** /**
* Gets the list of user defined file types (MIME types) * Gets the MIME type of a file, detecting it if it is not already known. If
* detection is necessary, the result is added to the case database.
* *
* @return the List<String> of user defined file types * @param file The file.
*
* @return A MIME type name.
*
* @throws TskCoreException if detection is required and there is a problem
* writing the result to the case database.
* @deprecated Use getFileType instead and use AbstractFile.getMIMEType
* instead of querying the blackboard.
*/ */
public List<String> getUserDefinedTypes() { @Deprecated
List<String> list = new ArrayList<>(); @SuppressWarnings("deprecation")
if (userDefinedFileTypes != null) { public String detectAndPostToBlackboard(AbstractFile file) throws TskCoreException {
for (FileType fileType : userDefinedFileTypes) { return getFileType(file);
list.add(fileType.getMimeType());
}
}
return list;
} }
} }

View File

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
<Component id="skipKnownCheckBox" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="46" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="11" max="-2" attributes="0"/>
<Component id="skipKnownCheckBox" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="86" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JCheckBox" name="skipKnownCheckBox">
<Properties>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties" key="FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties" key="FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="skipKnownCheckBoxActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -1,99 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 - 2014 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.modules.filetypeid;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel;
/**
* UI component used to set ingest job options for file type identifier ingest
* modules.
*/
class FileTypeIdIngestJobSettingsPanel extends IngestModuleIngestJobSettingsPanel {
private final FileTypeIdModuleSettings settings;
FileTypeIdIngestJobSettingsPanel(FileTypeIdModuleSettings settings) {
this.settings = settings;
initComponents();
customizeComponents();
}
/**
* @inheritDoc
*/
@Override
public IngestModuleIngestJobSettings getSettings() {
return settings;
}
/**
* Does child component initialization in addition to that done by the
* Matisse generated code.
*/
private void customizeComponents() {
skipKnownCheckBox.setSelected(settings.skipKnownFiles());
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
skipKnownCheckBox = new javax.swing.JCheckBox();
skipKnownCheckBox.setSelected(true);
skipKnownCheckBox.setText(org.openide.util.NbBundle.getMessage(FileTypeIdIngestJobSettingsPanel.class, "FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.text")); // NOI18N
skipKnownCheckBox.setToolTipText(org.openide.util.NbBundle.getMessage(FileTypeIdIngestJobSettingsPanel.class, "FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.toolTipText")); // NOI18N
skipKnownCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
skipKnownCheckBoxActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(10, 10, 10)
.addComponent(skipKnownCheckBox)
.addContainerGap(46, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(11, 11, 11)
.addComponent(skipKnownCheckBox)
.addContainerGap(86, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
private void skipKnownCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_skipKnownCheckBoxActionPerformed
settings.setSkipKnownFiles(skipKnownCheckBox.isSelected());
}//GEN-LAST:event_skipKnownCheckBoxActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JCheckBox skipKnownCheckBox;
// End of variables declaration//GEN-END:variables
}

View File

@ -27,7 +27,6 @@ import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskData.FileKnown;
import org.sleuthkit.autopsy.ingest.IngestModule.ProcessResult; import org.sleuthkit.autopsy.ingest.IngestModule.ProcessResult;
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
@ -38,7 +37,6 @@ import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
public class FileTypeIdIngestModule implements FileIngestModule { public class FileTypeIdIngestModule implements FileIngestModule {
private static final Logger logger = Logger.getLogger(FileTypeIdIngestModule.class.getName()); private static final Logger logger = Logger.getLogger(FileTypeIdIngestModule.class.getName());
private final FileTypeIdModuleSettings settings;
private long jobId; private long jobId;
private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs = new HashMap<>(); private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs = new HashMap<>();
private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
@ -66,11 +64,8 @@ public class FileTypeIdIngestModule implements FileIngestModule {
/** /**
* Creates an ingest module that detects the type of a file based on * Creates an ingest module that detects the type of a file based on
* signature (magic) values. Posts results to the blackboard. * signature (magic) values. Posts results to the blackboard.
*
* @param settings The ingest module settings.
*/ */
FileTypeIdIngestModule(FileTypeIdModuleSettings settings) { FileTypeIdIngestModule() {
this.settings = settings;
} }
/** /**
@ -83,7 +78,7 @@ public class FileTypeIdIngestModule implements FileIngestModule {
try { try {
fileTypeDetector = new FileTypeDetector(); fileTypeDetector = new FileTypeDetector();
} catch (FileTypeDetector.FileTypeDetectorInitException ex) { } catch (FileTypeDetector.FileTypeDetectorInitException ex) {
throw new IngestModuleException(NbBundle.getMessage(this.getClass(), "FileTypeIdIngestModule.startUp.fileTypeDetectorInitializationException.msg")); throw new IngestModuleException(NbBundle.getMessage(this.getClass(), "FileTypeIdIngestModule.startUp.fileTypeDetectorInitializationException.msg"), ex);
} }
} }
@ -93,13 +88,6 @@ public class FileTypeIdIngestModule implements FileIngestModule {
@Override @Override
public ProcessResult process(AbstractFile file) { public ProcessResult process(AbstractFile file) {
/**
* Skip known files if configured to do so.
*/
if (settings.skipKnownFiles() && (file.getKnown() == FileKnown.KNOWN)) {
return ProcessResult.OK;
}
/** /**
* Attempt to detect the file type. Do it within an exception firewall, * Attempt to detect the file type. Do it within an exception firewall,
* so that any issues with reading file content or complaints from tika * so that any issues with reading file content or complaints from tika
@ -153,7 +141,7 @@ public class FileTypeIdIngestModule implements FileIngestModule {
* Update the match time total and increment number of files processed for * Update the match time total and increment number of files processed for
* this ingest job. * this ingest job.
* *
* @param jobId The ingest job identifier. * @param jobId The ingest job identifier.
* @param matchTimeInc Amount of time to add. * @param matchTimeInc Amount of time to add.
*/ */
private static synchronized void addToTotals(long jobId, long matchTimeInc) { private static synchronized void addToTotals(long jobId, long matchTimeInc) {

View File

@ -26,7 +26,6 @@ import org.sleuthkit.autopsy.ingest.IngestModuleFactory;
import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter;
import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel;
/** /**
* A factory that creates file ingest modules that determine the types of files. * A factory that creates file ingest modules that determine the types of files.
@ -92,35 +91,6 @@ public class FileTypeIdModuleFactory extends IngestModuleFactoryAdapter {
return globalSettingsPanel; return globalSettingsPanel;
} }
/**
* @inheritDoc
*/
@Override
public IngestModuleIngestJobSettings getDefaultIngestJobSettings() {
return new FileTypeIdModuleSettings();
}
/**
* @inheritDoc
*/
@Override
public boolean hasIngestJobSettingsPanel() {
return true;
}
/**
* @inheritDoc
*/
@Override
public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings settings) {
assert settings instanceof FileTypeIdModuleSettings;
if (!(settings instanceof FileTypeIdModuleSettings)) {
throw new IllegalArgumentException(NbBundle.getMessage(this.getClass(),
"FileTypeIdModuleFactory.getIngestJobSettingsPanel.exception.msg"));
}
return new FileTypeIdIngestJobSettingsPanel((FileTypeIdModuleSettings) settings);
}
/** /**
* @inheritDoc * @inheritDoc
*/ */
@ -134,11 +104,6 @@ public class FileTypeIdModuleFactory extends IngestModuleFactoryAdapter {
*/ */
@Override @Override
public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) {
assert settings instanceof FileTypeIdModuleSettings; return new FileTypeIdIngestModule();
if (!(settings instanceof FileTypeIdModuleSettings)) {
throw new IllegalArgumentException(
NbBundle.getMessage(this.getClass(), "FileTypeIdModuleFactory.createFileIngestModule.exception.msg"));
}
return new FileTypeIdIngestModule((FileTypeIdModuleSettings) settings);
} }
} }

View File

@ -1,56 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.modules.filetypeid;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
/**
* Ingest job options for the file type identifier ingest module instances.
*/
// TODO: This class does not need to be public.
public class FileTypeIdModuleSettings implements IngestModuleIngestJobSettings {
private static final long serialVersionUID = 1L;
private boolean skipKnownFiles = true;
private boolean skipSmallFiles = false; // No longer used.
FileTypeIdModuleSettings() {
}
FileTypeIdModuleSettings(boolean skipKnownFiles) {
this.skipKnownFiles = skipKnownFiles;
}
/**
* @inheritDoc
*/
@Override
public long getVersionNumber() {
return serialVersionUID;
}
void setSkipKnownFiles(boolean enabled) {
skipKnownFiles = enabled;
}
boolean skipKnownFiles() {
return skipKnownFiles;
}
}

View File

@ -152,7 +152,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
// Initialize job totals // Initialize job totals
initTotalsForIngestJob(jobId); initTotalsForIngestJob(jobId);
} catch (SecurityException | IOException | UnsupportedOperationException ex) { } catch (SecurityException | IOException | UnsupportedOperationException ex) {
throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "cannotCreateOutputDir.message", ex.getLocalizedMessage())); throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "cannotCreateOutputDir.message", ex.getLocalizedMessage()), ex);
} }
} }
} }
@ -396,7 +396,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
} catch (FileAlreadyExistsException ex) { } catch (FileAlreadyExistsException ex) {
// No worries. // No worries.
} catch (IOException | SecurityException | UnsupportedOperationException ex) { } catch (IOException | SecurityException | UnsupportedOperationException ex) {
throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "cannotCreateOutputDir.message", ex.getLocalizedMessage())); throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "cannotCreateOutputDir.message", ex.getLocalizedMessage()), ex);
} }
return path; return path;
} }

View File

@ -983,8 +983,8 @@ class ReportHTML implements TableReportModule {
StringBuilder head = new StringBuilder(); StringBuilder head = new StringBuilder();
head.append("<html>\n<head>\n<title>").append( //NON-NLS head.append("<html>\n<head>\n<title>").append( //NON-NLS
NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.title")).append("</title>\n"); //NON-NLS NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.title")).append("</title>\n"); //NON-NLS
head.append("<style type=\"text/css\">\n"); //NON-NLS
head.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS head.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
head.append("<style type=\"text/css\">\n"); //NON-NLS
head.append("body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); //NON-NLS head.append("body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); //NON-NLS
head.append("#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); //NON-NLS head.append("#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); //NON-NLS
head.append("h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); //NON-NLS head.append("h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); //NON-NLS

View File

@ -440,6 +440,7 @@ public class TimeLineController {
return false; return false;
} }
@SuppressWarnings("deprecation") // TODO (EUR-733): Do not use SleuthkitCase.getLastObjectId
@ThreadConfined(type = ThreadConfined.ThreadType.ANY) @ThreadConfined(type = ThreadConfined.ThreadType.ANY)
@NbBundle.Messages({"TimeLineController.errorTitle=Timeline error.", @NbBundle.Messages({"TimeLineController.errorTitle=Timeline error.",
"TimeLineController.outOfDate.errorMessage=Error determing if the timeline is out of date. We will assume it should be updated. See the logs for more details.", "TimeLineController.outOfDate.errorMessage=Error determing if the timeline is out of date. We will assume it should be updated. See the logs for more details.",

View File

@ -461,6 +461,7 @@ public class EventsRepository {
updateProgress(workDone, total); updateProgress(workDone, total);
} }
@SuppressWarnings("deprecation") // TODO (EUR-733): Do not use SleuthkitCase.getLastObjectId
@Override @Override
@NbBundle.Messages({"progressWindow.msg.refreshingFileTags=Refreshing file tags", @NbBundle.Messages({"progressWindow.msg.refreshingFileTags=Refreshing file tags",
"progressWindow.msg.refreshingResultTags=Refreshing result tags", "progressWindow.msg.refreshingResultTags=Refreshing result tags",

View File

@ -6,8 +6,8 @@ OpenIDE-Module-Name=ImageGallery
OpenIDE-Module-Short-Description=Advanced image and video gallery OpenIDE-Module-Short-Description=Advanced image and video gallery
ImageGalleryOptionsPanel.enabledForCaseBox.text=Enable Image Gallery updates for the current case. ImageGalleryOptionsPanel.enabledForCaseBox.text=Enable Image Gallery updates for the current case.
ImageGalleryOptionsPanel.enabledByDefaultBox.text=Enable Image Gallery for new cases by default. ImageGalleryOptionsPanel.enabledByDefaultBox.text=Enable Image Gallery for new cases by default.
ImageGalleryOptionsPanel.enabledForCaseBox.toolTipText=If Image Gallery is disabled, only the fact that an update is needed is recorded. If Image Gallery is enabled after ingest, it will do one bulk update based on the results form ingest. If Image Gallery is disabled, you will be prompted to enable it when attempting to open its window. ImageGalleryOptionsPanel.enabledForCaseBox.toolTipText=If Image Gallery is disabled, only the fact that an update is needed is recorded. If Image Gallery is enabled after ingest, it will do one bulk update based on the results from ingest. If Image Gallery is disabled, you will be prompted to enable it when attempting to open its window.
ImageGalleryOptionsPanel.descriptionLabel.text=<html>To minimize its startup times, Image Gallery will constantly update its internal database. <br />This can cause ingest to be slower if you do not need the Image Gallery features. <br />Use these settings to disable Image Gallery if you do not need it.</html> ImageGalleryOptionsPanel.descriptionLabel.text=<html>To minimize its startup times, Image Gallery will constantly update its internal database. <br />This can cause ingest to be slower if you do not need the Image Gallery features. <br />Use these settings to disable Image Gallery if you do not need it.</html>
ImageGalleryOptionsPanel.furtherDescriptionArea.text=If Image Gallery is disabled, only the fact that an update is needed is recorded. If Image Gallery is enabled after ingest, it will do one bulk update based on the results form ingest. If Image Gallery is disabled, you will be prompted to enable it when attempting to open its window. ImageGalleryOptionsPanel.furtherDescriptionArea.text=If Image Gallery is disabled, only the fact that an update is needed is recorded. If Image Gallery is enabled after ingest, it will do one bulk update based on the results from ingest. If Image Gallery is disabled, you will be prompted to enable it when attempting to open its window.
ImageGalleryOptionsPanel.unavailableDuringInjestLabel.text=This setting is unavailable during ingest. ImageGalleryOptionsPanel.unavailableDuringInjestLabel.text=This setting is unavailable during ingest.
ImageGalleryOptionsPanel.infoIconLabel.text= ImageGalleryOptionsPanel.groupCategorizationWarningBox.text=Don't show a warning when overwriting categories, by acting on an entire group.

View File

@ -107,6 +107,8 @@ public enum FileTypeUtils {
, "sn", "ras" //sun raster NON-NLS , "sn", "ras" //sun raster NON-NLS
, "ico" //windows icons NON-NLS , "ico" //windows icons NON-NLS
, "tga" //targa NON-NLS , "tga" //targa NON-NLS
, "wmf", "emf" // windows meta file NON-NLS
, "wmz", "emz" //compressed windows meta file NON-NLS
)); ));
//add list of known video extensions //add list of known video extensions
@ -129,6 +131,8 @@ public enum FileTypeUtils {
* mime types. * mime types.
*/ */
supportedMimeTypes.addAll(Arrays.asList("application/x-123")); //NON-NLS supportedMimeTypes.addAll(Arrays.asList("application/x-123")); //NON-NLS
supportedMimeTypes.addAll(Arrays.asList("application/x-wmf")); //NON-NLS
supportedMimeTypes.addAll(Arrays.asList("application/x-emf")); //NON-NLS
//add list of mimetypes ImageIO claims to support //add list of mimetypes ImageIO claims to support
supportedMimeTypes.addAll(Stream.of(ImageIO.getReaderMIMETypes()) supportedMimeTypes.addAll(Stream.of(ImageIO.getReaderMIMETypes())

View File

@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.imagegallery;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@ -34,6 +35,7 @@ import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper; import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyIntegerProperty; import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper; import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty;
@ -54,7 +56,6 @@ import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import org.apache.commons.lang3.StringUtils;
import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable; import org.openide.util.Cancellable;
@ -85,6 +86,7 @@ import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.VirtualDirectory;
/** /**
* Connects different parts of ImageGallery together and is hub for flow of * Connects different parts of ImageGallery together and is hub for flow of
@ -94,6 +96,7 @@ public final class ImageGalleryController implements Executor {
private final Executor execDelegate = Executors.newSingleThreadExecutor(); private final Executor execDelegate = Executors.newSingleThreadExecutor();
private Runnable showTree; private Runnable showTree;
private Toolbar toolbar;
@Override @Override
public void execute(Runnable command) { public void execute(Runnable command) {
@ -127,14 +130,13 @@ public final class ImageGalleryController implements Executor {
*/ */
private final SimpleBooleanProperty listeningEnabled = new SimpleBooleanProperty(false); private final SimpleBooleanProperty listeningEnabled = new SimpleBooleanProperty(false);
private final ReadOnlyIntegerWrapper queueSizeProperty = new ReadOnlyIntegerWrapper(0);
private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false); private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false);
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private final ReadOnlyBooleanWrapper stale = new ReadOnlyBooleanWrapper(false); private final ReadOnlyBooleanWrapper stale = new ReadOnlyBooleanWrapper(false);
private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false); private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false);
private final ReadOnlyDoubleWrapper thumbnailSize = new ReadOnlyDoubleWrapper(100);
private final FileIDSelectionModel selectionModel = new FileIDSelectionModel(this); private final FileIDSelectionModel selectionModel = new FileIDSelectionModel(this);
@ -153,7 +155,6 @@ public final class ImageGalleryController implements Executor {
private Node infoOverlay; private Node infoOverlay;
private SleuthkitCase sleuthKitCase; private SleuthkitCase sleuthKitCase;
// private NavPanel navPanel;
public ReadOnlyBooleanProperty getMetaDataCollapsed() { public ReadOnlyBooleanProperty getMetaDataCollapsed() {
return metaDataCollapsed.getReadOnlyProperty(); return metaDataCollapsed.getReadOnlyProperty();
@ -163,6 +164,10 @@ public final class ImageGalleryController implements Executor {
this.metaDataCollapsed.set(metaDataCollapsed); this.metaDataCollapsed.set(metaDataCollapsed);
} }
public ReadOnlyDoubleProperty thumbnailSizeProperty() {
return thumbnailSize.getReadOnlyProperty();
}
private GroupViewState getViewState() { private GroupViewState getViewState() {
return historyManager.getCurrentState(); return historyManager.getCurrentState();
} }
@ -175,7 +180,7 @@ public final class ImageGalleryController implements Executor {
return historyManager.currentState(); return historyManager.currentState();
} }
public synchronized FileIDSelectionModel getSelectionModel() { public FileIDSelectionModel getSelectionModel() {
return selectionModel; return selectionModel;
} }
@ -187,12 +192,16 @@ public final class ImageGalleryController implements Executor {
return db; return db;
} }
synchronized public void setListeningEnabled(boolean enabled) { public void setListeningEnabled(boolean enabled) {
listeningEnabled.set(enabled); synchronized (listeningEnabled) {
listeningEnabled.set(enabled);
}
} }
synchronized boolean isListeningEnabled() { boolean isListeningEnabled() {
return listeningEnabled.get(); synchronized (listeningEnabled) {
return listeningEnabled.get();
}
} }
@ThreadConfined(type = ThreadConfined.ThreadType.ANY) @ThreadConfined(type = ThreadConfined.ThreadType.ANY)
@ -248,12 +257,14 @@ public final class ImageGalleryController implements Executor {
checkForGroups(); checkForGroups();
}); });
IngestManager.getInstance().addIngestModuleEventListener((PropertyChangeEvent evt) -> { IngestManager ingestManager = IngestManager.getInstance();
Platform.runLater(this::updateRegroupDisabled); PropertyChangeListener ingestEventHandler =
}); propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled);
IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> {
Platform.runLater(this::updateRegroupDisabled); ingestManager.addIngestModuleEventListener(ingestEventHandler);
}); ingestManager.addIngestJobEventListener(ingestEventHandler);
queueSizeProperty.addListener(obs -> this.updateRegroupDisabled());
} }
public ReadOnlyBooleanProperty getCanAdvance() { public ReadOnlyBooleanProperty getCanAdvance() {
@ -280,8 +291,9 @@ public final class ImageGalleryController implements Executor {
return historyManager.retreat(); return historyManager.retreat();
} }
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void updateRegroupDisabled() { private void updateRegroupDisabled() {
regroupDisabled.set(getFileUpdateQueueSizeProperty().get() > 0 || IngestManager.getInstance().isIngestRunning()); regroupDisabled.set((queueSizeProperty.get() > 0) || IngestManager.getInstance().isIngestRunning());
} }
/** /**
@ -290,16 +302,16 @@ public final class ImageGalleryController implements Executor {
* aren't, add a blocking progress spinner with appropriate message. * aren't, add a blocking progress spinner with appropriate message.
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. " + @NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. "
" No groups will be available until ingest is finished and listening is re-enabled.", + " No groups will be available until ingest is finished and listening is re-enabled.",
"ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.", "ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.",
"ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.", "ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.",
"ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. " + "ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. "
" No groups will be available until ingest is finished and listening is re-enabled.", + " No groups will be available until ingest is finished and listening is re-enabled.",
"ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.", "ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.",
"ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:" + "ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:"
" the current Group By setting resulted in no groups, " + + " the current Group By setting resulted in no groups, "
"or no groups are fully analyzed but ingest is not running."}) + "or no groups are fully analyzed but ingest is not running."})
public void checkForGroups() { public void checkForGroups() {
if (groupManager.getAnalyzedGroups().isEmpty()) { if (groupManager.getAnalyzedGroups().isEmpty()) {
if (IngestManager.getInstance().isIngestRunning()) { if (IngestManager.getInstance().isIngestRunning()) {
@ -312,7 +324,7 @@ public final class ImageGalleryController implements Executor {
new ProgressIndicator())); new ProgressIndicator()));
} }
} else if (getFileUpdateQueueSizeProperty().get() > 0) { } else if (queueSizeProperty.get() > 0) {
replaceNotification(fullUIStackPane, replaceNotification(fullUIStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(), new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(),
new ProgressIndicator())); new ProgressIndicator()));
@ -357,20 +369,14 @@ public final class ImageGalleryController implements Executor {
} }
} }
private void restartWorker() { synchronized private DBWorkerThread restartWorker() {
if (dbWorkerThread != null) { if (dbWorkerThread == null) {
dbWorkerThread = new DBWorkerThread(this);
dbWorkerThread.start();
} else {
// Keep using the same worker thread if one exists // Keep using the same worker thread if one exists
return;
} }
dbWorkerThread = new DBWorkerThread(); return dbWorkerThread;
getFileUpdateQueueSizeProperty().addListener((Observable o) -> {
Platform.runLater(this::updateRegroupDisabled);
});
Thread th = new Thread(dbWorkerThread, "DB-Worker-Thread");
th.setDaemon(false); // we want it to go away when it is done
th.start();
} }
/** /**
@ -411,15 +417,18 @@ public final class ImageGalleryController implements Executor {
setListeningEnabled(false); setListeningEnabled(false);
ThumbnailCache.getDefault().clearCache(); ThumbnailCache.getDefault().clearCache();
historyManager.clear(); historyManager.clear();
groupManager.clear();
tagsManager.clearFollowUpTagName(); tagsManager.clearFollowUpTagName();
tagsManager.unregisterListener(groupManager); tagsManager.unregisterListener(groupManager);
tagsManager.unregisterListener(categoryManager); tagsManager.unregisterListener(categoryManager);
dbWorkerThread.cancelAllTasks(); dbWorkerThread.cancel();
dbWorkerThread = null; dbWorkerThread = null;
restartWorker(); dbWorkerThread = restartWorker();
if (toolbar != null) {
toolbar.reset();
}
Toolbar.getDefault(this).reset();
groupManager.clear();
if (db != null) { if (db != null) {
db.closeDBCon(); db.closeDBCon();
} }
@ -431,17 +440,15 @@ public final class ImageGalleryController implements Executor {
* *
* @param innerTask * @param innerTask
*/ */
public void queueDBWorkerTask(InnerTask innerTask) { public synchronized void queueDBWorkerTask(BackgroundTask innerTask) {
// @@@ We could make a lock for the worker thread
if (dbWorkerThread == null) { if (dbWorkerThread == null) {
restartWorker(); dbWorkerThread = restartWorker();
} }
dbWorkerThread.addTask(innerTask); dbWorkerThread.addTask(innerTask);
} }
@Nullable @Nullable
synchronized public DrawableFile<?> getFileFromId(Long fileID) throws TskCoreException { synchronized public DrawableFile getFileFromId(Long fileID) throws TskCoreException {
if (Objects.isNull(db)) { if (Objects.isNull(db)) {
LOGGER.log(Level.WARNING, "Could not get file from id, no DB set. The case is probably closed."); //NON-NLS LOGGER.log(Level.WARNING, "Could not get file from id, no DB set. The case is probably closed."); //NON-NLS
return null; return null;
@ -455,8 +462,12 @@ public final class ImageGalleryController implements Executor {
Platform.runLater(this::checkForGroups); Platform.runLater(this::checkForGroups);
} }
public ReadOnlyIntegerProperty getFileUpdateQueueSizeProperty() { public synchronized void setToolbar(Toolbar toolbar) {
return queueSizeProperty.getReadOnlyProperty(); if (this.toolbar != null) {
throw new IllegalStateException("Can not set the toolbar a second time!");
}
this.toolbar = toolbar;
thumbnailSize.bind(toolbar.thumbnailSizeProperty());
} }
public ReadOnlyDoubleProperty regroupProgress() { public ReadOnlyDoubleProperty regroupProgress() {
@ -496,29 +507,43 @@ public final class ImageGalleryController implements Executor {
return undoManager; return undoManager;
} }
// @@@ REVIEW IF THIS SHOLD BE STATIC... public ReadOnlyIntegerProperty getDBTasksQueueSizeProperty() {
//TODO: concept seems like the controller deal with how much work to do at a given time return queueSizeProperty.getReadOnlyProperty();
}
private final ReadOnlyIntegerWrapper queueSizeProperty = new ReadOnlyIntegerWrapper(0);
// @@@ review this class for synchronization issues (i.e. reset and cancel being called, add, etc.) // @@@ review this class for synchronization issues (i.e. reset and cancel being called, add, etc.)
private class DBWorkerThread implements Runnable { static private class DBWorkerThread extends Thread implements Cancellable {
private final ImageGalleryController controller;
DBWorkerThread(ImageGalleryController controller) {
super("DB-Worker-Thread");
setDaemon(false);
this.controller = controller;
}
// true if the process was requested to stop. Currently no way to reset it // true if the process was requested to stop. Currently no way to reset it
private volatile boolean cancelled = false; private volatile boolean cancelled = false;
// list of tasks to run // list of tasks to run
private final BlockingQueue<InnerTask> workQueue = new LinkedBlockingQueue<>(); private final BlockingQueue<BackgroundTask> workQueue = new LinkedBlockingQueue<>();
/** /**
* Cancel all of the queued up tasks and the currently scheduled task. * Cancel all of the queued up tasks and the currently scheduled task.
* Note that after you cancel, you cannot submit new jobs to this * Note that after you cancel, you cannot submit new jobs to this
* thread. * thread.
*/ */
public void cancelAllTasks() { @Override
public boolean cancel() {
cancelled = true; cancelled = true;
for (InnerTask it : workQueue) { for (BackgroundTask it : workQueue) {
it.cancel(); it.cancel();
} }
workQueue.clear(); workQueue.clear();
queueSizeProperty.set(workQueue.size()); int size = workQueue.size();
Platform.runLater(() -> controller.queueSizeProperty.set(size));
return true;
} }
/** /**
@ -526,11 +551,10 @@ public final class ImageGalleryController implements Executor {
* *
* @param it * @param it
*/ */
public void addTask(InnerTask it) { public void addTask(BackgroundTask it) {
workQueue.add(it); workQueue.add(it);
Platform.runLater(() -> { int size = workQueue.size();
queueSizeProperty.set(workQueue.size()); Platform.runLater(() -> controller.queueSizeProperty.set(size));
});
} }
@Override @Override
@ -538,19 +562,17 @@ public final class ImageGalleryController implements Executor {
// nearly infinite loop waiting for tasks // nearly infinite loop waiting for tasks
while (true) { while (true) {
if (cancelled) { if (cancelled || isInterrupted()) {
return; return;
} }
try { try {
InnerTask it = workQueue.take(); BackgroundTask it = workQueue.take();
if (it.isCancelled() == false) { if (it.isCancelled() == false) {
it.run(); it.run();
} }
int size = workQueue.size();
Platform.runLater(() -> { Platform.runLater(() -> controller.queueSizeProperty.set(size));
queueSizeProperty.set(workQueue.size());
});
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
LOGGER.log(Level.SEVERE, "Failed to run DB worker thread", ex); //NON-NLS LOGGER.log(Level.SEVERE, "Failed to run DB worker thread", ex); //NON-NLS
@ -567,8 +589,15 @@ public final class ImageGalleryController implements Executor {
* Abstract base class for task to be done on {@link DBWorkerThread} * Abstract base class for task to be done on {@link DBWorkerThread}
*/ */
@NbBundle.Messages({"ImageGalleryController.InnerTask.progress.name=progress", @NbBundle.Messages({"ImageGalleryController.InnerTask.progress.name=progress",
"ImageGalleryController.InnerTask.message.name=status"}) "ImageGalleryController.InnerTask.message.name=status"})
static public abstract class InnerTask implements Runnable, Cancellable { static public abstract class BackgroundTask implements Runnable, Cancellable {
private final SimpleObjectProperty<Worker.State> state = new SimpleObjectProperty<>(Worker.State.READY);
private final SimpleDoubleProperty progress = new SimpleDoubleProperty(this, Bundle.ImageGalleryController_InnerTask_progress_name());
private final SimpleStringProperty message = new SimpleStringProperty(this, Bundle.ImageGalleryController_InnerTask_message_name());
protected BackgroundTask() {
}
public double getProgress() { public double getProgress() {
return progress.get(); return progress.get();
@ -585,9 +614,6 @@ public final class ImageGalleryController implements Executor {
public final void updateMessage(String Status) { public final void updateMessage(String Status) {
this.message.set(Status); this.message.set(Status);
} }
SimpleObjectProperty<Worker.State> state = new SimpleObjectProperty<>(Worker.State.READY);
SimpleDoubleProperty progress = new SimpleDoubleProperty(this, Bundle.ImageGalleryController_InnerTask_progress_name());
SimpleStringProperty message = new SimpleStringProperty(this, Bundle.ImageGalleryController_InnerTask_message_name());
public SimpleDoubleProperty progressProperty() { public SimpleDoubleProperty progressProperty() {
return progress; return progress;
@ -601,24 +627,21 @@ public final class ImageGalleryController implements Executor {
return state.get(); return state.get();
} }
protected void updateState(Worker.State newState) {
state.set(newState);
}
public ReadOnlyObjectProperty<Worker.State> stateProperty() { public ReadOnlyObjectProperty<Worker.State> stateProperty() {
return new ReadOnlyObjectWrapper<>(state.get()); return new ReadOnlyObjectWrapper<>(state.get());
} }
protected InnerTask() {
}
@Override @Override
synchronized public boolean cancel() { public synchronized boolean cancel() {
updateState(Worker.State.CANCELLED); updateState(Worker.State.CANCELLED);
return true; return true;
} }
synchronized protected boolean isCancelled() { protected void updateState(Worker.State newState) {
state.set(newState);
}
protected synchronized boolean isCancelled() {
return getState() == Worker.State.CANCELLED; return getState() == Worker.State.CANCELLED;
} }
} }
@ -626,7 +649,7 @@ public final class ImageGalleryController implements Executor {
/** /**
* Abstract base class for tasks associated with a file in the database * Abstract base class for tasks associated with a file in the database
*/ */
static public abstract class FileTask extends InnerTask { static public abstract class FileTask extends BackgroundTask {
private final AbstractFile file; private final AbstractFile file;
private final DrawableDB taskDB; private final DrawableDB taskDB;
@ -644,7 +667,6 @@ public final class ImageGalleryController implements Executor {
this.file = f; this.file = f;
this.taskDB = taskDB; this.taskDB = taskDB;
} }
} }
/** /**
@ -662,7 +684,7 @@ public final class ImageGalleryController implements Executor {
@Override @Override
public void run() { public void run() {
try { try {
DrawableFile<?> drawableFile = DrawableFile.create(getFile(), true, getTaskDB().isVideoFile(getFile())); DrawableFile drawableFile = DrawableFile.create(getFile(), true, false);
getTaskDB().updateFile(drawableFile); getTaskDB().updateFile(drawableFile);
} catch (NullPointerException ex) { } catch (NullPointerException ex) {
// This is one of the places where we get many errors if the case is closed during processing. // This is one of the places where we get many errors if the case is closed during processing.
@ -701,63 +723,63 @@ public final class ImageGalleryController implements Executor {
} }
} }
/** @NbBundle.Messages({"BulkTask.committingDb.status=commiting image/video database",
* Task that runs when image gallery listening is (re) enabled. "BulkTask.stopCopy.status=Stopping copy to drawable db task.",
* "BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."})
* Grabs all files with supported image/video mime types or extensions, and abstract static private class BulkTransferTask extends BackgroundTask {
* adds them to the Drawable DB. Uses the presence of a mimetype as an
* approximation to 'analyzed'.
*/
@NbBundle.Messages({"CopyAnalyzedFiles.populatingDb.status=populating analyzed image/video database",
"CopyAnalyzedFiles.committingDb.status=commiting image/video database",
"CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.",
"CopyAnalyzedFiles.errPopulating.errMsg=There was an error populating Image Gallery database."})
static private class CopyAnalyzedFiles extends InnerTask {
private final ImageGalleryController controller; static private final String FILE_EXTENSION_CLAUSE =
private final DrawableDB taskDB; "(name LIKE '%." //NON-NLS
private final SleuthkitCase tskCase; + String.join("' OR name LIKE '%.", FileTypeUtils.getAllSupportedExtensions()) //NON-NLS
+ "')";
CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) { static private final String MIMETYPE_CLAUSE =
"(mime_type LIKE '" //NON-NLS
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
+ "') ";
static final String DRAWABLE_QUERY =
//grab files with supported extension
"(" + FILE_EXTENSION_CLAUSE
//grab files with supported mime-types
+ " OR " + MIMETYPE_CLAUSE //NON-NLS
//grab files with image or video mime-types even if we don't officially support them
+ " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS
final ImageGalleryController controller;
final DrawableDB taskDB;
final SleuthkitCase tskCase;
ProgressHandle progressHandle;
BulkTransferTask(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
this.controller = controller; this.controller = controller;
this.taskDB = taskDB; this.taskDB = taskDB;
this.tskCase = tskCase; this.tskCase = tskCase;
} }
static private final String FILE_EXTENSION_CLAUSE = abstract void cleanup(boolean success);
"(name LIKE '%." //NON-NLS
+ StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") //NON-NLS
+ "')";
static private final String MIMETYPE_CLAUSE = abstract List<AbstractFile> getFiles() throws TskCoreException;
"(mime_type LIKE '" //NON-NLS
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(), "' OR mime_type LIKE '") //NON-NLS
+ "') ";
static private final String DRAWABLE_QUERY = abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) throws TskCoreException;
//grab files with supported extension
FILE_EXTENSION_CLAUSE
//grab files with supported mime-types
+ " OR " + MIMETYPE_CLAUSE //NON-NLS
//grab files with image or video mime-types even if we don't officially support them
+ " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%'"; //NON-NLS
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle(Bundle.CopyAnalyzedFiles_populatingDb_status());
@Override @Override
public void run() { public void run() {
progressHandle = getInitialProgressHandle();
progressHandle.start(); progressHandle.start();
updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status()); updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status());
try { try {
//grab all files with supported extension or detected mime types //grab all files with supported extension or detected mime types
final List<AbstractFile> files = tskCase.findAllFilesWhere(DRAWABLE_QUERY); final List<AbstractFile> files = getFiles();
progressHandle.switchToDeterminate(files.size()); progressHandle.switchToDeterminate(files.size());
updateProgress(0.0); updateProgress(0.0);
//do in transaction //do in transaction
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction(); DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
int units = 0; int workDone = 0;
for (final AbstractFile f : files) { for (final AbstractFile f : files) {
if (isCancelled()) { if (isCancelled()) {
LOGGER.log(Level.WARNING, "Task cancelled: not all contents may be transfered to drawable database."); //NON-NLS LOGGER.log(Level.WARNING, "Task cancelled: not all contents may be transfered to drawable database."); //NON-NLS
@ -765,161 +787,172 @@ public final class ImageGalleryController implements Executor {
break; break;
} }
final boolean known = f.getKnown() == TskData.FileKnown.KNOWN; processFile(f, tr);
if (known) { workDone++;
taskDB.removeFile(f.getId(), tr); //remove known files progressHandle.progress(f.getName(), workDone);
} else { updateProgress(workDone - 1 / (double) files.size());
Optional<String> mimeType = FileTypeUtils.getMimeType(f);
if (mimeType.isPresent()) {
//mime type
if (FileTypeUtils.isDrawableMimeType(mimeType.get())) { //supported mimetype => analyzed
taskDB.updateFile(DrawableFile.create(f, true, false), tr);
} else { //unsupported mimtype => analyzed but shouldn't include
taskDB.removeFile(f.getId(), tr);
}
} else {
//no mime tyoe
if (FileTypeUtils.isDrawable(f)) {
//no mime type but supported => add as not analyzed
taskDB.insertFile(DrawableFile.create(f, false, false), tr);
} else {
//no mime type, not supported => remove ( should never get here)
taskDB.removeFile(f.getId(), tr);
}
}
}
units++;
final int prog = units;
progressHandle.progress(f.getName(), units);
updateProgress(prog - 1 / (double) files.size());
updateMessage(f.getName()); updateMessage(f.getName());
} }
progressHandle.finish(); progressHandle.finish();
progressHandle = ProgressHandleFactory.createHandle(Bundle.BulkTask_committingDb_status());
progressHandle = ProgressHandleFactory.createHandle(Bundle.CopyAnalyzedFiles_committingDb_status()); updateMessage(Bundle.BulkTask_committingDb_status());
updateMessage(Bundle.CopyAnalyzedFiles_committingDb_status());
updateProgress(1.0); updateProgress(1.0);
progressHandle.start(); progressHandle.start();
taskDB.commitTransaction(tr, true); taskDB.commitTransaction(tr, true);
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
progressHandle.progress(Bundle.CopyAnalyzedFiles_stopCopy_status()); progressHandle.progress(Bundle.BulkTask_stopCopy_status());
Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents: " + ex.getMessage()); //NON-NLS LOGGER.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS
MessageNotifyUtil.Notify.warn(Bundle.CopyAnalyzedFiles_errPopulating_errMsg(), ex.getMessage()); MessageNotifyUtil.Notify.warn(Bundle.BulkTask_errPopulating_errMsg(), ex.getMessage());
cleanup(false);
return;
} finally {
progressHandle.finish(); progressHandle.finish();
updateMessage(""); updateMessage("");
updateProgress(-1.0); updateProgress(-1.0);
controller.setStale(true);
return;
} }
cleanup(true);
}
progressHandle.finish(); abstract ProgressHandle getInitialProgressHandle();
updateMessage(""); }
updateProgress(-1.0);
controller.setStale(false); /**
* Task that runs when image gallery listening is (re) enabled.
*
* Grabs all files with supported image/video mime types or extensions, and
* adds them to the Drawable DB. Uses the presence of a mimetype as an
* approximation to 'analyzed'.
*/
@NbBundle.Messages({"CopyAnalyzedFiles.committingDb.status=commiting image/video database",
"CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.",
"CopyAnalyzedFiles.errPopulating.errMsg=There was an error populating Image Gallery database."})
static private class CopyAnalyzedFiles extends BulkTransferTask {
CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
super(controller, taskDB, tskCase);
}
@Override
protected void cleanup(boolean success) {
controller.setStale(!success);
}
@Override
List<AbstractFile> getFiles() throws TskCoreException {
return tskCase.findAllFilesWhere(DRAWABLE_QUERY);
}
@Override
void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr) throws TskCoreException {
final boolean known = f.getKnown() == TskData.FileKnown.KNOWN;
if (known) {
taskDB.removeFile(f.getId(), tr); //remove known files
} else {
Optional<String> mimeType = FileTypeUtils.getMimeType(f);
if (mimeType.isPresent()) {
//mime type
if (FileTypeUtils.isDrawableMimeType(mimeType.get())) { //supported mimetype => analyzed
taskDB.updateFile(DrawableFile.create(f, true, false), tr);
} else { //unsupported mimtype => analyzed but shouldn't include
taskDB.removeFile(f.getId(), tr);
}
} else {
//no mime tyoe
if (FileTypeUtils.isDrawable(f)) {
//no mime type but supported => add as not analyzed
taskDB.insertFile(DrawableFile.create(f, false, false), tr);
} else {
//no mime type, not supported => remove ( should never get here)
taskDB.removeFile(f.getId(), tr);
}
}
}
}
@Override
@NbBundle.Messages({"CopyAnalyzedFiles.populatingDb.status=populating analyzed image/video database",})
ProgressHandle getInitialProgressHandle() {
return ProgressHandleFactory.createHandle(Bundle.CopyAnalyzedFiles_populatingDb_status(), this);
} }
} }
/** /**
* task that does pre-ingest copy over of files from a new datasource (uses * Copy files from a newly added data source into the DB. Get all "drawable"
* fs_obj_id to identify files from new datasources) * files, based on extension and mime-type. After ingest we use file type id
* module and if necessary jpeg/png signature matching to add/remove files
* *
* TODO: create methods to simplify progress value/text updates to both * TODO: create methods to simplify progress value/text updates to both
* netbeans and ImageGallery progress/status * netbeans and ImageGallery progress/status
*/ */
@NbBundle.Messages({"PrePopulateDataSourceFiles.prepopulatingDb.status=prepopulating image/video database", @NbBundle.Messages({"PrePopulateDataSourceFiles.committingDb.status=commiting image/video database"})
"PrePopulateDataSourceFiles.committingDb.status=commiting image/video database"}) static private class PrePopulateDataSourceFiles extends BulkTransferTask {
private class PrePopulateDataSourceFiles extends InnerTask {
private static final Logger LOGGER = Logger.getLogger(PrePopulateDataSourceFiles.class.getName());
private final Content dataSource; private final Content dataSource;
/**
* here we grab by extension but in file_done listener we look at file
* type id attributes but fall back on jpeg signatures and extensions to
* check for supported images
*/
// (name like '.jpg' or name like '.png' ...)
private final String DRAWABLE_QUERY = "(name LIKE '%." + StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") + "') "; //NON-NLS
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this);
/** /**
* *
* @param dataSourceId Data source object ID * @param dataSourceId Data source object ID
*/ */
PrePopulateDataSourceFiles(Content dataSource) { PrePopulateDataSourceFiles(Content dataSource, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
super(); super(controller, taskDB, tskCase);
this.dataSource = dataSource; this.dataSource = dataSource;
} }
/**
* Copy files from a newly added data source into the DB. Get all
* "drawable" files, based on extension. After ingest we use file type
* id module and if necessary jpeg/png signature matching to add/remove
* files
*/
@Override @Override
public void run() { protected void cleanup(boolean success) {
progressHandle.start(); }
updateMessage(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status());
try { @Override
String fsQuery = "(fs_obj_id IS NULL) "; //default clause NON-NLS void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) {
taskDB.insertFile(DrawableFile.create(f, false, false), tr);
}
@Override
List<AbstractFile> getFiles() throws TskCoreException {
if (dataSource instanceof Image) {
List<FileSystem> fileSystems = ((Image) dataSource).getFileSystems();
if (fileSystems.isEmpty()) {
/*
* no filesystems, don't bother with the initial population,
* just sort things out on file_done events
*/
progressHandle.finish();
return Collections.emptyList();
}
//use this clause to only grab files from the newly added filesystems.
String fsQuery = fileSystems.stream()
.map(fileSystem -> String.valueOf(fileSystem.getId()))
.collect(Collectors.joining(" OR fs_obj_id = ", "(fs_obj_id = ", ") ")); //NON-NLS
return tskCase.findAllFilesWhere(fsQuery + " AND " + DRAWABLE_QUERY); //NON-NLS
} else if (dataSource instanceof VirtualDirectory) {
/* /*
* NOTE: Logical files currently (Apr '15) have a null value for * fs_obj_id is set only for file system files, so we will match
* fs_obj_id in DB. for them, we will not specify a fs_obj_id, * the VirtualDirectory's name in the parent path.
* which means we will grab files from another data source, but *
* the drawable DB is smart enough to de-dupe them. For Images * TODO: A future database schema could probably make this
* we can do better. * cleaner. If we had a datasource_id column in the files table
* we could just match agains that.
*/ */
if (dataSource instanceof Image) { return tskCase.findAllFilesWhere(" parent_path LIKE '/" + dataSource.getName() + "/%' AND " + DRAWABLE_QUERY); //NON-NLS
List<FileSystem> fileSystems = ((Image) dataSource).getFileSystems(); } else {
if (fileSystems.isEmpty()) { String msg = "Uknown datasource type: " + dataSource.getClass().getName();
/* LOGGER.log(Level.SEVERE, msg);
* no filesystems, don't bother with the initial throw new IllegalArgumentException(msg);
* population, just sort things out on file_done events
*/
progressHandle.finish();
return;
}
//use this clause to only grab files from the newly added filesystems.
fsQuery = fileSystems.stream()
.map(fileSystem -> String.valueOf(fileSystem.getId()))
.collect(Collectors.joining(" OR fs_obj_id = ", "(fs_obj_id = ", ") ")); //NON-NLS
}
final List<AbstractFile> files = getSleuthKitCase().findAllFilesWhere(fsQuery + " AND " + DRAWABLE_QUERY); //NON-NLS
progressHandle.switchToDeterminate(files.size());
//do in transaction
DrawableDB.DrawableTransaction tr = db.beginTransaction();
int units = 0;
for (final AbstractFile f : files) {
if (isCancelled()) {
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database"); //NON-NLS
progressHandle.finish();
break;
}
db.insertFile(DrawableFile.create(f, false, false), tr);
units++;
progressHandle.progress(f.getName(), units);
}
progressHandle.finish();
progressHandle = ProgressHandleFactory.createHandle(Bundle.PrePopulateDataSourceFiles_committingDb_status());
progressHandle.start();
db.commitTransaction(tr, false);
} catch (TskCoreException ex) {
Logger.getLogger(PrePopulateDataSourceFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex); //NON-NLS
} }
}
progressHandle.finish(); @Override
@NbBundle.Messages({"PrePopulateDataSourceFiles.prepopulatingDb.status=prepopulating image/video database",})
ProgressHandle getInitialProgressHandle() {
return ProgressHandleFactory.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this);
} }
} }
@ -1011,7 +1044,7 @@ public final class ImageGalleryController implements Executor {
//copy all file data to drawable databse //copy all file data to drawable databse
Content newDataSource = (Content) evt.getNewValue(); Content newDataSource = (Content) evt.getNewValue();
if (isListeningEnabled()) { if (isListeningEnabled()) {
queueDBWorkerTask(new PrePopulateDataSourceFiles(newDataSource)); queueDBWorkerTask(new PrePopulateDataSourceFiles(newDataSource, ImageGalleryController.this, getDatabase(), getSleuthKitCase()));
} else {//TODO: keep track of what we missed for later } else {//TODO: keep track of what we missed for later
setStale(true); setStale(true);
} }
@ -1028,7 +1061,6 @@ public final class ImageGalleryController implements Executor {
getTagsManager().fireTagDeletedEvent(tagDeletedEvent); getTagsManager().fireTagDeletedEvent(tagDeletedEvent);
} }
break; break;
} }
} }
} }

View File

@ -27,42 +27,53 @@
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0"> <Group type="102" attributes="0">
<EmptySpace min="21" pref="21" max="-2" attributes="0"/> <Group type="103" groupAlignment="0" attributes="0">
<Component id="unavailableDuringInjestLabel" min="-2" max="-2" attributes="0"/> <Group type="102" attributes="0">
</Group> <EmptySpace min="21" pref="21" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0"> <Component id="unavailableDuringInjestLabel" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0"> </Group>
<EmptySpace min="21" pref="21" max="-2" attributes="0"/> <Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="infoIconLabel" min="-2" max="-2" attributes="0"/> <Component id="enabledByDefaultBox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <Component id="enabledForCaseBox" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="furtherDescriptionArea" max="32767" attributes="0"/> <Component id="descriptionLabel" alignment="1" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="21" pref="21" max="-2" attributes="0"/>
<Component id="infoIconLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="furtherDescriptionArea" max="32767" attributes="0"/>
</Group>
</Group>
<Component id="groupCategorizationWarningBox" alignment="0" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<Component id="enabledByDefaultBox" min="-2" max="-2" attributes="0"/> <EmptySpace min="0" pref="36" max="32767" attributes="0"/>
<Component id="enabledForCaseBox" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="descriptionLabel" alignment="0" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<Component id="jSeparator1" max="32767" attributes="0"/>
</Group> </Group>
<EmptySpace pref="46" max="32767" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
</Group> </Group>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
<DimensionLayout dim="1"> <DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0"> <Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/> <EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="descriptionLabel" min="-2" max="-2" attributes="0"/> <Component id="descriptionLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/> <EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="enabledByDefaultBox" min="-2" max="-2" attributes="0"/> <Component id="enabledByDefaultBox" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/> <EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="enabledForCaseBox" min="-2" max="-2" attributes="0"/> <Component id="enabledForCaseBox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/> <EmptySpace min="-2" max="-2" attributes="0"/>
<Component id="unavailableDuringInjestLabel" min="-2" max="-2" attributes="0"/> <Component id="unavailableDuringInjestLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/> <EmptySpace type="separate" min="-2" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Component id="infoIconLabel" min="-2" max="-2" attributes="0"/> <Component id="infoIconLabel" min="-2" max="-2" attributes="0"/>
<Component id="furtherDescriptionArea" min="-2" max="-2" attributes="0"/> <Component id="furtherDescriptionArea" min="-2" pref="100" max="-2" attributes="0"/>
</Group> </Group>
<EmptySpace min="-2" pref="45" max="-2" attributes="0"/> <EmptySpace type="separate" min="-2" max="-2" attributes="0"/>
<Component id="jSeparator1" min="-2" pref="10" max="-2" attributes="0"/>
<EmptySpace type="unrelated" min="-2" max="-2" attributes="0"/>
<Component id="groupCategorizationWarningBox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group> </Group>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
@ -145,9 +156,6 @@
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/imagegallery/images/info-icon-16.png"/> <Image iconType="3" name="/org/sleuthkit/autopsy/imagegallery/images/info-icon-16.png"/>
</Property> </Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.infoIconLabel.text" replaceFormat="NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JLabel" name="unavailableDuringInjestLabel"> <Component class="javax.swing.JLabel" name="unavailableDuringInjestLabel">
@ -165,5 +173,14 @@
</Property> </Property>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JSeparator" name="jSeparator1">
</Component>
<Component class="javax.swing.JCheckBox" name="groupCategorizationWarningBox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.groupCategorizationWarningBox.text" replaceFormat="NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents> </SubComponents>
</Form> </Form>

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-15 Basis Technology Corp. * Copyright 2013-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,17 +18,17 @@
*/ */
package org.sleuthkit.autopsy.imagegallery; package org.sleuthkit.autopsy.imagegallery;
import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestManager;
/** The Image/Video Gallery panel in the NetBeans provided Options Dialogs /**
* The Image/Video Gallery panel in the NetBeans provided Options Dialogs
* accessed via Tools -> Options * accessed via Tools -> Options
* *
* Uses {@link ImageGalleryPreferences} and {@link PerCaseProperties} to * Uses {@link ImageGalleryPreferences} and {@link PerCaseProperties} to persist
* persist settings * settings
*/ */
final class ImageGalleryOptionsPanel extends javax.swing.JPanel { final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
@ -50,10 +50,10 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
}); });
} }
/** This method is called from within the constructor to /**
* initialize the form. * This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is * WARNING: Do NOT modify this code. The content of this method is always
* always regenerated by the Form Editor. * regenerated by the Form Editor.
*/ */
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() { private void initComponents() {
@ -64,6 +64,8 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
furtherDescriptionArea = new javax.swing.JTextArea(); furtherDescriptionArea = new javax.swing.JTextArea();
infoIconLabel = new javax.swing.JLabel(); infoIconLabel = new javax.swing.JLabel();
unavailableDuringInjestLabel = new javax.swing.JLabel(); unavailableDuringInjestLabel = new javax.swing.JLabel();
jSeparator1 = new javax.swing.JSeparator();
groupCategorizationWarningBox = new javax.swing.JCheckBox();
setFont(getFont().deriveFont(getFont().getStyle() & ~java.awt.Font.BOLD, 11)); setFont(getFont().deriveFont(getFont().getStyle() & ~java.awt.Font.BOLD, 11));
@ -98,12 +100,13 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
infoIconLabel.setFont(infoIconLabel.getFont().deriveFont(infoIconLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); infoIconLabel.setFont(infoIconLabel.getFont().deriveFont(infoIconLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
infoIconLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/imagegallery/images/info-icon-16.png"))); // NOI18N infoIconLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/imagegallery/images/info-icon-16.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(infoIconLabel, NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.infoIconLabel.text")); // NOI18N
unavailableDuringInjestLabel.setFont(unavailableDuringInjestLabel.getFont().deriveFont(unavailableDuringInjestLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); unavailableDuringInjestLabel.setFont(unavailableDuringInjestLabel.getFont().deriveFont(unavailableDuringInjestLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
unavailableDuringInjestLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/imagegallery/images/warning16.png"))); // NOI18N unavailableDuringInjestLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/imagegallery/images/warning16.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(unavailableDuringInjestLabel, NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.unavailableDuringInjestLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(unavailableDuringInjestLabel, NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.unavailableDuringInjestLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(groupCategorizationWarningBox, NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.groupCategorizationWarningBox.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout); this.setLayout(layout);
layout.setHorizontalGroup( layout.setHorizontalGroup(
@ -112,18 +115,23 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
.addContainerGap() .addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addGap(21, 21, 21) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(unavailableDuringInjestLabel)) .addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addGap(21, 21, 21)
.addGroup(layout.createSequentialGroup() .addComponent(unavailableDuringInjestLabel))
.addGap(21, 21, 21) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(infoIconLabel) .addComponent(enabledByDefaultBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(enabledForCaseBox)
.addComponent(furtherDescriptionArea, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addComponent(descriptionLabel, javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(enabledByDefaultBox) .addGroup(layout.createSequentialGroup()
.addComponent(enabledForCaseBox) .addGap(21, 21, 21)
.addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) .addComponent(infoIconLabel)
.addContainerGap(46, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(furtherDescriptionArea, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
.addComponent(groupCategorizationWarningBox))
.addGap(0, 36, Short.MAX_VALUE))
.addComponent(jSeparator1))
.addContainerGap())
); );
layout.setVerticalGroup( layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@ -139,8 +147,12 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
.addGap(18, 18, 18) .addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(infoIconLabel) .addComponent(infoIconLabel)
.addComponent(furtherDescriptionArea, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addComponent(furtherDescriptionArea, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(45, 45, 45)) .addGap(18, 18, 18)
.addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, 10, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(groupCategorizationWarningBox)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
); );
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
@ -152,7 +164,6 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
// TODO add your handling code here: // TODO add your handling code here:
}//GEN-LAST:event_enabledForCaseBoxActionPerformed }//GEN-LAST:event_enabledForCaseBoxActionPerformed
/** {@inheritDoc} */
void load() { void load() {
enabledByDefaultBox.setSelected(ImageGalleryPreferences.isEnabledByDefault()); enabledByDefaultBox.setSelected(ImageGalleryPreferences.isEnabledByDefault());
if (Case.isCaseOpen() && IngestManager.getInstance().isIngestRunning() == false) { if (Case.isCaseOpen() && IngestManager.getInstance().isIngestRunning() == false) {
@ -162,20 +173,21 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
enabledForCaseBox.setEnabled(false); enabledForCaseBox.setEnabled(false);
enabledForCaseBox.setSelected(enabledByDefaultBox.isSelected()); enabledForCaseBox.setSelected(enabledByDefaultBox.isSelected());
} }
groupCategorizationWarningBox.setSelected(ImageGalleryPreferences.isGroupCategorizationWarningDisabled());
} }
/** {@inheritDoc } */
void store() { void store() {
ImageGalleryPreferences.setEnabledByDefault(enabledByDefaultBox.isSelected()); ImageGalleryPreferences.setEnabledByDefault(enabledByDefaultBox.isSelected());
ImageGalleryController.getDefault().setListeningEnabled(enabledForCaseBox.isSelected()); ImageGalleryController.getDefault().setListeningEnabled(enabledForCaseBox.isSelected());
if (Case.isCaseOpen()) { if (Case.isCaseOpen()) {
new PerCaseProperties(Case.getCurrentCase()).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.ENABLED, Boolean.toString(enabledForCaseBox.isSelected())); new PerCaseProperties(Case.getCurrentCase()).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.ENABLED, Boolean.toString(enabledForCaseBox.isSelected()));
} }
ImageGalleryPreferences.setGroupCategorizationWarningDisabled(groupCategorizationWarningBox.isSelected());
} }
/** {@inheritDoc } /**
* * @return true, since there is no way for this form to be invalid
* @return true, since there is no way for this form to be invalid */ */
boolean valid() { boolean valid() {
// TODO check whether form is consistent and complete // TODO check whether form is consistent and complete
return true; return true;
@ -186,7 +198,9 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
private javax.swing.JCheckBox enabledByDefaultBox; private javax.swing.JCheckBox enabledByDefaultBox;
private javax.swing.JCheckBox enabledForCaseBox; private javax.swing.JCheckBox enabledForCaseBox;
private javax.swing.JTextArea furtherDescriptionArea; private javax.swing.JTextArea furtherDescriptionArea;
private javax.swing.JCheckBox groupCategorizationWarningBox;
private javax.swing.JLabel infoIconLabel; private javax.swing.JLabel infoIconLabel;
private javax.swing.JSeparator jSeparator1;
private javax.swing.JLabel unavailableDuringInjestLabel; private javax.swing.JLabel unavailableDuringInjestLabel;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
} }

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013 Basis Technology Corp. * Copyright 2013-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -22,14 +22,21 @@ import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import org.openide.util.NbPreferences; import org.openide.util.NbPreferences;
/** Persists Image Analyzer preference to a per user .properties file */ /**
class ImageGalleryPreferences { * Persists Image Gallery preference to a per user .properties file
*/
public class ImageGalleryPreferences {
/** NBPreferences object used to persist settings */ /**
* NBPreferences object used to persist settings
*/
private static final Preferences preferences = NbPreferences.forModule(ImageGalleryPreferences.class); private static final Preferences preferences = NbPreferences.forModule(ImageGalleryPreferences.class);
/** key for the listening enabled for new cases setting */ /**
* key for the listening enabled for new cases setting
*/
private static final String ENABLED_BY_DEFAULT = "enabled_by_default"; //NON-NLS private static final String ENABLED_BY_DEFAULT = "enabled_by_default"; //NON-NLS
private static final String GROUP_CATEGORIZATION_WARNING_DISABLED = "group_categorization_warning_disabled"; //NON-NLS
/** /**
* Return setting of whether Image Analyzer should be automatically enabled * Return setting of whether Image Analyzer should be automatically enabled
@ -38,15 +45,30 @@ class ImageGalleryPreferences {
* *
* @return true if new cases should have image analyzer enabled. * @return true if new cases should have image analyzer enabled.
*/ */
static boolean isEnabledByDefault() { public static boolean isEnabledByDefault() {
final boolean aBoolean = preferences.getBoolean(ENABLED_BY_DEFAULT, true); final boolean aBoolean = preferences.getBoolean(ENABLED_BY_DEFAULT, true);
return aBoolean; return aBoolean;
} }
static void setEnabledByDefault(boolean b) { public static void setEnabledByDefault(boolean b) {
preferences.putBoolean(ENABLED_BY_DEFAULT, b); preferences.putBoolean(ENABLED_BY_DEFAULT, b);
} }
/**
* Return whether the warning about overwriting categories when acting on an
* entire group is disabled.
*
* @return true if the warning is disabled.
*/
public static boolean isGroupCategorizationWarningDisabled() {
final boolean aBoolean = preferences.getBoolean(GROUP_CATEGORIZATION_WARNING_DISABLED, false);
return aBoolean;
}
public static void setGroupCategorizationWarningDisabled(boolean b) {
preferences.putBoolean(GROUP_CATEGORIZATION_WARNING_DISABLED, b);
}
static void addChangeListener(PreferenceChangeListener l) { static void addChangeListener(PreferenceChangeListener l) {
preferences.addPreferenceChangeListener(l); preferences.addPreferenceChangeListener(l);
} }

View File

@ -148,7 +148,8 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
fullUIStack.getChildren().add(borderPane); fullUIStack.getChildren().add(borderPane);
splitPane = new SplitPane(); splitPane = new SplitPane();
borderPane.setCenter(splitPane); borderPane.setCenter(splitPane);
borderPane.setTop(Toolbar.getDefault(controller)); Toolbar toolbar = new Toolbar(controller);
borderPane.setTop(toolbar);
borderPane.setBottom(new StatusBar(controller)); borderPane.setBottom(new StatusBar(controller));
metaDataTable = new MetaDataPane(controller); metaDataTable = new MetaDataPane(controller);
@ -157,16 +158,18 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
hashHitList = new HashHitGroupList(controller); hashHitList = new HashHitGroupList(controller);
TabPane tabPane = new TabPane(groupTree, hashHitList); TabPane tabPane = new TabPane(groupTree, hashHitList);
tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE);
tabPane.setMinWidth(TabPane.USE_PREF_SIZE);
VBox.setVgrow(tabPane, Priority.ALWAYS); VBox.setVgrow(tabPane, Priority.ALWAYS);
leftPane = new VBox(tabPane, new SummaryTablePane(controller)); leftPane = new VBox(tabPane, new SummaryTablePane(controller));
SplitPane.setResizableWithParent(leftPane, Boolean.FALSE); SplitPane.setResizableWithParent(leftPane, Boolean.FALSE);
SplitPane.setResizableWithParent(groupPane, Boolean.TRUE); SplitPane.setResizableWithParent(groupPane, Boolean.TRUE);
SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE); SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE);
splitPane.getItems().addAll(leftPane, centralStack, metaDataTable); splitPane.getItems().addAll(leftPane, centralStack, metaDataTable);
splitPane.setDividerPositions(0.0, 1.0); splitPane.setDividerPositions(0.1, 1.0);
ImageGalleryController.getDefault().setStacks(fullUIStack, centralStack); ImageGalleryController.getDefault().setStacks(fullUIStack, centralStack);
ImageGalleryController.getDefault().setToolbar(toolbar);
ImageGalleryController.getDefault().setShowTree(() -> tabPane.getSelectionModel().select(groupTree)); ImageGalleryController.getDefault().setShowTree(() -> tabPane.getSelectionModel().select(groupTree));
}); });
} }

View File

@ -97,7 +97,7 @@ public enum ThumbnailCache {
* could not be generated * could not be generated
*/ */
@Nullable @Nullable
public Image get(DrawableFile<?> file) { public Image get(DrawableFile file) {
try { try {
return cache.get(file.getId(), () -> load(file)); return cache.get(file.getId(), () -> load(file));
} catch (UncheckedExecutionException | CacheLoader.InvalidCacheLoadException | ExecutionException ex) { } catch (UncheckedExecutionException | CacheLoader.InvalidCacheLoadException | ExecutionException ex) {
@ -124,9 +124,9 @@ public enum ThumbnailCache {
* *
* @return an (possibly empty) optional containing a thumbnail * @return an (possibly empty) optional containing a thumbnail
*/ */
private Image load(DrawableFile<?> file) { private Image load(DrawableFile file) {
if (FileTypeUtils.isGIF(file)) { if (FileTypeUtils.isGIF(file.getAbstractFile())) {
//directly read gif to preserve potential animation, //directly read gif to preserve potential animation,
//NOTE: not saved to disk! //NOTE: not saved to disk!
return new Image(new BufferedInputStream(new ReadContentInputStream(file.getAbstractFile())), MAX_THUMBNAIL_SIZE, MAX_THUMBNAIL_SIZE, true, true); return new Image(new BufferedInputStream(new ReadContentInputStream(file.getAbstractFile())), MAX_THUMBNAIL_SIZE, MAX_THUMBNAIL_SIZE, true, true);
@ -171,7 +171,7 @@ public enum ThumbnailCache {
* @return a Optional containing a File to store the cached icon in or an * @return a Optional containing a File to store the cached icon in or an
* empty optional if there was a problem. * empty optional if there was a problem.
*/ */
private static Optional<File> getCacheFile(DrawableFile<?> file) { private static Optional<File> getCacheFile(DrawableFile file) {
try { try {
return Optional.of(ImageUtils.getCachedThumbnailFile(file.getAbstractFile(), MAX_THUMBNAIL_SIZE)); return Optional.of(ImageUtils.getCachedThumbnailFile(file.getAbstractFile(), MAX_THUMBNAIL_SIZE));
@ -181,7 +181,7 @@ public enum ThumbnailCache {
} }
} }
public Task<Image> getThumbnailTask(DrawableFile<?> file) { public Task<Image> getThumbnailTask(DrawableFile file) {
final Image thumbnail = cache.getIfPresent(file.getId()); final Image thumbnail = cache.getIfPresent(file.getId());
if (thumbnail != null) { if (thumbnail != null) {
return TaskUtils.taskFrom(() -> thumbnail); return TaskUtils.taskFrom(() -> thumbnail);

View File

@ -1,127 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.actions;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.Menu;
import javax.swing.SwingWorker;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Instances of this Action allow users to apply tags to content.
*
* //TODO: since we are not using actionsGlobalContext anymore and this has
* diverged from autopsy action, make this extend from controlsfx Action
*/
public class AddDrawableTagAction extends AddTagAction {
private static final Logger LOGGER = Logger.getLogger(AddDrawableTagAction.class.getName());
private final ImageGalleryController controller;
public AddDrawableTagAction(ImageGalleryController controller) {
this.controller = controller;
}
public Menu getPopupMenu() {
return new TagMenu(controller);
}
@Override
@NbBundle.Messages({"AddDrawableTagAction.displayName.plural=Tag Files",
"AddDrawableTagAction.displayName.singular=Tag File"})
protected String getActionDisplayName() {
return Utilities.actionsGlobalContext().lookupAll(AbstractFile.class).size() > 1
? Bundle.AddDrawableTagAction_displayName_plural()
: Bundle.AddDrawableTagAction_displayName_singular();
}
@Override
public void addTag(TagName tagName, String comment) {
Set<Long> selectedFiles = new HashSet<>(controller.getSelectionModel().getSelected());
addTagsToFiles(tagName, comment, selectedFiles);
}
@Override
@NbBundle.Messages({"# {0} - fileID",
"AddDrawableTagAction.addTagsToFiles.alert=Unable to tag file {0}."})
public void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles) {
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
for (Long fileID : selectedFiles) {
try {
final DrawableFile<?> file = controller.getFileFromId(fileID);
LOGGER.log(Level.INFO, "tagging {0} with {1} and comment {2}", new Object[]{file.getName(), tagName.getDisplayName(), comment}); //NON-NLS
// check if the same tag is being added for the same abstract file.
DrawableTagsManager tagsManager = controller.getTagsManager();
List<ContentTag> contentTags = tagsManager.getContentTagsByContent(file);
Optional<TagName> duplicateTagName = contentTags.stream()
.map(ContentTag::getName)
.filter(tagName::equals)
.findAny();
if (duplicateTagName.isPresent()) {
LOGGER.log(Level.INFO, "{0} already tagged as {1}. Skipping.", new Object[]{file.getName(), tagName.getDisplayName()}); //NON-NLS
} else {
LOGGER.log(Level.INFO, "Tagging {0} as {1}", new Object[]{file.getName(), tagName.getDisplayName()}); //NON-NLS
controller.getTagsManager().addContentTag(file, tagName, comment);
}
} catch (TskCoreException tskCoreException) {
LOGGER.log(Level.SEVERE, "Error tagging file", tskCoreException); //NON-NLS
Platform.runLater(() -> {
new Alert(Alert.AlertType.ERROR, Bundle.AddDrawableTagAction_addTagsToFiles_alert(fileID)).show();
});
}
}
return null;
}
@Override
protected void done() {
super.done();
try {
get();
} catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.SEVERE, "unexpected exception while tagging files", ex); //NON-NLS
}
}
}.execute();
}
}

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013 Basis Technology Corp. * Copyright 2013-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -20,67 +20,128 @@ package org.sleuthkit.autopsy.imagegallery.actions;
import java.awt.Window; import java.awt.Window;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import javafx.event.ActionEvent; import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.collections.ObservableSet;
import javafx.scene.control.Alert;
import javafx.scene.control.Menu; import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.image.ImageView;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.windows.TopComponent; import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager; import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog; import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog;
import org.sleuthkit.autopsy.actions.GetTagNameDialog; import org.sleuthkit.autopsy.actions.GetTagNameDialog;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent; import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/** /**
* An abstract base class for actions that allow users to tag SleuthKit data * Instances of this Action allow users to apply tags to content.
* model objects.
*
* //TODO: this class started as a cut and paste from
* org.sleuthkit.autopsy.actions.AddTagAction and needs to be refactored or
* reintegrated to the AddTagAction hierarchy of Autopysy.
*/ */
abstract class AddTagAction { public class AddTagAction extends Action {
protected static final String NO_COMMENT = ""; private static final Logger LOGGER = Logger.getLogger(AddTagAction.class.getName());
/** private final ImageGalleryController controller;
* Template method to allow derived classes to provide a string for a menu private final Set<Long> selectedFileIDs;
* item label. private final TagName tagName;
*/
abstract protected String getActionDisplayName();
/** public AddTagAction(ImageGalleryController controller, TagName tagName, Set<Long> selectedFileIDs) {
* Template method to allow derived classes to add the indicated tag and super(tagName.getDisplayName());
* comment to one or more a SleuthKit data model objects. this.controller = controller;
*/ this.selectedFileIDs = selectedFileIDs;
abstract protected void addTag(TagName tagName, String comment); this.tagName = tagName;
setGraphic(controller.getTagsManager().getGraphic(tagName));
setText(tagName.getDisplayName());
setEventHandler(actionEvent -> addTagWithComment(""));
}
/** static public Menu getTagMenu(ImageGalleryController controller) {
* Template method to allow derived classes to add the indicated tag and return new TagMenu(controller);
* comment to a list of one or more file IDs. }
*/
abstract protected void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles); private void addTagWithComment(String comment) {
addTagsToFiles(tagName, comment, selectedFileIDs);
}
@NbBundle.Messages({"# {0} - fileID",
"AddDrawableTagAction.addTagsToFiles.alert=Unable to tag file {0}."})
private void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles) {
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
// check if the same tag is being added for the same abstract file.
DrawableTagsManager tagsManager = controller.getTagsManager();
for (Long fileID : selectedFiles) {
try {
final DrawableFile file = controller.getFileFromId(fileID);
LOGGER.log(Level.INFO, "tagging {0} with {1} and comment {2}", new Object[]{file.getName(), tagName.getDisplayName(), comment}); //NON-NLS
List<ContentTag> contentTags = tagsManager.getContentTags(file);
Optional<TagName> duplicateTagName = contentTags.stream()
.map(ContentTag::getName)
.filter(tagName::equals)
.findAny();
if (duplicateTagName.isPresent()) {
LOGGER.log(Level.INFO, "{0} already tagged as {1}. Skipping.", new Object[]{file.getName(), tagName.getDisplayName()}); //NON-NLS
} else {
LOGGER.log(Level.INFO, "Tagging {0} as {1}", new Object[]{file.getName(), tagName.getDisplayName()}); //NON-NLS
controller.getTagsManager().addContentTag(file, tagName, comment);
}
} catch (TskCoreException tskCoreException) {
LOGGER.log(Level.SEVERE, "Error tagging file", tskCoreException); //NON-NLS
Platform.runLater(() ->
new Alert(Alert.AlertType.ERROR, Bundle.AddDrawableTagAction_addTagsToFiles_alert(fileID)).show()
);
}
}
return null;
}
@Override
protected void done() {
super.done();
try {
get();
} catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.SEVERE, "unexpected exception while tagging files", ex); //NON-NLS
}
}
}.execute();
}
/**
* Instances of this class implement a context menu user interface for
* creating or selecting a tag name for a tag and specifying an optional tag
* comment.
*/
// @@@ This user interface has some significant usability issues and needs
// to be reworked.
@NbBundle.Messages({"AddTagAction.menuItem.quickTag=Quick Tag", @NbBundle.Messages({"AddTagAction.menuItem.quickTag=Quick Tag",
"AddTagAction.menuItem.noTags=No tags", "AddTagAction.menuItem.noTags=No tags",
"AddTagAction.menuItem.newTag=New Tag...", "AddTagAction.menuItem.newTag=New Tag...",
"AddTagAction.menuItem.tagAndComment=Tag and Comment..."}) "AddTagAction.menuItem.tagAndComment=Tag and Comment...",
protected class TagMenu extends Menu { "AddDrawableTagAction.displayName.plural=Tag Files",
"AddDrawableTagAction.displayName.singular=Tag File"})
private static class TagMenu extends Menu {
TagMenu(ImageGalleryController controller) { TagMenu(ImageGalleryController controller) {
super(getActionDisplayName()); setGraphic(new ImageView(DrawableAttribute.TAGS.getIcon()));
ObservableSet<Long> selectedFileIDs = controller.getSelectionModel().getSelected();
setText(selectedFileIDs.size() > 1
? Bundle.AddDrawableTagAction_displayName_plural()
: Bundle.AddDrawableTagAction_displayName_singular());
// Create a "Quick Tag" sub-menu. // Create a "Quick Tag" sub-menu.
Menu quickTagMenu = new Menu(Bundle.AddTagAction_menuItem_quickTag()); Menu quickTagMenu = new Menu(Bundle.AddTagAction_menuItem_quickTag());
@ -98,10 +159,8 @@ abstract class AddTagAction {
quickTagMenu.getItems().add(empty); quickTagMenu.getItems().add(empty);
} else { } else {
for (final TagName tagName : tagNames) { for (final TagName tagName : tagNames) {
MenuItem tagNameItem = new MenuItem(tagName.getDisplayName()); AddTagAction addDrawableTagAction = new AddTagAction(controller, tagName, selectedFileIDs);
tagNameItem.setOnAction((ActionEvent t) -> { MenuItem tagNameItem = ActionUtils.createMenuItem(addDrawableTagAction);
addTag(tagName, NO_COMMENT);
});
quickTagMenu.getItems().add(tagNameItem); quickTagMenu.getItems().add(tagNameItem);
} }
} }
@ -112,14 +171,13 @@ abstract class AddTagAction {
* or select a tag name and adds a tag with the resulting name. * or select a tag name and adds a tag with the resulting name.
*/ */
MenuItem newTagMenuItem = new MenuItem(Bundle.AddTagAction_menuItem_newTag()); MenuItem newTagMenuItem = new MenuItem(Bundle.AddTagAction_menuItem_newTag());
newTagMenuItem.setOnAction((ActionEvent t) -> { newTagMenuItem.setOnAction(actionEvent ->
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
TagName tagName = GetTagNameDialog.doDialog(getIGWindow()); TagName tagName = GetTagNameDialog.doDialog(getIGWindow());
if (tagName != null) { if (tagName != null) {
addTag(tagName, NO_COMMENT); new AddTagAction(controller, tagName, selectedFileIDs).handle(actionEvent);
} }
}); }));
});
quickTagMenu.getItems().add(newTagMenuItem); quickTagMenu.getItems().add(newTagMenuItem);
/* /*
@ -129,26 +187,17 @@ abstract class AddTagAction {
* name. * name.
*/ */
MenuItem tagAndCommentItem = new MenuItem(Bundle.AddTagAction_menuItem_tagAndComment()); MenuItem tagAndCommentItem = new MenuItem(Bundle.AddTagAction_menuItem_tagAndComment());
tagAndCommentItem.setOnAction((ActionEvent t) -> { tagAndCommentItem.setOnAction(actionEvent ->
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow()); GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow());
if (null != tagNameAndComment) { if (null != tagNameAndComment) {
if (CategoryManager.isCategoryTagName(tagNameAndComment.getTagName())) { new AddTagAction(controller, tagNameAndComment.getTagName(), selectedFileIDs).addTagWithComment(tagNameAndComment.getComment());
new CategorizeAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
} else {
new AddDrawableTagAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
} }
} }));
});
});
getItems().add(tagAndCommentItem); getItems().add(tagAndCommentItem);
} }
} }
/**
* @return the Window containing the ImageGalleryTopComponent
*/
static private Window getIGWindow() { static private Window getIGWindow() {
TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID); TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
return SwingUtilities.getWindowAncestor(etc); return SwingUtilities.getWindowAncestor(etc);

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013 Basis Technology Corp. * Copyright 2013-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -21,26 +21,28 @@ package org.sleuthkit.autopsy.imagegallery.actions;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javafx.event.ActionEvent; import javafx.collections.ObservableSet;
import javafx.scene.control.Menu; import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCodeCombination;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.ContentTag;
@ -49,48 +51,43 @@ import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
* Adaptation of Tag Actions to enforce category-tag uniqueness
* *
* TODO: since we are not using actionsGlobalContext anymore and this has
* diverged from autopsy action, make this extend from controlsfx Action
*/ */
@NbBundle.Messages({"CategorizeAction.displayName=Categorize"}) @NbBundle.Messages({"CategorizeAction.displayName=Categorize"})
public class CategorizeAction extends AddTagAction { public class CategorizeAction extends Action {
private static final Logger LOGGER = Logger.getLogger(CategorizeAction.class.getName()); private static final Logger LOGGER = Logger.getLogger(CategorizeAction.class.getName());
private final ImageGalleryController controller; private final ImageGalleryController controller;
private final UndoRedoManager undoManager; private final UndoRedoManager undoManager;
private final Category cat;
private final Set<Long> selectedFileIDs;
private final Boolean createUndo;
public CategorizeAction(ImageGalleryController controller) { public CategorizeAction(ImageGalleryController controller, Category cat, Set<Long> selectedFileIDs) {
super(); this(controller, cat, selectedFileIDs, true);
this.controller = controller;
undoManager = controller.getUndoManager();
} }
public Menu getPopupMenu() { private CategorizeAction(ImageGalleryController controller, Category cat, Set<Long> selectedFileIDs, Boolean createUndo) {
super(cat.getDisplayName());
this.controller = controller;
this.undoManager = controller.getUndoManager();
this.cat = cat;
this.selectedFileIDs = selectedFileIDs;
this.createUndo = createUndo;
setGraphic(cat.getGraphic());
setEventHandler(actionEvent -> addCatToFiles(selectedFileIDs));
setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber()))));
}
static public Menu getCategoriesMenu(ImageGalleryController controller) {
return new CategoryMenu(controller); return new CategoryMenu(controller);
} }
@Override
protected String getActionDisplayName() {
return Bundle.CategorizeAction_displayName();
}
@Override final void addCatToFiles(Set<Long> ids) {
public void addTag(TagName tagName, String comment) { Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{ids.toString(), cat.getDisplayName()}); //NON-NLS
Set<Long> selectedFiles = new HashSet<>(controller.getSelectionModel().getSelected()); controller.queueDBWorkerTask(new CategorizeTask(ids, cat, createUndo));
addTagsToFiles(tagName, comment, selectedFiles);
}
@Override
protected void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles) {
addTagsToFiles(tagName, comment, selectedFiles, true);
}
public void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles, boolean createUndo) {
Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{selectedFiles.toString(), tagName.getDisplayName()}); //NON-NLS
controller.queueDBWorkerTask(new CategorizeTask(selectedFiles, tagName, comment, createUndo));
} }
/** /**
@ -101,61 +98,55 @@ public class CategorizeAction extends AddTagAction {
CategoryMenu(ImageGalleryController controller) { CategoryMenu(ImageGalleryController controller) {
super(Bundle.CategorizeAction_displayName()); super(Bundle.CategorizeAction_displayName());
setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon()));
ObservableSet<Long> selected = controller.getSelectionModel().getSelected();
// Each category get an item in the sub-menu. Selecting one of these menu items adds // Each category get an item in the sub-menu. Selecting one of these menu items adds
// a tag with the associated category. // a tag with the associated category.
for (final Category cat : Category.values()) { for (final Category cat : Category.values()) {
MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, cat, selected));
MenuItem categoryItem = new MenuItem(cat.getDisplayName());
categoryItem.setOnAction((ActionEvent t) -> {
final CategorizeAction categorizeAction = new CategorizeAction(controller);
categorizeAction.addTag(controller.getCategoryManager().getTagName(cat), NO_COMMENT);
});
categoryItem.setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber()))));
getItems().add(categoryItem); getItems().add(categoryItem);
} }
} }
} }
@NbBundle.Messages({"# {0} - fileID number", @NbBundle.Messages({"# {0} - fileID number",
"CategorizeTask.errorUnable.msg=Unable to categorize {0}.", "CategorizeTask.errorUnable.msg=Unable to categorize {0}.",
"CategorizeTask.errorUnable.title=Categorizing Error"}) "CategorizeTask.errorUnable.title=Categorizing Error"})
private class CategorizeTask extends ImageGalleryController.InnerTask { private class CategorizeTask extends ImageGalleryController.BackgroundTask {
private final Set<Long> fileIDs; private final Set<Long> fileIDs;
@Nonnull
private final TagName tagName;
private final String comment;
private final boolean createUndo;
CategorizeTask(Set<Long> fileIDs, @Nonnull TagName tagName, String comment, boolean createUndo) { private final boolean createUndo;
private final Category cat;
CategorizeTask(Set<Long> fileIDs, @Nonnull Category cat, boolean createUndo) {
super(); super();
this.fileIDs = fileIDs; this.fileIDs = fileIDs;
java.util.Objects.requireNonNull(tagName); java.util.Objects.requireNonNull(cat);
this.tagName = tagName; this.cat = cat;
this.comment = comment;
this.createUndo = createUndo; this.createUndo = createUndo;
} }
@Override @Override
public void run() { public void run() {
final DrawableTagsManager tagsManager = controller.getTagsManager(); final DrawableTagsManager tagsManager = controller.getTagsManager();
final CategoryManager categoryManager = controller.getCategoryManager(); final CategoryManager categoryManager = controller.getCategoryManager();
Map<Long, TagName> oldCats = new HashMap<>(); Map<Long, Category> oldCats = new HashMap<>();
TagName tagName = categoryManager.getTagName(cat);
TagName catZeroTagName = categoryManager.getTagName(Category.ZERO);
for (long fileID : fileIDs) { for (long fileID : fileIDs) {
try { try {
DrawableFile<?> file = controller.getFileFromId(fileID); //drawable db access DrawableFile file = controller.getFileFromId(fileID); //drawable db access
if (createUndo) { if (createUndo) {
Category oldCat = file.getCategory(); //drawable db access Category oldCat = file.getCategory(); //drawable db access
TagName oldCatTagName = categoryManager.getTagName(oldCat); TagName oldCatTagName = categoryManager.getTagName(oldCat);
if (false == tagName.equals(oldCatTagName)) { if (false == tagName.equals(oldCatTagName)) {
oldCats.put(fileID, oldCatTagName); oldCats.put(fileID, oldCat);
} }
} }
final List<ContentTag> fileTags = tagsManager.getContentTagsByContent(file); final List<ContentTag> fileTags = tagsManager.getContentTags(file);
if (tagName == categoryManager.getTagName(Category.ZERO)) { if (tagName == categoryManager.getTagName(Category.ZERO)) {
// delete all cat tags for cat-0 // delete all cat tags for cat-0
fileTags.stream() fileTags.stream()
@ -173,7 +164,7 @@ public class CategorizeAction extends AddTagAction {
.map(Tag::getName) .map(Tag::getName)
.filter(tagName::equals) .filter(tagName::equals)
.collect(Collectors.toList()).isEmpty()) { .collect(Collectors.toList()).isEmpty()) {
tagsManager.addContentTag(file, tagName, comment); tagsManager.addContentTag(file, tagName, "");
} }
} }
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
@ -186,7 +177,7 @@ public class CategorizeAction extends AddTagAction {
} }
if (createUndo && oldCats.isEmpty() == false) { if (createUndo && oldCats.isEmpty() == false) {
undoManager.addToUndo(new CategorizationChange(controller, tagName, oldCats)); undoManager.addToUndo(new CategorizationChange(controller, cat, oldCats));
} }
} }
} }
@ -197,11 +188,11 @@ public class CategorizeAction extends AddTagAction {
@Immutable @Immutable
private final class CategorizationChange implements UndoRedoManager.UndoableCommand { private final class CategorizationChange implements UndoRedoManager.UndoableCommand {
private final TagName newCategory; private final Category newCategory;
private final ImmutableMap<Long, TagName> oldCategories; private final ImmutableMap<Long, Category> oldCategories;
private final ImageGalleryController controller; private final ImageGalleryController controller;
CategorizationChange(ImageGalleryController controller, TagName newCategory, Map<Long, TagName> oldCategories) { CategorizationChange(ImageGalleryController controller, Category newCategory, Map<Long, Category> oldCategories) {
this.controller = controller; this.controller = controller;
this.newCategory = newCategory; this.newCategory = newCategory;
this.oldCategories = ImmutableMap.copyOf(oldCategories); this.oldCategories = ImmutableMap.copyOf(oldCategories);
@ -213,8 +204,8 @@ public class CategorizeAction extends AddTagAction {
*/ */
@Override @Override
public void run() { public void run() {
CategorizeAction categorizeAction = new CategorizeAction(controller); new CategorizeAction(controller, newCategory, this.oldCategories.keySet(), false)
categorizeAction.addTagsToFiles(newCategory, "", this.oldCategories.keySet(), false); .handle(null);
} }
/** /**
@ -223,9 +214,10 @@ public class CategorizeAction extends AddTagAction {
*/ */
@Override @Override
public void undo() { public void undo() {
CategorizeAction categorizeAction = new CategorizeAction(controller);
for (Map.Entry<Long, TagName> entry : oldCategories.entrySet()) { for (Map.Entry<Long, Category> entry : oldCategories.entrySet()) {
categorizeAction.addTagsToFiles(entry.getValue(), "", Collections.singleton(entry.getKey()), false); new CategorizeAction(controller, entry.getValue(), Collections.singleton(entry.getKey()), false)
.handle(null);
} }
} }
} }

View File

@ -19,21 +19,109 @@
package org.sleuthkit.autopsy.imagegallery.actions; package org.sleuthkit.autopsy.imagegallery.actions;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import java.util.Set; import java.util.HashMap;
import org.controlsfx.control.action.Action; import java.util.Map;
import java.util.logging.Level;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import javafx.geometry.VPos;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryPreferences;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.datamodel.TskCoreException;
/** /**
* * An action that categorizes all the files in the currently active group with
* the given category.
*/ */
public class CategorizeGroupAction extends Action { public class CategorizeGroupAction extends CategorizeAction {
public CategorizeGroupAction(Category cat, ImageGalleryController controller) { private final static Logger LOGGER = Logger.getLogger(CategorizeGroupAction.class.getName());
super(cat.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> {
Set<Long> fileIdSet = ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs()); public CategorizeGroupAction(Category newCat, ImageGalleryController controller) {
new CategorizeAction(controller).addTagsToFiles(controller.getTagsManager().getTagName(cat), "", fileIdSet); super(controller, newCat, null);
setEventHandler(actionEvent -> {
ObservableList<Long> fileIDs = controller.viewState().get().getGroup().getFileIDs();
if (ImageGalleryPreferences.isGroupCategorizationWarningDisabled()) {
//if they have preveiously disabled the warning, just go ahead and apply categories.
addCatToFiles(ImmutableSet.copyOf(fileIDs));
} else {
final Map<Category, Long> catCountMap = new HashMap<>();
for (Long fileID : fileIDs) {
try {
Category category = controller.getFileFromId(fileID).getCategory();
if (false == Category.ZERO.equals(category) && newCat.equals(category) == false) {
catCountMap.merge(category, 1L, Long::sum);
}
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Failed to categorize files.", ex);
}
}
if (catCountMap.isEmpty()) {
//if there are not going to be any categories overwritten, skip the warning.
addCatToFiles(ImmutableSet.copyOf(fileIDs));
} else {
showConfirmationDialog(catCountMap, newCat, fileIDs);
}
}
}); });
setGraphic(cat.getGraphic()); }
@NbBundle.Messages({"CategorizeGroupAction.OverwriteButton.text=Overwrite",
"# {0} - number of files with the category", "# {1} - the name of the category",
"CategorizeGroupAction.fileCountMessage={0} with {1}",
"CategorizeGroupAction.dontShowAgain=Don't show this message again",
"CategorizeGroupAction.fileCountHeader=Files in the following categories will have their categories overwritten: "})
private void showConfirmationDialog(final Map<Category, Long> catCountMap, Category newCat, ObservableList<Long> fileIDs) {
ButtonType categorizeButtonType =
new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY);
VBox textFlow = new VBox();
for (Map.Entry<Category, Long> entry : catCountMap.entrySet()) {
if (entry.getKey().equals(newCat) == false) {
if (entry.getValue() > 0) {
Label label = new Label(Bundle.CategorizeGroupAction_fileCountMessage(entry.getValue(), entry.getKey().getDisplayName()),
entry.getKey().getGraphic());
label.setContentDisplay(ContentDisplay.RIGHT);
textFlow.getChildren().add(label);
}
}
}
CheckBox checkBox = new CheckBox(Bundle.CategorizeGroupAction_dontShowAgain());
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "", categorizeButtonType, ButtonType.CANCEL); //NON-NLS
Separator separator = new Separator(Orientation.HORIZONTAL);
separator.setPrefHeight(30);
separator.setValignment(VPos.BOTTOM);
VBox.setVgrow(separator, Priority.ALWAYS);
VBox vBox = new VBox(5, textFlow, separator, checkBox);
alert.getDialogPane().setContent(vBox);
alert.setHeaderText(Bundle.CategorizeGroupAction_fileCountHeader());
alert.showAndWait()
.filter(categorizeButtonType::equals)
.ifPresent(button -> {
//if they accept the overwrites, then apply them.
addCatToFiles(ImmutableSet.copyOf(fileIDs));
if (checkBox.isSelected()) {
//do we want to save this even on cancel also?
ImageGalleryPreferences.setGroupCategorizationWarningDisabled(true);
}
});
} }
} }

View File

@ -18,17 +18,18 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.actions; package org.sleuthkit.autopsy.imagegallery.actions;
import org.controlsfx.control.action.Action;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
/** /**
* *
*/ */
public class CategorizeSelectedFilesAction extends Action { public class CategorizeSelectedFilesAction extends CategorizeAction {
public CategorizeSelectedFilesAction(Category cat, ImageGalleryController controller) { public CategorizeSelectedFilesAction(Category cat, ImageGalleryController controller) {
super(cat.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), "")); super(controller, cat, null);
setGraphic(cat.getGraphic()); setEventHandler(actionEvent ->
addCatToFiles(controller.getSelectionModel().getSelected())
);
} }
} }

View File

@ -40,7 +40,7 @@ public class DeleteFollowUpTagAction extends Action {
private static final Logger LOGGER = Logger.getLogger(DeleteFollowUpTagAction.class.getName()); private static final Logger LOGGER = Logger.getLogger(DeleteFollowUpTagAction.class.getName());
@NbBundle.Messages("DeleteFollwUpTagAction.displayName=Delete Follow Up Tag") @NbBundle.Messages("DeleteFollwUpTagAction.displayName=Delete Follow Up Tag")
public DeleteFollowUpTagAction(final ImageGalleryController controller, final DrawableFile<?> file) { public DeleteFollowUpTagAction(final ImageGalleryController controller, final DrawableFile file) {
super(Bundle.DeleteFollwUpTagAction_displayName()); super(Bundle.DeleteFollwUpTagAction_displayName());
setEventHandler((ActionEvent t) -> { setEventHandler((ActionEvent t) -> {
new SwingWorker<Void, Void>() { new SwingWorker<Void, Void>() {
@ -52,7 +52,7 @@ public class DeleteFollowUpTagAction extends Action {
try { try {
final TagName followUpTagName = tagsManager.getFollowUpTagName(); final TagName followUpTagName = tagsManager.getFollowUpTagName();
List<ContentTag> contentTagsByContent = tagsManager.getContentTagsByContent(file); List<ContentTag> contentTagsByContent = tagsManager.getContentTags(file);
for (ContentTag ct : contentTagsByContent) { for (ContentTag ct : contentTagsByContent) {
if (ct.getName().getDisplayName().equals(followUpTagName.getDisplayName())) { if (ct.getName().getDisplayName().equals(followUpTagName.getDisplayName())) {
tagsManager.deleteContentTag(ct); tagsManager.deleteContentTag(ct);

View File

@ -39,7 +39,7 @@ public class OpenExternalViewerAction extends Action {
private static final Image EXTERNAL = new Image(OpenExternalViewerAction.class.getResource("/org/sleuthkit/autopsy/imagegallery/images/external.png").toExternalForm()); //NON-NLS private static final Image EXTERNAL = new Image(OpenExternalViewerAction.class.getResource("/org/sleuthkit/autopsy/imagegallery/images/external.png").toExternalForm()); //NON-NLS
private static final ActionEvent ACTION_EVENT = new ActionEvent(OpenExternalViewerAction.class, ActionEvent.ACTION_PERFORMED, ""); //Swing ActionEvent //NOI18N private static final ActionEvent ACTION_EVENT = new ActionEvent(OpenExternalViewerAction.class, ActionEvent.ACTION_PERFORMED, ""); //Swing ActionEvent //NOI18N
public OpenExternalViewerAction(DrawableFile<?> file) { public OpenExternalViewerAction(DrawableFile file) {
super(Bundle.OpenExternalViewerAction_displayName()); super(Bundle.OpenExternalViewerAction_displayName());
/** /**

View File

@ -19,21 +19,19 @@
package org.sleuthkit.autopsy.imagegallery.actions; package org.sleuthkit.autopsy.imagegallery.actions;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import java.util.Set;
import org.controlsfx.control.action.Action;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TagName;
/** /**
* *
*/ */
public class TagGroupAction extends Action { public class TagGroupAction extends AddTagAction {
public TagGroupAction(final TagName tagName, ImageGalleryController controller) { public TagGroupAction(final TagName tagName, ImageGalleryController controller) {
super(tagName.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> { super(controller, tagName, null);
Set<Long> fileIdSet = ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs()); setEventHandler(actionEvent ->
new AddDrawableTagAction(controller).addTagsToFiles(tagName, "", fileIdSet); new AddTagAction(controller, tagName, ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs())).
}); handle(actionEvent)
setGraphic(controller.getTagsManager().getGraphic(tagName)); );
} }
} }

View File

@ -18,17 +18,19 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.actions; package org.sleuthkit.autopsy.imagegallery.actions;
import org.controlsfx.control.action.Action;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TagName;
/** /**
* *
*/ */
public class TagSelectedFilesAction extends Action { public class TagSelectedFilesAction extends AddTagAction {
public TagSelectedFilesAction(final TagName tagName, ImageGalleryController controller) { public TagSelectedFilesAction(final TagName tagName, ImageGalleryController controller) {
super(tagName.getDisplayName(), actionEvent -> new AddDrawableTagAction(controller).addTag(tagName, "")); super(controller, tagName, null);
setGraphic(controller.getTagsManager().getGraphic(tagName)); setEventHandler(actionEvent ->
new AddTagAction(controller, tagName, controller.getSelectionModel().getSelected()).
handle(actionEvent)
);
} }
} }

View File

@ -25,6 +25,9 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Background; import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Border; import javafx.scene.layout.Border;
@ -94,6 +97,7 @@ public enum Category {
private final String displayName; private final String displayName;
private final int id; private final int id;
private Image snapshot;
private Category(Color color, int id, String name) { private Category(Color color, int id, String name) {
this.color = color; this.color = color;
@ -118,11 +122,15 @@ public enum Category {
return displayName; return displayName;
} }
public Node getGraphic() { synchronized public Node getGraphic() {
Region region = new Region(); if (snapshot == null) {
region.setBackground(new Background(new BackgroundFill(getColor(), CORNER_RADII_4, Insets.EMPTY))); Region region = new Region();
region.setPrefSize(16, 16); region.setBackground(new Background(new BackgroundFill(getColor(), CORNER_RADII_4, Insets.EMPTY)));
region.setBorder(new Border(new BorderStroke(getColor().darker(), BorderStrokeStyle.SOLID, CORNER_RADII_4, BORDER_WIDTHS_2))); region.setPrefSize(16, 16);
return region; region.setBorder(new Border(new BorderStroke(getColor().darker(), BorderStrokeStyle.SOLID, CORNER_RADII_4, BORDER_WIDTHS_2)));
Scene scene = new Scene(region, 16, 16, Color.TRANSPARENT);
snapshot = region.snapshot(null, null);
}
return new ImageView(snapshot);
} }
} }

View File

@ -248,7 +248,7 @@ public class CategoryManager {
final DrawableTagsManager tagsManager = controller.getTagsManager(); final DrawableTagsManager tagsManager = controller.getTagsManager();
try { try {
//remove old category tag(s) if necessary //remove old category tag(s) if necessary
for (ContentTag ct : tagsManager.getContentTagsByContent(addedTag.getContent())) { for (ContentTag ct : tagsManager.getContentTags(addedTag.getContent())) {
if (ct.getId() != addedTag.getId() if (ct.getId() != addedTag.getId()
&& CategoryManager.isCategoryTagName(ct.getName())) { && CategoryManager.isCategoryTagName(ct.getName())) {
try { try {

View File

@ -111,13 +111,13 @@ public class DrawableAttribute<T extends Comparable<T>> {
new DrawableAttribute<>(AttributeName.CREATED_TIME, Bundle.DrawableAttribute_createdTime(), new DrawableAttribute<>(AttributeName.CREATED_TIME, Bundle.DrawableAttribute_createdTime(),
true, true,
"clock--plus.png", //NON-NLS "clock--plus.png", //NON-NLS
f -> Collections.singleton(ContentUtils.getStringTime(f.getCrtime(), f))); f -> Collections.singleton(ContentUtils.getStringTime(f.getCrtime(), f.getAbstractFile())));
public final static DrawableAttribute<String> MODIFIED_TIME = public final static DrawableAttribute<String> MODIFIED_TIME =
new DrawableAttribute<>(AttributeName.MODIFIED_TIME, Bundle.DrawableAttribute_modifiedTime(), new DrawableAttribute<>(AttributeName.MODIFIED_TIME, Bundle.DrawableAttribute_modifiedTime(),
true, true,
"clock--pencil.png", //NON-NLS "clock--pencil.png", //NON-NLS
f -> Collections.singleton(ContentUtils.getStringTime(f.getMtime(), f))); f -> Collections.singleton(ContentUtils.getStringTime(f.getMtime(), f.getAbstractFile())));
public final static DrawableAttribute<String> MAKE = public final static DrawableAttribute<String> MAKE =
new DrawableAttribute<>(AttributeName.MAKE, Bundle.DrawableAttribute_cameraMake(), new DrawableAttribute<>(AttributeName.MAKE, Bundle.DrawableAttribute_cameraMake(),
@ -168,9 +168,9 @@ public class DrawableAttribute<T extends Comparable<T>> {
Arrays.asList(NAME, ANALYZED, CATEGORY, TAGS, PATH, CREATED_TIME, Arrays.asList(NAME, ANALYZED, CATEGORY, TAGS, PATH, CREATED_TIME,
MODIFIED_TIME, MD5_HASH, HASHSET, MAKE, MODEL, OBJ_ID, WIDTH, HEIGHT, MIME_TYPE); MODIFIED_TIME, MD5_HASH, HASHSET, MAKE, MODEL, OBJ_ID, WIDTH, HEIGHT, MIME_TYPE);
private final Function<DrawableFile<?>, Collection<T>> extractor; private final Function<DrawableFile, Collection<T>> extractor;
private DrawableAttribute(AttributeName name, String displayName, Boolean isDBColumn, String imageName, Function<DrawableFile<?>, Collection<T>> extractor) { private DrawableAttribute(AttributeName name, String displayName, Boolean isDBColumn, String imageName, Function<DrawableFile, Collection<T>> extractor) {
this.attrName = name; this.attrName = name;
this.displayName = new ReadOnlyStringWrapper(displayName); this.displayName = new ReadOnlyStringWrapper(displayName);
this.isDBColumn = isDBColumn; this.isDBColumn = isDBColumn;
@ -223,7 +223,7 @@ public class DrawableAttribute<T extends Comparable<T>> {
return displayName.get(); return displayName.get();
} }
public Collection<T> getValue(DrawableFile<?> f) { public Collection<T> getValue(DrawableFile f) {
return extractor.apply(f); return extractor.apply(f);
} }

View File

@ -497,9 +497,9 @@ public final class DrawableDB {
ArrayList<BlackboardArtifact> artifacts = tskCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, fileID); ArrayList<BlackboardArtifact> artifacts = tskCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, fileID);
for (BlackboardArtifact a : artifacts) { for (BlackboardArtifact a : artifacts) {
List<BlackboardAttribute> attributes = a.getAttributes(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME); BlackboardAttribute attribute = a.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
for (BlackboardAttribute attr : attributes) { if (attribute != null) {
hashNames.add(attr.getValueString()); hashNames.add(attribute.getValueString());
} }
} }
return Collections.unmodifiableSet(hashNames); return Collections.unmodifiableSet(hashNames);
@ -569,23 +569,23 @@ public final class DrawableDB {
return removeFile; return removeFile;
} }
public void updateFile(DrawableFile<?> f) { public void updateFile(DrawableFile f) {
DrawableTransaction trans = beginTransaction(); DrawableTransaction trans = beginTransaction();
updateFile(f, trans); updateFile(f, trans);
commitTransaction(trans, true); commitTransaction(trans, true);
} }
public void insertFile(DrawableFile<?> f) { public void insertFile(DrawableFile f) {
DrawableTransaction trans = beginTransaction(); DrawableTransaction trans = beginTransaction();
insertFile(f, trans); insertFile(f, trans);
commitTransaction(trans, true); commitTransaction(trans, true);
} }
public void insertFile(DrawableFile<?> f, DrawableTransaction tr) { public void insertFile(DrawableFile f, DrawableTransaction tr) {
insertOrUpdateFile(f, tr, insertFileStmt); insertOrUpdateFile(f, tr, insertFileStmt);
} }
public void updateFile(DrawableFile<?> f, DrawableTransaction tr) { public void updateFile(DrawableFile f, DrawableTransaction tr) {
insertOrUpdateFile(f, tr, updateFileStmt); insertOrUpdateFile(f, tr, updateFileStmt);
} }
@ -602,7 +602,7 @@ public final class DrawableDB {
* @param tr a transaction to use, must not be null * @param tr a transaction to use, must not be null
* @param stmt the statement that does the actull inserting * @param stmt the statement that does the actull inserting
*/ */
private void insertOrUpdateFile(DrawableFile<?> f, @Nonnull DrawableTransaction tr, @Nonnull PreparedStatement stmt) { private void insertOrUpdateFile(DrawableFile f, @Nonnull DrawableTransaction tr, @Nonnull PreparedStatement stmt) {
if (tr.isClosed()) { if (tr.isClosed()) {
throw new IllegalArgumentException("can't update database with closed transaction"); throw new IllegalArgumentException("can't update database with closed transaction");
@ -686,7 +686,7 @@ public final class DrawableDB {
tr.commit(notify); tr.commit(notify);
} }
public Boolean isFileAnalyzed(DrawableFile<?> f) { public Boolean isFileAnalyzed(DrawableFile f) {
return isFileAnalyzed(f.getId()); return isFileAnalyzed(f.getId());
} }
@ -903,16 +903,11 @@ public final class DrawableDB {
StringBuilder query = new StringBuilder("SELECT " + groupBy.attrName.toString() + ", COUNT(*) FROM drawable_files GROUP BY " + groupBy.attrName.toString()); //NON-NLS StringBuilder query = new StringBuilder("SELECT " + groupBy.attrName.toString() + ", COUNT(*) FROM drawable_files GROUP BY " + groupBy.attrName.toString()); //NON-NLS
String orderByClause = ""; String orderByClause = "";
switch (sortBy) {
case GROUP_BY_VALUE: if (sortBy == GROUP_BY_VALUE) {
orderByClause = " ORDER BY " + groupBy.attrName.toString(); //NON-NLS orderByClause = " ORDER BY " + groupBy.attrName.toString();
break; } else if (sortBy == GroupSortBy.FILE_COUNT) {
case FILE_COUNT: orderByClause = " ORDER BY COUNT(*)";
orderByClause = " ORDER BY COUNT(*)"; //NON-NLS
break;
case NONE:
// case PRIORITY:
break;
} }
query.append(orderByClause); query.append(orderByClause);
@ -984,7 +979,7 @@ public final class DrawableDB {
* @throws TskCoreException if unable to get a file from the currently open * @throws TskCoreException if unable to get a file from the currently open
* {@link SleuthkitCase} * {@link SleuthkitCase}
*/ */
private DrawableFile<?> getFileFromID(Long id, boolean analyzed) throws TskCoreException { private DrawableFile getFileFromID(Long id, boolean analyzed) throws TskCoreException {
try { try {
AbstractFile f = tskCase.getAbstractFileById(id); AbstractFile f = tskCase.getAbstractFileById(id);
return DrawableFile.create(f, analyzed, isVideoFile(f)); return DrawableFile.create(f, analyzed, isVideoFile(f));
@ -1002,7 +997,7 @@ public final class DrawableDB {
* @throws TskCoreException if unable to get a file from the currently open * @throws TskCoreException if unable to get a file from the currently open
* {@link SleuthkitCase} * {@link SleuthkitCase}
*/ */
public DrawableFile<?> getFileFromID(Long id) throws TskCoreException { public DrawableFile getFileFromID(Long id) throws TskCoreException {
try { try {
AbstractFile f = tskCase.getAbstractFileById(id); AbstractFile f = tskCase.getAbstractFileById(id);
return DrawableFile.create(f, return DrawableFile.create(f,
@ -1247,8 +1242,8 @@ public final class DrawableDB {
String fileIdsList = "(" + StringUtils.join(fileIDs, ",") + " )"; String fileIdsList = "(" + StringUtils.join(fileIDs, ",") + " )";
//count the fileids that are in the given list and don't have a non-zero category assigned to them. //count the fileids that are in the given list and don't have a non-zero category assigned to them.
String name = String name
"SELECT COUNT(obj_id) FROM tsk_files where obj_id IN " + fileIdsList //NON-NLS = "SELECT COUNT(obj_id) FROM tsk_files where obj_id IN " + fileIdsList //NON-NLS
+ " AND obj_id NOT IN (SELECT obj_id FROM content_tags WHERE content_tags.tag_name_id IN " + catTagNameIDs + ")"; //NON-NLS + " AND obj_id NOT IN (SELECT obj_id FROM content_tags WHERE content_tags.tag_name_id IN " + catTagNameIDs + ")"; //NON-NLS
try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(name); try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(name);
ResultSet resultSet = executeQuery.getResultSet();) { ResultSet resultSet = executeQuery.getResultSet();) {

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-15 Basis Technology Corp. * Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.imagegallery.datamodel; package org.sleuthkit.autopsy.imagegallery.datamodel;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -42,39 +43,36 @@ import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.ContentVisitor; import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.SleuthkitItemVisitor;
import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TagName; import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
* @TODO: There is something I don't understand or have done wrong about * A file that contains visual information such as an image or video.
* implementing this class,as it is unreadable by
* {@link ReadContentInputStream}. As a work around we keep a reference to the
* original {@link AbstractFile} to use when reading the image. -jm
*/ */
public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile { public abstract class DrawableFile {
private static final Logger LOGGER = Logger.getLogger(DrawableFile.class.getName()); private static final Logger LOGGER = Logger.getLogger(DrawableFile.class.getName());
public static DrawableFile<?> create(AbstractFile abstractFileById, boolean analyzed) { public static DrawableFile create(AbstractFile abstractFileById, boolean analyzed) {
return create(abstractFileById, analyzed, FileTypeUtils.isVideoFile(abstractFileById)); return create(abstractFileById, analyzed, FileTypeUtils.isVideoFile(abstractFileById));
} }
/** /**
* Skip the database query if we have already determined the file type. * Skip the database query if we have already determined the file type.
*/ */
public static DrawableFile<?> create(AbstractFile abstractFileById, boolean analyzed, boolean isVideo) { public static DrawableFile create(AbstractFile abstractFileById, boolean analyzed, boolean isVideo) {
return isVideo return isVideo
? new VideoFile<>(abstractFileById, analyzed) ? new VideoFile(abstractFileById, analyzed)
: new ImageFile<>(abstractFileById, analyzed); : new ImageFile(abstractFileById, analyzed);
} }
public static DrawableFile<?> create(Long id, boolean analyzed) throws TskCoreException, IllegalStateException { public static DrawableFile create(Long id, boolean analyzed) throws TskCoreException, IllegalStateException {
return create(Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(id), analyzed); return create(Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(id), analyzed);
} }
@ -82,7 +80,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
private String drawablePath; private String drawablePath;
private final T file; private final AbstractFile file;
private final SimpleBooleanProperty analyzed; private final SimpleBooleanProperty analyzed;
@ -92,89 +90,77 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
private String model; private String model;
protected DrawableFile(T file, Boolean analyzed) { protected DrawableFile(AbstractFile file, Boolean analyzed) {
/*
* @TODO: the two 'new Integer(0).shortValue()' values and null are
* placeholders because the super constructor expects values i can't get
* easily at the moment. I assume this is related to why
* ReadContentInputStream can't read from DrawableFiles.
*/
super(file.getSleuthkitCase(),
file.getId(),
file.getAttrType(),
file.getAttrId(),
file.getName(),
file.getType(),
file.getMetaAddr(),
(int) file.getMetaSeq(),
file.getDirType(),
file.getMetaType(),
null,
new Integer(0).shortValue(),
file.getSize(),
file.getCtime(),
file.getCrtime(),
file.getAtime(),
file.getMtime(),
new Integer(0).shortValue(),
file.getUid(),
file.getGid(),
file.getMd5Hash(),
file.getKnown(),
file.getParentPath(),
file.getMIMEType());
this.analyzed = new SimpleBooleanProperty(analyzed); this.analyzed = new SimpleBooleanProperty(analyzed);
this.file = file; this.file = file;
} }
public abstract boolean isVideo(); public abstract boolean isVideo();
@Override
public boolean isRoot() {
return false;
}
@Override
public <T> T accept(SleuthkitItemVisitor<T> v) {
return file.accept(v);
}
@Override
public <T> T accept(ContentVisitor<T> v) {
return file.accept(v);
}
@Override
public List<Content> getChildren() throws TskCoreException {
return new ArrayList<>();
}
@Override
public List<Long> getChildrenIds() throws TskCoreException {
return new ArrayList<>();
}
public List<Pair<DrawableAttribute<?>, Collection<?>>> getAttributesList() { public List<Pair<DrawableAttribute<?>, Collection<?>>> getAttributesList() {
return DrawableAttribute.getValues().stream() return DrawableAttribute.getValues().stream()
.map(this::makeAttributeValuePair) .map(this::makeAttributeValuePair)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public String getMIMEType() {
return file.getMIMEType();
}
public long getId() {
return file.getId();
}
public long getCtime() {
return file.getCtime();
}
public long getCrtime() {
return file.getCrtime();
}
public long getAtime() {
return file.getAtime();
}
public long getMtime() {
return file.getMtime();
}
public String getMd5Hash() {
return file.getMd5Hash();
}
public String getName() {
return file.getName();
}
public String getAtimeAsDate() {
return file.getAtimeAsDate();
}
public synchronized String getUniquePath() throws TskCoreException {
return file.getUniquePath();
}
public SleuthkitCase getSleuthkitCase() {
return file.getSleuthkitCase();
}
private Pair<DrawableAttribute<?>, Collection<?>> makeAttributeValuePair(DrawableAttribute<?> t) { private Pair<DrawableAttribute<?>, Collection<?>> makeAttributeValuePair(DrawableAttribute<?> t) {
return new Pair<>(t, t.getValue(DrawableFile.this)); return new Pair<>(t, t.getValue(DrawableFile.this));
} }
public String getModel() { public String getModel() {
if (model == null) { if (model == null) {
model = WordUtils.capitalizeFully((String) getValueOfBBAttribute(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL)); model = WordUtils.capitalizeFully((String) getValueOfBBAttribute(ARTIFACT_TYPE.TSK_METADATA_EXIF, ATTRIBUTE_TYPE.TSK_DEVICE_MODEL));
} }
return model; return model;
} }
public String getMake() { public String getMake() {
if (make == null) { if (make == null) {
make = WordUtils.capitalizeFully((String) getValueOfBBAttribute(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE)); make = WordUtils.capitalizeFully((String) getValueOfBBAttribute(ARTIFACT_TYPE.TSK_METADATA_EXIF, ATTRIBUTE_TYPE.TSK_DEVICE_MAKE));
} }
return make; return make;
} }
@ -182,18 +168,18 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
public Set<TagName> getTagNames() { public Set<TagName> getTagNames() {
try { try {
return getSleuthkitCase().getContentTagsByContent(this).stream() return getContentTags().stream()
.map(Tag::getName) .map(Tag::getName)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
Logger.getAnonymousLogger().log(Level.WARNING, "problem looking up " + DrawableAttribute.TAGS.getDisplayName() + " for " + file.getName(), ex); //NON-NLS Logger.getAnonymousLogger().log(Level.WARNING, "problem looking up " + DrawableAttribute.TAGS.getDisplayName() + " for " + file.getName(), ex); //NON-NLS
} catch (IllegalStateException ex) { } catch (IllegalStateException ex) {
Logger.getAnonymousLogger().log(Level.WARNING, "there is no case open; failed to look up " + DrawableAttribute.TAGS.getDisplayName() + " for " + file.getName()); //NON-NLS Logger.getAnonymousLogger().log(Level.WARNING, "there is no case open; failed to look up " + DrawableAttribute.TAGS.getDisplayName() + " for " + getContentPathSafe(), ex); //NON-NLS
} }
return Collections.emptySet(); return Collections.emptySet();
} }
protected Object getValueOfBBAttribute(BlackboardArtifact.ARTIFACT_TYPE artType, BlackboardAttribute.ATTRIBUTE_TYPE attrType) { protected Object getValueOfBBAttribute(ARTIFACT_TYPE artType, ATTRIBUTE_TYPE attrType) {
try { try {
//why doesn't file.getArtifacts() work? //why doesn't file.getArtifacts() work?
@ -223,7 +209,8 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
} }
} }
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
Logger.getAnonymousLogger().log(Level.WARNING, "problem looking up {0}/{1}" + " " + " for {2}", new Object[]{artType.getDisplayName(), attrType.getDisplayName(), getName()}); //NON-NLS Logger.getAnonymousLogger().log(Level.WARNING, ex,
() -> MessageFormat.format("problem looking up {0}/{1}" + " " + " for {2}", new Object[]{artType.getDisplayName(), attrType.getDisplayName(), getContentPathSafe()})); //NON-NLS
} }
return ""; return "";
} }
@ -247,7 +234,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
*/ */
private void updateCategory() { private void updateCategory() {
try { try {
category.set(getSleuthkitCase().getContentTagsByContent(this).stream() category.set(getContentTags().stream()
.map(Tag::getName).filter(CategoryManager::isCategoryTagName) .map(Tag::getName).filter(CategoryManager::isCategoryTagName)
.map(TagName::getDisplayName) .map(TagName::getDisplayName)
.map(Category::fromDisplayName) .map(Category::fromDisplayName)
@ -255,12 +242,16 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
.orElse(Category.ZERO) .orElse(Category.ZERO)
); );
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "problem looking up category for file " + this.getName() + ex.getLocalizedMessage()); //NON-NLS LOGGER.log(Level.WARNING, "problem looking up category for " + this.getContentPathSafe(), ex); //NON-NLS
} catch (IllegalStateException ex) { } catch (IllegalStateException ex) {
// We get here many times if the case is closed during ingest, so don't print out a ton of warnings. // We get here many times if the case is closed during ingest, so don't print out a ton of warnings.
} }
} }
private List<ContentTag> getContentTags() throws TskCoreException {
return getSleuthkitCase().getContentTagsByContent(file);
}
@Deprecated @Deprecated
public Image getThumbnail() { public Image getThumbnail() {
try { try {
@ -317,7 +308,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
return analyzed.get(); return analyzed.get();
} }
public T getAbstractFile() { public AbstractFile getAbstractFile() {
return this.file; return this.file;
} }
@ -333,12 +324,16 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
drawablePath = StringUtils.removeEnd(getUniquePath(), getName()); drawablePath = StringUtils.removeEnd(getUniquePath(), getName());
return drawablePath; return drawablePath;
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "failed to get drawablePath from {0}", getName()); //NON-NLS LOGGER.log(Level.WARNING, "failed to get drawablePath from " + getContentPathSafe(), ex); //NON-NLS
return ""; return "";
} }
} }
} }
public Set<String> getHashSetNames() throws TskCoreException {
return file.getHashSetNames();
}
@Nonnull @Nonnull
public Set<String> getHashSetNamesUnchecked() { public Set<String> getHashSetNamesUnchecked() {
try { try {
@ -359,7 +354,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
*/ */
public String getContentPathSafe() { public String getContentPathSafe() {
try { try {
return this.getUniquePath(); return getUniquePath();
} catch (TskCoreException tskCoreException) { } catch (TskCoreException tskCoreException) {
String contentName = this.getName(); String contentName = this.getName();
LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NOI18N NON-NLS LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NOI18N NON-NLS

View File

@ -45,13 +45,24 @@ import org.sleuthkit.datamodel.TskCoreException;
* Manages Tags, Tagging, and the relationship between Categories and Tags in * Manages Tags, Tagging, and the relationship between Categories and Tags in
* the autopsy Db. Delegates some work to the backing {@link TagsManager}. * the autopsy Db. Delegates some work to the backing {@link TagsManager}.
*/ */
@NbBundle.Messages({"DrawableTagsManager.followUp=Follow Up"}) @NbBundle.Messages({"DrawableTagsManager.followUp=Follow Up",
"DrawableTagsManager.bookMark=Bookmark"})
public class DrawableTagsManager { public class DrawableTagsManager {
private static final Logger LOGGER = Logger.getLogger(DrawableTagsManager.class.getName()); private static final Logger LOGGER = Logger.getLogger(DrawableTagsManager.class.getName());
private static final String FOLLOW_UP = Bundle.DrawableTagsManager_followUp(); private static final String FOLLOW_UP = Bundle.DrawableTagsManager_followUp();
private static final String BOOKMARK = Bundle.DrawableTagsManager_bookMark();
private static Image FOLLOW_UP_IMAGE; private static Image FOLLOW_UP_IMAGE;
private static Image BOOKMARK_IMAGE;
public static String getFollowUpText() {
return FOLLOW_UP;
}
public static String getBookmarkText() {
return BOOKMARK;
}
final private Object autopsyTagsManagerLock = new Object(); final private Object autopsyTagsManagerLock = new Object();
private TagsManager autopsyTagsManager; private TagsManager autopsyTagsManager;
@ -70,6 +81,7 @@ public class DrawableTagsManager {
* The tag name corresponding to the "built-in" tag "Follow Up" * The tag name corresponding to the "built-in" tag "Follow Up"
*/ */
private TagName followUpTagName; private TagName followUpTagName;
private TagName bookmarkTagName;
public DrawableTagsManager(TagsManager autopsyTagsManager) { public DrawableTagsManager(TagsManager autopsyTagsManager) {
this.autopsyTagsManager = autopsyTagsManager; this.autopsyTagsManager = autopsyTagsManager;
@ -141,6 +153,15 @@ public class DrawableTagsManager {
} }
} }
private Object getBookmarkTagName() throws TskCoreException {
synchronized (autopsyTagsManagerLock) {
if (Objects.isNull(bookmarkTagName)) {
bookmarkTagName = getTagName(BOOKMARK);
}
return bookmarkTagName;
}
}
/** /**
* get all the TagNames that are not categories * get all the TagNames that are not categories
* *
@ -164,21 +185,35 @@ public class DrawableTagsManager {
} }
/** /**
* Gets content tags count by content. * Gets content tags by content.
* *
* @param The content of interest. * @param content The content of interest.
* *
* @return A list, possibly empty, of the tags that have been applied to the * @return A list, possibly empty, of the tags that have been applied to the
* artifact. * content.
* *
* @throws TskCoreException * @throws TskCoreException if there was an error reading from the db
*/ */
public List<ContentTag> getContentTagsByContent(Content content) throws TskCoreException { public List<ContentTag> getContentTags(Content content) throws TskCoreException {
synchronized (autopsyTagsManagerLock) { synchronized (autopsyTagsManagerLock) {
return autopsyTagsManager.getContentTagsByContent(content); return autopsyTagsManager.getContentTagsByContent(content);
} }
} }
/**
* Gets content tags by DrawableFile.
*
* @param drawable The DrawableFile of interest.
*
* @return A list, possibly empty, of the tags that have been applied to the
* DrawableFile.
*
* @throws TskCoreException if there was an error reading from the db
*/
public List<ContentTag> getContentTags(DrawableFile drawable) throws TskCoreException {
return getContentTags(drawable.getAbstractFile());
}
public TagName getTagName(String displayName) throws TskCoreException { public TagName getTagName(String displayName) throws TskCoreException {
synchronized (autopsyTagsManagerLock) { synchronized (autopsyTagsManagerLock) {
try { try {
@ -192,7 +227,7 @@ public class DrawableTagsManager {
} catch (TagsManager.TagNameAlreadyExistsException ex) { } catch (TagsManager.TagNameAlreadyExistsException ex) {
throw new TskCoreException("tagame exists but wasn't found", ex); throw new TskCoreException("tagame exists but wasn't found", ex);
} }
} catch (IllegalStateException ex) { } catch (NullPointerException | IllegalStateException ex) {
LOGGER.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS LOGGER.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS
throw new TskCoreException("Case was closed out from underneath", ex); throw new TskCoreException("Case was closed out from underneath", ex);
} }
@ -207,7 +242,7 @@ public class DrawableTagsManager {
} }
} }
public ContentTag addContentTag(DrawableFile<?> file, TagName tagName, String comment) throws TskCoreException { public ContentTag addContentTag(DrawableFile file, TagName tagName, String comment) throws TskCoreException {
synchronized (autopsyTagsManagerLock) { synchronized (autopsyTagsManagerLock) {
return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment); return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment);
} }
@ -241,9 +276,11 @@ public class DrawableTagsManager {
try { try {
if (tagname.equals(getFollowUpTagName())) { if (tagname.equals(getFollowUpTagName())) {
return new ImageView(getFollowUpImage()); return new ImageView(getFollowUpImage());
} else if (tagname.equals(getBookmarkTagName())) {
return new ImageView(getBookmarkImage());
} }
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Failed to get \"Follow Up\" tag name from db.", ex); LOGGER.log(Level.SEVERE, "Failed to get \"Follow Up\" or \"Bookmark\"tag name from db.", ex);
} }
return DrawableAttribute.TAGS.getGraphicForValue(tagname); return DrawableAttribute.TAGS.getGraphicForValue(tagname);
} }
@ -254,4 +291,12 @@ public class DrawableTagsManager {
} }
return FOLLOW_UP_IMAGE; return FOLLOW_UP_IMAGE;
} }
synchronized private static Image getBookmarkImage() {
if (BOOKMARK_IMAGE == null) {
BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png");
}
return BOOKMARK_IMAGE;
}
} }

View File

@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.imagegallery.datamodel;
import java.io.IOException; import java.io.IOException;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javax.imageio.ImageIO;
import org.sleuthkit.autopsy.coreutils.ImageUtils; import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
@ -31,17 +30,12 @@ import org.sleuthkit.datamodel.AbstractFile;
* wrapper(/decorator?/adapter?) around {@link AbstractFile} and provides * wrapper(/decorator?/adapter?) around {@link AbstractFile} and provides
* methods to get an thumbnail sized and a full sized {@link Image}. * methods to get an thumbnail sized and a full sized {@link Image}.
*/ */
public class ImageFile<T extends AbstractFile> extends DrawableFile<T> { public class ImageFile extends DrawableFile {
private static final Logger LOGGER = Logger.getLogger(ImageFile.class.getName()); private static final Logger LOGGER = Logger.getLogger(ImageFile.class.getName());
static { ImageFile(AbstractFile f, Boolean analyzed) {
ImageIO.scanForPlugins();
}
ImageFile(T f, Boolean analyzed) {
super(f, analyzed); super(f, analyzed);
} }
@Override @Override

View File

@ -36,13 +36,13 @@ import org.sleuthkit.autopsy.coreutils.VideoUtils;
import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
public class VideoFile<T extends AbstractFile> extends DrawableFile<T> { public class VideoFile extends DrawableFile {
private static final Logger LOGGER = Logger.getLogger(VideoFile.class.getName()); private static final Logger LOGGER = Logger.getLogger(VideoFile.class.getName());
private static final Image VIDEO_ICON = new Image("org/sleuthkit/autopsy/imagegallery/images/Clapperboard.png"); //NON-NLS private static final Image VIDEO_ICON = new Image("org/sleuthkit/autopsy/imagegallery/images/Clapperboard.png"); //NON-NLS
VideoFile(T file, Boolean analyzed) { VideoFile(AbstractFile file, Boolean analyzed) {
super(file, analyzed); super(file, analyzed);
} }

View File

@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -126,7 +127,7 @@ public class GroupManager {
private volatile DrawableAttribute<?> groupBy = DrawableAttribute.PATH; private volatile DrawableAttribute<?> groupBy = DrawableAttribute.PATH;
private volatile SortOrder sortOrder = SortOrder.ASCENDING; private volatile SortOrder sortOrder = SortOrder.ASCENDING;
private final ReadOnlyObjectWrapper<GroupSortBy> sortByProp = new ReadOnlyObjectWrapper<>(sortBy); private final ReadOnlyObjectWrapper< Comparator<DrawableGroup>> sortByProp = new ReadOnlyObjectWrapper<>(sortBy);
private final ReadOnlyObjectWrapper< DrawableAttribute<?>> groupByProp = new ReadOnlyObjectWrapper<>(groupBy); private final ReadOnlyObjectWrapper< DrawableAttribute<?>> groupByProp = new ReadOnlyObjectWrapper<>(groupBy);
private final ReadOnlyObjectWrapper<SortOrder> sortOrderProp = new ReadOnlyObjectWrapper<>(sortOrder); private final ReadOnlyObjectWrapper<SortOrder> sortOrderProp = new ReadOnlyObjectWrapper<>(sortOrder);
@ -169,7 +170,7 @@ public class GroupManager {
* file is a part of * file is a part of
*/ */
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
synchronized public Set<GroupKey<?>> getGroupKeysForFile(DrawableFile<?> file) { synchronized public Set<GroupKey<?>> getGroupKeysForFile(DrawableFile file) {
Set<GroupKey<?>> resultSet = new HashSet<>(); Set<GroupKey<?>> resultSet = new HashSet<>();
for (Comparable<?> val : groupBy.getValue(file)) { for (Comparable<?> val : groupBy.getValue(file)) {
if (groupBy == DrawableAttribute.TAGS) { if (groupBy == DrawableAttribute.TAGS) {
@ -193,7 +194,7 @@ public class GroupManager {
synchronized public Set<GroupKey<?>> getGroupKeysForFileID(Long fileID) { synchronized public Set<GroupKey<?>> getGroupKeysForFileID(Long fileID) {
try { try {
if (nonNull(db)) { if (nonNull(db)) {
DrawableFile<?> file = db.getFileFromID(fileID); DrawableFile file = db.getFileFromID(fileID);
return getGroupKeysForFile(file); return getGroupKeysForFile(file);
} else { } else {
Logger.getLogger(GroupManager.class.getName()).log(Level.WARNING, "Failed to load file with id: {0} from database. There is no database assigned.", fileID); //NON-NLS Logger.getLogger(GroupManager.class.getName()).log(Level.WARNING, "Failed to load file with id: {0} from database. There is no database assigned.", fileID); //NON-NLS
@ -274,7 +275,7 @@ public class GroupManager {
} else if (unSeenGroups.contains(group) == false) { } else if (unSeenGroups.contains(group) == false) {
unSeenGroups.add(group); unSeenGroups.add(group);
} }
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder)); FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy));
} }
/** /**
@ -299,11 +300,11 @@ public class GroupManager {
Platform.runLater(() -> { Platform.runLater(() -> {
if (analyzedGroups.contains(group)) { if (analyzedGroups.contains(group)) {
analyzedGroups.remove(group); analyzedGroups.remove(group);
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder)); FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy));
} }
if (unSeenGroups.contains(group)) { if (unSeenGroups.contains(group)) {
unSeenGroups.remove(group); unSeenGroups.remove(group);
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder)); FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy));
} }
}); });
} }
@ -450,7 +451,7 @@ public class GroupManager {
} }
} }
public GroupSortBy getSortBy() { public Comparator<DrawableGroup> getSortBy() {
return sortBy; return sortBy;
} }
@ -459,7 +460,7 @@ public class GroupManager {
Platform.runLater(() -> sortByProp.set(sortBy)); Platform.runLater(() -> sortByProp.set(sortBy));
} }
public ReadOnlyObjectProperty<GroupSortBy> getSortByProperty() { public ReadOnlyObjectProperty< Comparator<DrawableGroup>> getSortByProperty() {
return sortByProp.getReadOnlyProperty(); return sortByProp.getReadOnlyProperty();
} }
@ -523,8 +524,8 @@ public class GroupManager {
setSortBy(sortBy); setSortBy(sortBy);
setSortOrder(sortOrder); setSortOrder(sortOrder);
Platform.runLater(() -> { Platform.runLater(() -> {
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder)); FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy));
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder)); FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy));
}); });
} }
} }
@ -666,7 +667,7 @@ public class GroupManager {
group = new DrawableGroup(groupKey, fileIDs, groupSeen); group = new DrawableGroup(groupKey, fileIDs, groupSeen);
controller.getCategoryManager().registerListener(group); controller.getCategoryManager().registerListener(group);
group.seenProperty().addListener((o, oldSeen, newSeen) -> { group.seenProperty().addListener((o, oldSeen, newSeen) -> {
markGroupSeen(group, newSeen); Platform.runLater(() -> markGroupSeen(group, newSeen));
}); });
groupMap.put(groupKey, group); groupMap.put(groupKey, group);
} }
@ -675,7 +676,7 @@ public class GroupManager {
if (analyzedGroups.contains(group) == false) { if (analyzedGroups.contains(group) == false) {
analyzedGroups.add(group); analyzedGroups.add(group);
if (Objects.isNull(task)) { if (Objects.isNull(task)) {
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder)); FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy));
} }
} }
markGroupSeen(group, groupSeen); markGroupSeen(group, groupSeen);
@ -719,12 +720,12 @@ public class GroupManager {
*/ */
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes"})
@NbBundle.Messages({"# {0} - groupBy attribute Name", @NbBundle.Messages({"# {0} - groupBy attribute Name",
"# {1} - sortBy name", "# {1} - sortBy name",
"# {2} - sort Order", "# {2} - sort Order",
"ReGroupTask.displayTitle=regrouping files by {0} sorted by {1} in {2} order", "ReGroupTask.displayTitle=regrouping files by {0} sorted by {1} in {2} order",
"# {0} - groupBy attribute Name", "# {0} - groupBy attribute Name",
"# {1} - atribute value", "# {1} - atribute value",
"ReGroupTask.progressUpdate=regrouping files by {0} : {1}"}) "ReGroupTask.progressUpdate=regrouping files by {0} : {1}"})
private class ReGroupTask<A extends Comparable<A>> extends LoggedTask<Void> { private class ReGroupTask<A extends Comparable<A>> extends LoggedTask<Void> {
private ProgressHandle groupProgress; private ProgressHandle groupProgress;
@ -735,8 +736,8 @@ public class GroupManager {
private final SortOrder sortOrder; private final SortOrder sortOrder;
public ReGroupTask(DrawableAttribute<A> groupBy, GroupSortBy sortBy, SortOrder sortOrder) { ReGroupTask(DrawableAttribute<A> groupBy, GroupSortBy sortBy, SortOrder sortOrder) {
super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.name(), sortOrder.toString()), true); super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), true);
this.groupBy = groupBy; this.groupBy = groupBy;
this.sortBy = sortBy; this.sortBy = sortBy;
@ -755,7 +756,7 @@ public class GroupManager {
return null; return null;
} }
groupProgress = ProgressHandleFactory.createHandle(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.name(), sortOrder.toString()), this); groupProgress = ProgressHandleFactory.createHandle(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), this);
Platform.runLater(() -> { Platform.runLater(() -> {
analyzedGroups.clear(); analyzedGroups.clear();
unSeenGroups.clear(); unSeenGroups.clear();
@ -778,7 +779,7 @@ public class GroupManager {
groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p); groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p);
popuplateIfAnalyzed(new GroupKey<A>(groupBy, val), this); popuplateIfAnalyzed(new GroupKey<A>(groupBy, val), this);
} }
Platform.runLater(() -> FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder))); Platform.runLater(() -> FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy)));
updateProgress(1, 1); updateProgress(1, 1);
return null; return null;
@ -793,4 +794,16 @@ public class GroupManager {
} }
} }
} }
private static <T> Comparator<T> applySortOrder(final SortOrder sortOrder, Comparator<T> comparator) {
switch (sortOrder) {
case ASCENDING:
return comparator;
case DESCENDING:
return comparator.reversed();
case UNSORTED:
default:
return new GroupSortBy.AllEqualComparator<>();
}
}
} }

View File

@ -18,89 +18,50 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping; package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javax.swing.SortOrder;
import static javax.swing.SortOrder.ASCENDING;
import static javax.swing.SortOrder.DESCENDING;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
/** /**
* enum of possible properties to sort groups by. This is the model for the drop * Pseudo enum of possible properties to sort groups by.
* down in Toolbar as well as each enum value having the stategy
* ({@link Comparator}) for sorting the groups
*/ */
@NbBundle.Messages({"GroupSortBy.groupSize=Group Size", @NbBundle.Messages({"GroupSortBy.groupSize=Group Size",
"GroupSortBy.groupName=Group Name", "GroupSortBy.groupName=Group Name",
"GroupSortBy.none=None", "GroupSortBy.none=None",
"GroupSortBy.priority=Priority"}) "GroupSortBy.priority=Priority"})
public enum GroupSortBy implements ComparatorProvider { public class GroupSortBy implements Comparator<DrawableGroup> {
/** /**
* sort the groups by the number of files in each sort the groups by the * sort the groups by the number of files in each
* number of files in each
*/ */
FILE_COUNT(Bundle.GroupSortBy_groupSize(), true, "folder-open-image.png") { //NON-NLS public final static GroupSortBy FILE_COUNT = new GroupSortBy(Bundle.GroupSortBy_groupSize(), "folder-open-image.png", Comparator.comparing(DrawableGroup::getSize));
@Override
public Comparator<DrawableGroup> getGrpComparator(final SortOrder sortOrder) {
return applySortOrder(sortOrder, Comparator.comparingInt(DrawableGroup::getSize));
}
@Override
public <A extends Comparable<A>> Comparator<A> getValueComparator(final DrawableAttribute<A> attr, final SortOrder sortOrder) {
return getDefaultValueComparator(attr, sortOrder);
}
},
/** /**
* sort the groups by the natural order of the grouping value ( eg group * sort the groups by the natural order of the grouping value ( eg group
* them by path alphabetically ) * them by path alphabetically )
*/ */
GROUP_BY_VALUE(Bundle.GroupSortBy_groupName(), true, "folder-rename.png") { //NON-NLS public final static GroupSortBy GROUP_BY_VALUE = new GroupSortBy(Bundle.GroupSortBy_groupName(), "folder-rename.png", Comparator.comparing(DrawableGroup::getGroupByValueDislpayName));
@Override
public Comparator<DrawableGroup> getGrpComparator(final SortOrder sortOrder) {
return applySortOrder(sortOrder, Comparator.comparing(t -> t.getGroupByValueDislpayName()));
}
@Override
public <A extends Comparable<A>> Comparator<A> getValueComparator(final DrawableAttribute<A> attr, final SortOrder sortOrder) {
return applySortOrder(sortOrder, Comparator.<A>naturalOrder());
}
},
/** /**
* don't sort the groups just use what ever order they come in (ingest * don't sort the groups just use what ever order they come in (ingest
* order) * order)
*/ */
NONE(Bundle.GroupSortBy_none(), false, "prohibition.png") { //NON-NLS public final static GroupSortBy NONE = new GroupSortBy(Bundle.GroupSortBy_none(), "prohibition.png", new AllEqualComparator<>());
@Override
public Comparator<DrawableGroup> getGrpComparator(SortOrder sortOrder) {
return new NoOpComparator<>();
}
@Override
public <A extends Comparable<A>> Comparator<A> getValueComparator(DrawableAttribute<A> attr, final SortOrder sortOrder) {
return new NoOpComparator<>();
}
},
/** /**
* sort the groups by some priority metric to be determined and implemented * sort the groups by some priority metric to be determined and implemented
*/ */
PRIORITY(Bundle.GroupSortBy_priority(), false, "hashset_hits.png") { //NON-NLS public final static GroupSortBy PRIORITY = new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png", Comparator.comparing(DrawableGroup::getHashHitDensity).thenComparing(Comparator.comparing(DrawableGroup::getUncategorizedCount)));
@Override
public Comparator<DrawableGroup> getGrpComparator(SortOrder sortOrder) {
return Comparator.nullsLast(Comparator.comparingDouble(DrawableGroup::getHashHitDensity).thenComparingInt(DrawableGroup::getSize).reversed());
}
@Override @Override
public <A extends Comparable<A>> Comparator<A> getValueComparator(DrawableAttribute<A> attr, SortOrder sortOrder) { public int compare(DrawableGroup o1, DrawableGroup o2) {
return getDefaultValueComparator(attr, sortOrder); return delegate.compare(o1, o2);
} }
};
private final static ObservableList<GroupSortBy> values = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(PRIORITY, NONE, GROUP_BY_VALUE, FILE_COUNT));
/** /**
* get a list of the values of this enum * get a list of the values of this enum
@ -108,8 +69,7 @@ public enum GroupSortBy implements ComparatorProvider {
* @return * @return
*/ */
public static ObservableList<GroupSortBy> getValues() { public static ObservableList<GroupSortBy> getValues() {
return FXCollections.observableArrayList(Arrays.asList(values())); return values;
} }
final private String displayName; final private String displayName;
@ -118,12 +78,12 @@ public enum GroupSortBy implements ComparatorProvider {
private final String imageName; private final String imageName;
private final Boolean sortOrderEnabled; private final Comparator<DrawableGroup> delegate;
private GroupSortBy(String displayName, Boolean sortOrderEnabled, String imagePath) { private GroupSortBy(String displayName, String imagePath, Comparator<DrawableGroup> internalComparator) {
this.displayName = displayName; this.displayName = displayName;
this.sortOrderEnabled = sortOrderEnabled;
this.imageName = imagePath; this.imageName = imagePath;
this.delegate = internalComparator;
} }
public String getDisplayName() { public String getDisplayName() {
@ -139,49 +99,11 @@ public enum GroupSortBy implements ComparatorProvider {
return icon; return icon;
} }
public Boolean isSortOrderEnabled() { static class AllEqualComparator<A> implements Comparator<A> {
return sortOrderEnabled;
}
private static <T> Comparator<T> applySortOrder(final SortOrder sortOrder, Comparator<T> comparator) {
switch (sortOrder) {
case ASCENDING:
return comparator;
case DESCENDING:
return comparator.reversed();
case UNSORTED:
default:
return new NoOpComparator<>();
}
}
private static class NoOpComparator<A> implements Comparator<A> {
@Override @Override
public int compare(A o1, A o2) { public int compare(A o1, A o2) {
return 0; return 0;
} }
} }
}
/**
* * implementers of this interface must provide a method to compare
* ({@link Comparable}) values and Groupings based on an
* {@link DrawableAttribute} and a {@link SortOrder}
*/
interface ComparatorProvider {
<A extends Comparable<A>> Comparator<A> getValueComparator(DrawableAttribute<A> attr, SortOrder sortOrder);
Comparator<DrawableGroup> getGrpComparator(SortOrder sortOrder);
default <A extends Comparable<A>> Comparator<A> getDefaultValueComparator(DrawableAttribute<A> attr, SortOrder sortOrder) {
return (A v1, A v2) -> {
DrawableGroup g1 = ImageGalleryController.getDefault().getGroupManager().getGroupForKey(new GroupKey<>(attr, v1));
DrawableGroup g2 = ImageGalleryController.getDefault().getGroupManager().getGroupForKey(new GroupKey<>(attr, v2));
return getGrpComparator(sortOrder).compare(g1, g2);
};
}
} }

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.HBox?>
<?import org.controlsfx.control.SegmentedButton?>
<fx:root id="HBox" alignment="CENTER" spacing="5.0" type="HBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label fx:id="label" text="Sort By:">
<labelFor>
<ComboBox fx:id="sortByBox" />
</labelFor>
</Label>
<fx:reference fx:id="sortBox" source="sortByBox" />
<SegmentedButton>
<buttons>
<RadioButton fx:id="ascRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="true">
<toggleGroup>
<ToggleGroup fx:id="orderGroup" />
</toggleGroup>
<graphic>
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/sort_ascending.png" />
</image>
</ImageView>
</graphic>
<tooltip>
<Tooltip text="Ascending" />
</tooltip>
</RadioButton>
<RadioButton fx:id="descRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="false" toggleGroup="$orderGroup">
<graphic>
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/sort_descending.png" />
</image>
</ImageView>
</graphic>
<tooltip>
<Tooltip text="Descending" />
</tooltip>
</RadioButton>
</buttons>
</SegmentedButton>
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</fx:root>

View File

@ -0,0 +1,181 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.gui;
import java.lang.reflect.InvocationTargetException;
import java.util.Comparator;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javax.swing.SortOrder;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
/**
*
*/
public class SortChooser<X, Y extends Comparator<X>> extends HBox {
@FXML
private RadioButton ascRadio;
@FXML
private RadioButton descRadio;
@FXML
private ToggleGroup orderGroup;
@FXML
private ComboBox<Y> sortByBox;
private final ObservableList<Y> comparators;
private final ReadOnlyObjectWrapper<SortOrder> sortOrder = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING);
private final SimpleBooleanProperty sortOrderDisabled = new SimpleBooleanProperty(false);
private final SimpleObjectProperty<ValueType> valueType = new SimpleObjectProperty<>(ValueType.NUMERIC);
public SortChooser(ObservableList<Y> comps) {
this.comparators = comps;
FXMLConstructor.construct(this, "SortChooser.fxml");
}
@FXML
void initialize() {
assert ascRadio != null : "fx:id=\"ascRadio\" was not injected: check your FXML file 'Toolbar.fxml'.";
assert descRadio != null : "fx:id=\"descRadio\" was not injected: check your FXML file 'Toolbar.fxml'.";
assert orderGroup != null : "fx:id=\"orderGroup\" was not injected: check your FXML file 'Toolbar.fxml'.";
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'Toolbar.fxml'.";
ascRadio.getStyleClass().remove("radio-button");
ascRadio.getStyleClass().add("toggle-button");
descRadio.getStyleClass().remove("radio-button");
descRadio.getStyleClass().add("toggle-button");
valueType.addListener((observable, oldValue, newValue) -> {
ascRadio.setGraphic(new ImageView(newValue.getAscendingImage()));
descRadio.setGraphic(new ImageView(newValue.getDescendingImage()));
});
ascRadio.disableProperty().bind(sortOrderDisabled);
descRadio.disableProperty().bind(sortOrderDisabled);
ascRadio.selectedProperty().addListener(selectedToggle -> {
sortOrder.set(orderGroup.getSelectedToggle() == ascRadio ? SortOrder.ASCENDING : SortOrder.DESCENDING);
});
sortByBox.setItems(comparators);
sortByBox.setCellFactory(listView -> new ComparatorCell());
sortByBox.setButtonCell(new ComparatorCell());
}
public ValueType getValueType() {
return valueType.get();
}
public void setValueType(ValueType type) {
valueType.set(type);
}
public SimpleObjectProperty<ValueType> valueTypeProperty() {
return valueType;
}
public void setSortOrderDisabled(boolean disabled) {
sortOrderDisabled.set(disabled);
}
public boolean isSortOrderDisabled() {
return sortOrderDisabled.get();
}
public SimpleBooleanProperty sortOrderDisabledProperty() {
return sortOrderDisabled;
}
public SortOrder getSortOrder() {
return sortOrder.get();
}
public ReadOnlyObjectProperty<SortOrder> sortOrderProperty() {
return sortOrder.getReadOnlyProperty();
}
public Y getComparator() {
return sortByBox.getSelectionModel().getSelectedItem();
}
public void setComparator(Y selected) {
sortByBox.getSelectionModel().select(selected);
}
public ReadOnlyObjectProperty<Y> comparatorProperty() {
return sortByBox.getSelectionModel().selectedItemProperty();
}
public enum ValueType {
LEXICOGRAPHIC("sort_asc_az.png", "sort_desc_az.png"),
NUMERIC("sort_ascending.png", "sort_descending.png");
private final Image ascImage;
private final Image descImage;
private ValueType(String ascImageName, String descImageName) {
this.ascImage = new Image("/org/sleuthkit/autopsy/imagegallery/images/" + ascImageName);
this.descImage = new Image("/org/sleuthkit/autopsy/imagegallery/images/" + descImageName);
}
private Image getAscendingImage() {
return ascImage;
}
private Image getDescendingImage() {
return descImage;
}
}
private class ComparatorCell extends ListCell<Y> {
@Override
protected void updateItem(Y item, boolean empty) {
super.updateItem(item, empty); //To change body of generated methods, choose Tools | Templates.
if (empty || null == item) {
setText(null);
setGraphic(null);
} else {
try {
String displayName = (String) item.getClass().getMethod("getDisplayName").invoke(item);
setText(displayName);
Image icon = (Image) item.getClass().getMethod("getIcon").invoke(item);
setGraphic(new ImageView(icon));
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
// Exceptions.printStackTrace(ex);
setText(item.toString());
setGraphic(null);
}
}
}
}
}

View File

@ -1,16 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?> <?import javafx.geometry.Insets?>
<?import javafx.geometry.*?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.image.*?> <?import javafx.scene.image.Image?>
<?import javafx.scene.layout.*?> <?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<fx:root id="AnchorPane" maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.AnchorPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> <fx:root id="AnchorPane" maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.AnchorPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<children> <children>
<BorderPane minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <BorderPane minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<right> <right>
<HBox alignment="CENTER_RIGHT" prefHeight="-1.0" prefWidth="-1.0" BorderPane.alignment="CENTER_RIGHT"> <HBox alignment="CENTER_RIGHT" prefHeight="-1.0" prefWidth="-1.0" spacing="5.0" BorderPane.alignment="CENTER_RIGHT">
<children> <children>
<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" HBox.hgrow="NEVER"> <StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" HBox.hgrow="NEVER">
<children> <children>
@ -18,7 +22,10 @@
<Label id="fileUpdateLabel" fx:id="fileUpdateTaskLabel" alignment="CENTER" contentDisplay="CENTER" graphicTextGap="0.0" labelFor="$fileTaskProgresBar" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="-1.0" text="0 File Update Tasks" StackPane.alignment="CENTER"> <Label id="fileUpdateLabel" fx:id="fileUpdateTaskLabel" alignment="CENTER" contentDisplay="CENTER" graphicTextGap="0.0" labelFor="$fileTaskProgresBar" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="-1.0" text="0 File Update Tasks" StackPane.alignment="CENTER">
<StackPane.margin> <StackPane.margin>
<Insets left="3.0" right="3.0" /> <Insets left="3.0" right="3.0" />
</StackPane.margin></Label> </StackPane.margin>
<padding>
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
</padding></Label>
</children> </children>
<HBox.margin> <HBox.margin>
<Insets /> <Insets />
@ -27,10 +34,13 @@
<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" HBox.hgrow="NEVER"> <StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" HBox.hgrow="NEVER">
<children> <children>
<ProgressBar fx:id="bgTaskProgressBar" maxHeight="-1.0" maxWidth="-1.0" minHeight="-Infinity" minWidth="-1.0" prefHeight="24.0" prefWidth="-1.0" progress="0.0" StackPane.alignment="CENTER" /> <ProgressBar fx:id="bgTaskProgressBar" maxHeight="-1.0" maxWidth="-1.0" minHeight="-Infinity" minWidth="-1.0" prefHeight="24.0" prefWidth="-1.0" progress="0.0" StackPane.alignment="CENTER" />
<Label fx:id="bgTaskLabel" alignment="CENTER" cache="false" contentDisplay="CENTER" disable="false" focusTraversable="false" labelFor="$uiTaskProgressBar" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" text="" StackPane.alignment="CENTER"> <Label fx:id="bgTaskLabel" alignment="CENTER" cache="false" contentDisplay="CENTER" disable="false" focusTraversable="false" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" text="" StackPane.alignment="CENTER">
<StackPane.margin> <StackPane.margin>
<Insets left="3.0" right="3.0" /> <Insets left="3.0" right="3.0" />
</StackPane.margin></Label> </StackPane.margin>
<padding>
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
</padding></Label>
</children> </children>
<HBox.margin> <HBox.margin>
<Insets right="5.0" /> <Insets right="5.0" />
@ -42,19 +52,6 @@
</BorderPane.margin> </BorderPane.margin>
</HBox> </HBox>
</right> </right>
<center>
<HBox>
<children>
<Label fx:id="statusLabel" maxWidth="-Infinity" minWidth="-Infinity" wrapText="true" BorderPane.alignment="CENTER" HBox.hgrow="ALWAYS">
<BorderPane.margin>
<Insets left="10.0" right="10.0" />
</BorderPane.margin>
<HBox.margin>
<Insets left="10.0" right="10.0" />
</HBox.margin></Label>
</children>
</HBox>
</center>
<left><Label fx:id="staleLabel" text="Some data may be out of date. Enable listening to ingest to update." BorderPane.alignment="CENTER"> <left><Label fx:id="staleLabel" text="Some data may be out of date. Enable listening to ingest to update." BorderPane.alignment="CENTER">
<graphic><ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true"> <graphic><ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image> <image>

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-14 Basis Technology Corp. * Copyright 2013-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -19,8 +19,6 @@
package org.sleuthkit.autopsy.imagegallery.gui; package org.sleuthkit.autopsy.imagegallery.gui;
import java.io.IOException; import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
@ -38,21 +36,12 @@ public class StatusBar extends AnchorPane {
private final ImageGalleryController controller; private final ImageGalleryController controller;
@FXML
private ResourceBundle resources;
@FXML
private URL location;
@FXML @FXML
private ProgressBar fileTaskProgresBar; private ProgressBar fileTaskProgresBar;
@FXML @FXML
private Label fileUpdateTaskLabel; private Label fileUpdateTaskLabel;
@FXML
private Label statusLabel;
@FXML @FXML
private Label bgTaskLabel; private Label bgTaskLabel;
@ -64,42 +53,32 @@ public class StatusBar extends AnchorPane {
@FXML @FXML
@NbBundle.Messages({"StatusBar.fileUpdateTaskLabel.text= File Update Tasks", @NbBundle.Messages({"StatusBar.fileUpdateTaskLabel.text= File Update Tasks",
"StatusBar.bgTaskLabel.text=Regrouping", "StatusBar.bgTaskLabel.text=Regrouping",
"StatuBar.toolTip=Some data may be out of date. Enable Image Gallery in Tools | Options | Image /Video Gallery , after ingest is complete to update the Image Gallery data."}) "StatuBar.toolTip=Some data may be out of date. Enable Image Gallery in Tools | Options | Image /Video Gallery , after ingest is complete to update the Image Gallery data."})
void initialize() { void initialize() {
assert fileTaskProgresBar != null : "fx:id=\"fileTaskProgresBar\" was not injected: check your FXML file 'StatusBar.fxml'."; assert fileTaskProgresBar != null : "fx:id=\"fileTaskProgresBar\" was not injected: check your FXML file 'StatusBar.fxml'.";
assert fileUpdateTaskLabel != null : "fx:id=\"fileUpdateTaskLabel\" was not injected: check your FXML file 'StatusBar.fxml'."; assert fileUpdateTaskLabel != null : "fx:id=\"fileUpdateTaskLabel\" was not injected: check your FXML file 'StatusBar.fxml'.";
assert statusLabel != null : "fx:id=\"statusLabel\" was not injected: check your FXML file 'StatusBar.fxml'.";
assert bgTaskLabel != null : "fx:id=\"bgTaskLabel\" was not injected: check your FXML file 'StatusBar.fxml'."; assert bgTaskLabel != null : "fx:id=\"bgTaskLabel\" was not injected: check your FXML file 'StatusBar.fxml'.";
assert bgTaskProgressBar != null : "fx:id=\"bgTaskProgressBar\" was not injected: check your FXML file 'StatusBar.fxml'."; assert bgTaskProgressBar != null : "fx:id=\"bgTaskProgressBar\" was not injected: check your FXML file 'StatusBar.fxml'.";
fileUpdateTaskLabel.textProperty().bind(controller.getFileUpdateQueueSizeProperty().asString().concat(Bundle.StatusBar_fileUpdateTaskLabel_text()));//;setText(newSize.toString() + " File Update Tasks"); fileUpdateTaskLabel.textProperty().bind(controller.getDBTasksQueueSizeProperty().asString().concat(Bundle.StatusBar_fileUpdateTaskLabel_text()));
fileTaskProgresBar.progressProperty().bind(controller.getFileUpdateQueueSizeProperty().negate()); fileTaskProgresBar.progressProperty().bind(controller.getDBTasksQueueSizeProperty().negate());
// controller.getFileUpdateQueueSizeProperty().addListener((ov, oldSize, newSize) -> {
// Platform.runLater(() -> {
//
//
// });
// });
controller.regroupProgress().addListener((ov, oldSize, newSize) -> { controller.regroupProgress().addListener((ov, oldSize, newSize) -> {
Platform.runLater(() -> { Platform.runLater(() -> {
if(controller.regroupProgress().lessThan(1.0).get()){ if (controller.regroupProgress().lessThan(1.0).get()) {
// Regrouping in progress // Regrouping in progress
bgTaskProgressBar.progressProperty().setValue(-1.0); bgTaskProgressBar.progressProperty().setValue(-1.0);
bgTaskLabel.setText(Bundle.StatusBar_bgTaskLabel_text()); bgTaskLabel.setText(Bundle.StatusBar_bgTaskLabel_text());
} else{ } else {
// Clear the progress bar // Clear the progress bar
bgTaskProgressBar.progressProperty().setValue(0.0); bgTaskProgressBar.progressProperty().setValue(0.0);
bgTaskLabel.setText(""); bgTaskLabel.setText("");
} }
}); });
}); });
Platform.runLater(() -> { Platform.runLater(() -> staleLabel.setTooltip(new Tooltip(Bundle.StatuBar_toolTip())));
staleLabel.setTooltip(new Tooltip(Bundle.StatuBar_toolTip()));
});
staleLabel.visibleProperty().bind(controller.stale()); staleLabel.visibleProperty().bind(controller.stale());
} }
@ -116,14 +95,4 @@ public class StatusBar extends AnchorPane {
} }
} }
public void setLabelText(final String newText) {
Platform.runLater(() -> {
statusLabel.setText(newText);
});
}
public String getLabeltext() {
return statusLabel.getText();
}
} }

View File

@ -1,113 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ComboBox?> <?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.MenuItem?> <?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.Separator?> <?import javafx.scene.control.Separator?>
<?import javafx.scene.control.Slider?> <?import javafx.scene.control.Slider?>
<?import javafx.scene.control.SplitMenuButton?> <?import javafx.scene.control.SplitMenuButton?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.control.ToolBar?> <?import javafx.scene.control.ToolBar?>
<?import javafx.scene.image.Image?> <?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?> <?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<fx:root minWidth="-1.0" orientation="HORIZONTAL" prefWidth="-1.0" type="javafx.scene.control.ToolBar" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1"> <fx:root minWidth="-1.0" orientation="HORIZONTAL" prefWidth="-1.0" type="javafx.scene.control.ToolBar" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<items> <items>
<Label fx:id="groupByLabel" text="Group By:"> <HBox alignment="CENTER" spacing="5.0">
<labelFor> <children>
<ComboBox fx:id="groupByBox" editable="false" /> <Label fx:id="groupByLabel" text="Group By:">
</labelFor> <labelFor>
</Label> <ComboBox fx:id="groupByBox" editable="false" />
<fx:reference source="groupByBox" /> </labelFor>
<Region prefHeight="-1.0" prefWidth="10.0" /> </Label>
<Label fx:id="sortByLabel" text="Sort By:"> <fx:reference source="groupByBox" />
<labelFor>
<ComboBox fx:id="sortByBox" /> </children>
</labelFor> </HBox>
</Label>
<HBox id="HBox" fx:id="sortControlGroup" alignment="CENTER" spacing="5.0">
<children>
<fx:reference source="sortByBox" />
<VBox alignment="CENTER_LEFT" prefHeight="-1.0" prefWidth="-1.0" spacing="2.0"> <Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="10.0" />
<children> <HBox alignment="CENTER" spacing="5.0">
<RadioButton fx:id="ascRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="true" text="Ascending"> <children>
<graphic> <Label fx:id="tagImageViewLabel" text="Tag Group's Files:">
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true"> <graphic>
<image> <ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<Image url="@../images/arrow_up.png" /> <image>
</image> <Image url="@../images/tag_red.png" />
</ImageView> </image>
</graphic> </ImageView>
<toggleGroup> </graphic>
<ToggleGroup fx:id="orderGroup" /> </Label>
</toggleGroup> <SplitMenuButton id="tagSplitMenu" fx:id="tagGroupMenuButton" disable="true" mnemonicParsing="false" text="Follow Up" textOverrun="ELLIPSIS">
</RadioButton> <items>
<RadioButton fx:id="descRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="false" text="Descending" toggleGroup="$orderGroup"> <MenuItem mnemonicParsing="false" text="Action 1" />
<graphic> <MenuItem mnemonicParsing="false" text="Action 2" />
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true"> </items>
<image> </SplitMenuButton>
<Image url="@../images/arrow_down.png" /> </children>
</image> </HBox>
</ImageView> <HBox alignment="CENTER" spacing="5.0">
</graphic> <children>
</RadioButton> <Label fx:id="categoryImageViewLabel" text="Categorize Group's Files:">
</children> <graphic>
</VBox> <ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
</children> <image>
</HBox> <Image url="@../images/category-icon.png" />
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="20.0" /> </image>
<HBox alignment="CENTER" spacing="5.0"> </ImageView>
<children> </graphic>
<Label fx:id="tagImageViewLabel" text="Tag Group's Files:"> </Label>
<graphic> <SplitMenuButton id="catSplitMenu" fx:id="catGroupMenuButton" disable="true" mnemonicParsing="false" text="Cat-0">
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true"> <items>
<image> <MenuItem mnemonicParsing="false" text="Action 1" />
<Image url="@../images/tag_red.png" /> <MenuItem mnemonicParsing="false" text="Action 2" />
</image> </items>
</ImageView> </SplitMenuButton>
</graphic> </children>
</Label> <padding>
<SplitMenuButton id="tagSplitMenu" fx:id="tagGroupMenuButton" disable="true" mnemonicParsing="false" text="Follow Up" textOverrun="ELLIPSIS"> <Insets left="5.0" />
<items> </padding>
<MenuItem mnemonicParsing="false" text="Action 1" /> </HBox>
<MenuItem mnemonicParsing="false" text="Action 2" /> <Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="10.0" />
</items> <HBox alignment="CENTER" spacing="5.0">
</SplitMenuButton> <children>
</children> <Label fx:id="thumbnailSizeLabel" text="Thumbnail Size (px):">
</HBox> <labelFor>
<HBox alignment="CENTER" spacing="5.0"> <Slider fx:id="sizeSlider" blockIncrement="100.0" majorTickUnit="100.0" max="300.0" min="100.0" minorTickCount="0" orientation="HORIZONTAL" prefHeight="-1.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" value="100.0" />
<children> </labelFor>
<Label fx:id="categoryImageViewLabel" text="Categorize Group's Files:"> </Label>
<graphic> <fx:reference source="sizeSlider" />
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true"> </children>
<image> </HBox>
<Image url="@../images/category-icon.png" /> </items>
</image>
</ImageView>
</graphic>
</Label>
<SplitMenuButton id="catSplitMenu" fx:id="catGroupMenuButton" disable="true" mnemonicParsing="false" text="Cat-0">
<items>
<MenuItem mnemonicParsing="false" text="Action 1" />
<MenuItem mnemonicParsing="false" text="Action 2" />
</items>
</SplitMenuButton>
</children>
</HBox>
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="20.0" />
<HBox alignment="CENTER" spacing="5.0">
<children>
<Label fx:id="thumbnailSizeLabel" text="Thumbnail Size (px):">
<labelFor>
<Slider fx:id="sizeSlider" blockIncrement="100.0" majorTickUnit="100.0" max="300.0" min="100.0" minorTickCount="0" orientation="HORIZONTAL" prefHeight="-1.0" showTickLabels="true" showTickMarks="true" snapToTicks="true" value="100.0" />
</labelFor>
</Label>
<fx:reference source="sizeSlider" />
</children>
</HBox>
</items>
</fx:root> </fx:root>

View File

@ -26,20 +26,14 @@ import javafx.application.Platform;
import javafx.beans.InvalidationListener; import javafx.beans.InvalidationListener;
import javafx.beans.Observable; import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty; import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Slider; import javafx.scene.control.Slider;
import javafx.scene.control.SplitMenuButton; import javafx.scene.control.SplitMenuButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar; import javafx.scene.control.ToolBar;
import javafx.scene.layout.HBox;
import javax.swing.SortOrder;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
@ -48,6 +42,7 @@ import org.sleuthkit.autopsy.imagegallery.actions.CategorizeGroupAction;
import org.sleuthkit.autopsy.imagegallery.actions.TagGroupAction; import org.sleuthkit.autopsy.imagegallery.actions.TagGroupAction;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
@ -67,21 +62,6 @@ public class Toolbar extends ToolBar {
@FXML @FXML
private Slider sizeSlider; private Slider sizeSlider;
@FXML
private ComboBox<GroupSortBy> sortByBox;
@FXML
private RadioButton ascRadio;
@FXML
private RadioButton descRadio;
@FXML
private ToggleGroup orderGroup;
@FXML
private HBox sortControlGroup;
@FXML @FXML
private SplitMenuButton catGroupMenuButton; private SplitMenuButton catGroupMenuButton;
@ -91,9 +71,6 @@ public class Toolbar extends ToolBar {
@FXML @FXML
private Label groupByLabel; private Label groupByLabel;
@FXML
private Label sortByLabel;
@FXML @FXML
private Label tagImageViewLabel; private Label tagImageViewLabel;
@ -103,54 +80,35 @@ public class Toolbar extends ToolBar {
@FXML @FXML
private Label thumbnailSizeLabel; private Label thumbnailSizeLabel;
private static Toolbar instance;
private final SimpleObjectProperty<SortOrder> orderProperty = new SimpleObjectProperty<>(SortOrder.ASCENDING);
private final InvalidationListener queryInvalidationListener = (Observable o) -> {
if (orderGroup.getSelectedToggle() == ascRadio) {
orderProperty.set(SortOrder.ASCENDING);
} else {
orderProperty.set(SortOrder.DESCENDING);
}
ImageGalleryController.getDefault().getGroupManager().regroup(groupByBox.getSelectionModel().getSelectedItem(), sortByBox.getSelectionModel().getSelectedItem(), getSortOrder(), false);
};
private final ImageGalleryController controller; private final ImageGalleryController controller;
private SortChooser<DrawableGroup, GroupSortBy> sortChooser;
synchronized public SortOrder getSortOrder() { private final InvalidationListener queryInvalidationListener = new InvalidationListener() {
return orderProperty.get(); public void invalidated(Observable o) {
} controller.getGroupManager().regroup(
groupByBox.getSelectionModel().getSelectedItem(),
public DoubleProperty sizeSliderValue() { sortChooser.getComparator(),
return sizeSlider.valueProperty(); sortChooser.getSortOrder(),
} false);
static synchronized public Toolbar getDefault(ImageGalleryController controller) {
if (instance == null) {
instance = new Toolbar(controller);
} }
return instance; };
public DoubleProperty thumbnailSizeProperty() {
return sizeSlider.valueProperty();
} }
@FXML @FXML
@NbBundle.Messages({"Toolbar.groupByLabel=Group By:", @NbBundle.Messages({"Toolbar.groupByLabel=Group By:",
"Toolbar.sortByLabel=Sort By:", "Toolbar.sortByLabel=Sort By:",
"Toolbar.ascRadio=Ascending", "Toolbar.ascRadio=Ascending",
"Toolbar.descRadio=Descending", "Toolbar.descRadio=Descending",
"Toolbar.tagImageViewLabel=Tag Group's Files:", "Toolbar.tagImageViewLabel=Tag Group's Files:",
"Toolbar.categoryImageViewLabel=Categorize Group's Files:", "Toolbar.categoryImageViewLabel=Categorize Group's Files:",
"Toolbar.thumbnailSizeLabel=Thumbnail Size (px):"}) "Toolbar.thumbnailSizeLabel=Thumbnail Size (px):"})
void initialize() { void initialize() {
assert ascRadio != null : "fx:id=\"ascRadio\" was not injected: check your FXML file 'Toolbar.fxml'.";
assert catGroupMenuButton != null : "fx:id=\"catSelectedMenubutton\" was not injected: check your FXML file 'Toolbar.fxml'."; assert catGroupMenuButton != null : "fx:id=\"catSelectedMenubutton\" was not injected: check your FXML file 'Toolbar.fxml'.";
assert descRadio != null : "fx:id=\"descRadio\" was not injected: check your FXML file 'Toolbar.fxml'.";
assert groupByBox != null : "fx:id=\"groupByBox\" was not injected: check your FXML file 'Toolbar.fxml'."; assert groupByBox != null : "fx:id=\"groupByBox\" was not injected: check your FXML file 'Toolbar.fxml'.";
assert orderGroup != null : "fx:id=\"orderGroup\" was not injected: check your FXML file 'Toolbar.fxml'.";
assert sizeSlider != null : "fx:id=\"sizeSlider\" was not injected: check your FXML file 'Toolbar.fxml'."; assert sizeSlider != null : "fx:id=\"sizeSlider\" was not injected: check your FXML file 'Toolbar.fxml'.";
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'Toolbar.fxml'.";
assert sortControlGroup != null : "fx:id=\"sortControlGroup\" was not injected: check your FXML file 'Toolbar.fxml'.";
assert tagGroupMenuButton != null : "fx:id=\"tagSelectedMenubutton\" was not injected: check your FXML file 'Toolbar.fxml'."; assert tagGroupMenuButton != null : "fx:id=\"tagSelectedMenubutton\" was not injected: check your FXML file 'Toolbar.fxml'.";
controller.viewState().addListener((observable, oldViewState, newViewState) -> { controller.viewState().addListener((observable, oldViewState, newViewState) -> {
@ -174,14 +132,6 @@ public class Toolbar extends ToolBar {
} }
}); });
groupByLabel.setText(Bundle.Toolbar_groupByLabel());
sortByLabel.setText(Bundle.Toolbar_sortByLabel());
ascRadio.setText(Bundle.Toolbar_ascRadio());
descRadio.setText(Bundle.Toolbar_descRadio());
tagImageViewLabel.setText(Bundle.Toolbar_tagImageViewLabel());
categoryImageViewLabel.setText(Bundle.Toolbar_categoryImageViewLabel());
thumbnailSizeLabel.setText(Bundle.Toolbar_thumbnailSizeLabel());
CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(Category.FIVE, controller); CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(Category.FIVE, controller);
catGroupMenuButton.setOnAction(cat5GroupAction); catGroupMenuButton.setOnAction(cat5GroupAction);
catGroupMenuButton.setText(cat5GroupAction.getText()); catGroupMenuButton.setText(cat5GroupAction.getText());
@ -194,6 +144,11 @@ public class Toolbar extends ToolBar {
} }
}); });
groupByLabel.setText(Bundle.Toolbar_groupByLabel());
tagImageViewLabel.setText(Bundle.Toolbar_tagImageViewLabel());
categoryImageViewLabel.setText(Bundle.Toolbar_categoryImageViewLabel());
thumbnailSizeLabel.setText(Bundle.Toolbar_thumbnailSizeLabel());
groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs())); groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs()));
groupByBox.getSelectionModel().select(DrawableAttribute.PATH); groupByBox.getSelectionModel().select(DrawableAttribute.PATH);
groupByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener); groupByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener);
@ -201,21 +156,20 @@ public class Toolbar extends ToolBar {
groupByBox.setCellFactory(listView -> new AttributeListCell()); groupByBox.setCellFactory(listView -> new AttributeListCell());
groupByBox.setButtonCell(new AttributeListCell()); groupByBox.setButtonCell(new AttributeListCell());
sortByBox.setCellFactory(listView -> new SortByListCell()); sortChooser = new SortChooser<>(GroupSortBy.getValues());
sortByBox.setButtonCell(new SortByListCell()); sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> {
sortByBox.setItems(GroupSortBy.getValues()); final boolean orderEnabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY;
sortChooser.setSortOrderDisabled(orderEnabled);
sortByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener);
sortByBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
final boolean orderEnabled = newValue == GroupSortBy.NONE || newValue == GroupSortBy.PRIORITY;
ascRadio.setDisable(orderEnabled);
descRadio.setDisable(orderEnabled);
final SortChooser.ValueType valueType = newComparator == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC;
sortChooser.setValueType(valueType);
queryInvalidationListener.invalidated(observable);
}); });
sortByBox.getSelectionModel().select(GroupSortBy.PRIORITY);
orderGroup.selectedToggleProperty().addListener(queryInvalidationListener); sortChooser.sortOrderProperty().addListener(queryInvalidationListener);
sortChooser.setComparator(GroupSortBy.PRIORITY);
getItems().add(1, sortChooser);
} }
private void syncGroupControlsEnabledState(GroupViewState newViewState) { private void syncGroupControlsEnabledState(GroupViewState newViewState) {
@ -230,13 +184,11 @@ public class Toolbar extends ToolBar {
public void reset() { public void reset() {
Platform.runLater(() -> { Platform.runLater(() -> {
groupByBox.getSelectionModel().select(DrawableAttribute.PATH); groupByBox.getSelectionModel().select(DrawableAttribute.PATH);
sortByBox.getSelectionModel().select(GroupSortBy.NONE);
orderGroup.selectToggle(ascRadio);
sizeSlider.setValue(SIZE_SLIDER_DEFAULT); sizeSlider.setValue(SIZE_SLIDER_DEFAULT);
}); });
} }
private Toolbar(ImageGalleryController controller) { public Toolbar(ImageGalleryController controller) {
this.controller = controller; this.controller = controller;
FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS
} }

View File

@ -104,7 +104,7 @@ public class VideoPlayer extends BorderPane {
mp.seek(Duration.millis(timeSlider.getValue())); mp.seek(Duration.millis(timeSlider.getValue()));
} }
}; };
private final VideoFile<?> file; private final VideoFile file;
@FXML @FXML
@NbBundle.Messages({"# {0} - exception type", @NbBundle.Messages({"# {0} - exception type",
@ -236,7 +236,7 @@ public class VideoPlayer extends BorderPane {
} }
} }
public VideoPlayer(MediaPlayer mp, VideoFile<?> file) { public VideoPlayer(MediaPlayer mp, VideoFile file) {
this.file = file; this.file = file;
this.mp = mp; this.mp = mp;
FXMLConstructor.construct(this, "MediaControl.fxml"); //NON-NLS FXMLConstructor.construct(this, "MediaControl.fxml"); //NON-NLS

View File

@ -31,13 +31,10 @@ import javafx.scene.image.Image;
import javafx.scene.input.MouseButton; import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
import org.sleuthkit.datamodel.AbstractContent;
/** /**
* GUI component that represents a single image as a tile with an icon, a label, * GUI component that represents a single image as a tile with an icon, a label,
@ -66,8 +63,8 @@ public class DrawableTile extends DrawableTileBase {
setCache(true); setCache(true);
setCacheHint(CacheHint.SPEED); setCacheHint(CacheHint.SPEED);
nameLabel.prefWidthProperty().bind(imageView.fitWidthProperty()); nameLabel.prefWidthProperty().bind(imageView.fitWidthProperty());
imageView.fitHeightProperty().bind(Toolbar.getDefault(getController()).sizeSliderValue()); imageView.fitHeightProperty().bind(getController().thumbnailSizeProperty());
imageView.fitWidthProperty().bind(Toolbar.getDefault(getController()).sizeSliderValue()); imageView.fitWidthProperty().bind(getController().thumbnailSizeProperty());
selectionModel.lastSelectedProperty().addListener(new WeakChangeListener<>(lastSelectionListener)); selectionModel.lastSelectedProperty().addListener(new WeakChangeListener<>(lastSelectionListener));
@ -108,12 +105,12 @@ public class DrawableTile extends DrawableTileBase {
} }
@Override @Override
Task<Image> newReadImageTask(DrawableFile<?> file) { Task<Image> newReadImageTask(DrawableFile file) {
return file.getThumbnailTask(); return file.getThumbnailTask();
} }
@Override @Override
protected String getTextForLabel() { protected String getTextForLabel() {
return getFile().map(AbstractContent::getName).orElse(""); return getFile().map(DrawableFile::getName).orElse("");
} }
} }

View File

@ -63,7 +63,7 @@ import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent; import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction; import org.sleuthkit.autopsy.imagegallery.actions.AddTagAction;
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
import org.sleuthkit.autopsy.imagegallery.actions.DeleteFollowUpTagAction; import org.sleuthkit.autopsy.imagegallery.actions.DeleteFollowUpTagAction;
import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction; import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction;
@ -142,7 +142,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
* @param controller the value of controller * @param controller the value of controller
*/ */
@NbBundle.Messages({"DrawableTileBase.menuItem.extractFiles=Extract File(s)", @NbBundle.Messages({"DrawableTileBase.menuItem.extractFiles=Extract File(s)",
"DrawableTileBase.menuItem.showContentViewer=Show Content Viewer"}) "DrawableTileBase.menuItem.showContentViewer=Show Content Viewer"})
protected DrawableTileBase(GroupPane groupPane, final ImageGalleryController controller) { protected DrawableTileBase(GroupPane groupPane, final ImageGalleryController controller) {
super(controller); super(controller);
this.groupPane = groupPane; this.groupPane = groupPane;
@ -179,13 +179,11 @@ public abstract class DrawableTileBase extends DrawableUIBase {
t.consume(); t.consume();
} }
private ContextMenu buildContextMenu(DrawableFile<?> file) { private ContextMenu buildContextMenu(DrawableFile file) {
final ArrayList<MenuItem> menuItems = new ArrayList<>(); final ArrayList<MenuItem> menuItems = new ArrayList<>();
menuItems.add(new CategorizeAction(getController()).getPopupMenu()); menuItems.add(CategorizeAction.getCategoriesMenu(getController()));
menuItems.add(AddTagAction.getTagMenu(getController()));
menuItems.add(new AddDrawableTagAction(getController()).getPopupMenu());
final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles()); final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles());
extractMenuItem.setOnAction(actionEvent -> { extractMenuItem.setOnAction(actionEvent -> {
@ -196,7 +194,6 @@ public abstract class DrawableTileBase extends DrawableUIBase {
}); });
menuItems.add(extractMenuItem); menuItems.add(extractMenuItem);
MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer()); MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer());
contentViewer.setOnAction(actionEvent -> { contentViewer.setOnAction(actionEvent -> {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
@ -242,7 +239,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
if (followUpToggle.isSelected() == true) { if (followUpToggle.isSelected() == true) {
try { try {
selectionModel.clearAndSelect(file.getId()); selectionModel.clearAndSelect(file.getId());
new AddDrawableTagAction(getController()).addTag(getController().getTagsManager().getFollowUpTagName(), ""); new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent);
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex); //NON-NLS LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex); //NON-NLS
} }

View File

@ -66,7 +66,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
private final ImageGalleryController controller; private final ImageGalleryController controller;
private Optional<DrawableFile<?>> fileOpt = Optional.empty(); private Optional<DrawableFile> fileOpt = Optional.empty();
private Optional<Long> fileIDOpt = Optional.empty(); private Optional<Long> fileIDOpt = Optional.empty();
private volatile Task<Image> imageTask; private volatile Task<Image> imageTask;
@ -89,12 +89,12 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
this.fileIDOpt = fileIDOpt; this.fileIDOpt = fileIDOpt;
} }
synchronized void setFileOpt(Optional<DrawableFile<?>> fileOpt) { synchronized void setFileOpt(Optional<DrawableFile> fileOpt) {
this.fileOpt = fileOpt; this.fileOpt = fileOpt;
} }
@Override @Override
synchronized public Optional<DrawableFile<?>> getFile() { synchronized public Optional<DrawableFile> getFile() {
if (fileIDOpt.isPresent()) { if (fileIDOpt.isPresent()) {
if (fileOpt.isPresent() && fileOpt.get().getId() == fileIDOpt.get()) { if (fileOpt.isPresent() && fileOpt.get().getId() == fileIDOpt.get()) {
return fileOpt; return fileOpt;
@ -131,7 +131,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
} }
} }
synchronized Node doReadImageTask(DrawableFile<?> file) { synchronized Node doReadImageTask(DrawableFile file) {
Task<Image> myTask = newReadImageTask(file); Task<Image> myTask = newReadImageTask(file);
imageTask = myTask; imageTask = myTask;
Node progressNode = newProgressIndicator(myTask); Node progressNode = newProgressIndicator(myTask);
@ -191,7 +191,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
} }
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void showImage(DrawableFile<?> file, Task<Image> imageTask) { private void showImage(DrawableFile file, Task<Image> imageTask) {
//Note that all error conditions are allready logged in readImageTask.succeeded() //Note that all error conditions are allready logged in readImageTask.succeeded()
try { try {
Image fxImage = imageTask.get(); Image fxImage = imageTask.get();
@ -210,7 +210,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
} }
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
void showErrorNode(String errorMessage, DrawableFile<?> file) { void showErrorNode(String errorMessage, DrawableFile file) {
Button createButton = ActionUtils.createButton(new OpenExternalViewerAction(file)); Button createButton = ActionUtils.createButton(new OpenExternalViewerAction(file));
VBox vBox = new VBox(10, new Label(errorMessage), createButton); VBox vBox = new VBox(10, new Label(errorMessage), createButton);
@ -218,5 +218,5 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
imageBorder.setCenter(vBox); imageBorder.setCenter(vBox);
} }
abstract Task<Image> newReadImageTask(DrawableFile<?> file); abstract Task<Image> newReadImageTask(DrawableFile file);
} }

View File

@ -52,7 +52,7 @@ public interface DrawableView {
Region getCategoryBorderRegion(); Region getCategoryBorderRegion();
Optional<DrawableFile<?>> getFile(); Optional<DrawableFile> getFile();
void setFile(final Long fileID); void setFile(final Long fileID);

View File

@ -19,6 +19,7 @@
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews; package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -47,6 +48,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@ -110,7 +112,7 @@ import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent; import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction; import org.sleuthkit.autopsy.imagegallery.actions.AddTagAction;
import org.sleuthkit.autopsy.imagegallery.actions.Back; import org.sleuthkit.autopsy.imagegallery.actions.Back;
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeSelectedFilesAction; import org.sleuthkit.autopsy.imagegallery.actions.CategorizeSelectedFilesAction;
@ -126,7 +128,6 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils; import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
@ -143,39 +144,39 @@ import org.sleuthkit.datamodel.TskCoreException;
* https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview * https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview
*/ */
public class GroupPane extends BorderPane { public class GroupPane extends BorderPane {
private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName()); private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName());
private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2); private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2);
private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2); private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2);
private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE); private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE);
private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)), private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)),
new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR)) new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR))
); );
private final FileIDSelectionModel selectionModel; private final FileIDSelectionModel selectionModel;
private static final List<KeyCode> categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5, private static final List<KeyCode> categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5,
KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5); KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5);
private final Back backAction; private final Back backAction;
private final Forward forwardAction; private final Forward forwardAction;
@FXML @FXML
private Button undoButton; private Button undoButton;
@FXML @FXML
private Button redoButton; private Button redoButton;
@FXML @FXML
private SplitMenuButton catSelectedSplitMenu; private SplitMenuButton catSelectedSplitMenu;
@FXML @FXML
private SplitMenuButton tagSelectedSplitMenu; private SplitMenuButton tagSelectedSplitMenu;
@FXML @FXML
private ToolBar headerToolBar; private ToolBar headerToolBar;
@FXML @FXML
private ToggleButton cat0Toggle; private ToggleButton cat0Toggle;
@FXML @FXML
@ -188,30 +189,30 @@ public class GroupPane extends BorderPane {
private ToggleButton cat4Toggle; private ToggleButton cat4Toggle;
@FXML @FXML
private ToggleButton cat5Toggle; private ToggleButton cat5Toggle;
@FXML @FXML
private SegmentedButton segButton; private SegmentedButton segButton;
private SlideShowView slideShowPane; private SlideShowView slideShowPane;
@FXML @FXML
private ToggleButton slideShowToggle; private ToggleButton slideShowToggle;
@FXML @FXML
private GridView<Long> gridView; private GridView<Long> gridView;
@FXML @FXML
private ToggleButton tileToggle; private ToggleButton tileToggle;
@FXML @FXML
private Button nextButton; private Button nextButton;
@FXML @FXML
private Button backButton; private Button backButton;
@FXML @FXML
private Button forwardButton; private Button forwardButton;
@FXML @FXML
private Label groupLabel; private Label groupLabel;
@FXML @FXML
@ -222,24 +223,24 @@ public class GroupPane extends BorderPane {
private Label catContainerLabel; private Label catContainerLabel;
@FXML @FXML
private Label catHeadingLabel; private Label catHeadingLabel;
@FXML @FXML
private HBox catSegmentedContainer; private HBox catSegmentedContainer;
@FXML @FXML
private HBox catSplitMenuContainer; private HBox catSplitMenuContainer;
private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler(); private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler();
private final NextUnseenGroup nextGroupAction; private final NextUnseenGroup nextGroupAction;
private final ImageGalleryController controller; private final ImageGalleryController controller;
private ContextMenu contextMenu; private ContextMenu contextMenu;
private Integer selectionAnchorIndex; private Integer selectionAnchorIndex;
private final UndoAction undoAction; private final UndoAction undoAction;
private final RedoAction redoAction; private final RedoAction redoAction;
GroupViewMode getGroupViewMode() { GroupViewMode getGroupViewMode() {
return groupViewMode.get(); return groupViewMode.get();
} }
@ -262,7 +263,7 @@ public class GroupPane extends BorderPane {
*/ */
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
private final Map<Long, DrawableCell> cellMap = new HashMap<>(); private final Map<Long, DrawableCell> cellMap = new HashMap<>();
private final InvalidationListener filesSyncListener = (observable) -> { private final InvalidationListener filesSyncListener = (observable) -> {
final String header = getHeaderString(); final String header = getHeaderString();
final List<Long> fileIds = getGroup().getFileIDs(); final List<Long> fileIds = getGroup().getFileIDs();
@ -272,7 +273,7 @@ public class GroupPane extends BorderPane {
groupLabel.setText(header); groupLabel.setText(header);
}); });
}; };
public GroupPane(ImageGalleryController controller) { public GroupPane(ImageGalleryController controller) {
this.controller = controller; this.controller = controller;
this.selectionModel = controller.getSelectionModel(); this.selectionModel = controller.getSelectionModel();
@ -281,10 +282,10 @@ public class GroupPane extends BorderPane {
forwardAction = new Forward(controller); forwardAction = new Forward(controller);
undoAction = new UndoAction(controller); undoAction = new UndoAction(controller);
redoAction = new RedoAction(controller); redoAction = new RedoAction(controller);
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
} }
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
public void activateSlideShowViewer(Long slideShowFileID) { public void activateSlideShowViewer(Long slideShowFileID) {
groupViewMode.set(GroupViewMode.SLIDE_SHOW); groupViewMode.set(GroupViewMode.SLIDE_SHOW);
@ -300,16 +301,16 @@ public class GroupPane extends BorderPane {
} else { } else {
slideShowPane.setFile(slideShowFileID); slideShowPane.setFile(slideShowFileID);
} }
setCenter(slideShowPane); setCenter(slideShowPane);
slideShowPane.requestFocus(); slideShowPane.requestFocus();
} }
void syncCatToggle(DrawableFile<?> file) { void syncCatToggle(DrawableFile file) {
getToggleForCategory(file.getCategory()).setSelected(true); getToggleForCategory(file.getCategory()).setSelected(true);
} }
public void activateTileViewer() { public void activateTileViewer() {
groupViewMode.set(GroupViewMode.TILE); groupViewMode.set(GroupViewMode.TILE);
tileToggle.setSelected(true); tileToggle.setSelected(true);
@ -321,11 +322,11 @@ public class GroupPane extends BorderPane {
slideShowPane = null; slideShowPane = null;
this.scrollToFileID(selectionModel.lastSelectedProperty().get()); this.scrollToFileID(selectionModel.lastSelectedProperty().get());
} }
public DrawableGroup getGroup() { public DrawableGroup getGroup() {
return grouping.get(); return grouping.get();
} }
private void selectAllFiles() { private void selectAllFiles() {
selectionModel.clearAndSelectAll(getGroup().getFileIDs()); selectionModel.clearAndSelectAll(getGroup().getFileIDs());
} }
@ -342,15 +343,15 @@ public class GroupPane extends BorderPane {
: Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()), : Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()),
getGroup().getHashSetHitsCount(), getGroup().getSize()); getGroup().getHashSetHitsCount(), getGroup().getSize());
} }
ContextMenu getContextMenu() { ContextMenu getContextMenu() {
return contextMenu; return contextMenu;
} }
ReadOnlyObjectProperty<DrawableGroup> grouping() { ReadOnlyObjectProperty<DrawableGroup> grouping() {
return grouping.getReadOnlyProperty(); return grouping.getReadOnlyProperty();
} }
private ToggleButton getToggleForCategory(Category category) { private ToggleButton getToggleForCategory(Category category) {
switch (category) { switch (category) {
case ZERO: case ZERO:
@ -377,10 +378,10 @@ public class GroupPane extends BorderPane {
*/ */
@FXML @FXML
@NbBundle.Messages({"GroupPane.gridViewContextMenuItem.extractFiles=Extract File(s)", @NbBundle.Messages({"GroupPane.gridViewContextMenuItem.extractFiles=Extract File(s)",
"GroupPane.bottomLabel.displayText=Group Viewing History: ", "GroupPane.bottomLabel.displayText=Group Viewing History: ",
"GroupPane.hederLabel.displayText=Tag Selected Files:", "GroupPane.hederLabel.displayText=Tag Selected Files:",
"GroupPane.catContainerLabel.displayText=Categorize Selected File:", "GroupPane.catContainerLabel.displayText=Categorize Selected File:",
"GroupPane.catHeadingLabel.displayText=Category:"}) "GroupPane.catHeadingLabel.displayText=Category:"})
void initialize() { void initialize() {
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'."; assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
@ -395,7 +396,7 @@ public class GroupPane extends BorderPane {
assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'.";
assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'.";
assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'."; assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'.";
for (Category cat : Category.values()) { for (Category cat : Category.values()) {
ToggleButton toggleForCategory = getToggleForCategory(cat); ToggleButton toggleForCategory = getToggleForCategory(cat);
toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2))); toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2)));
@ -403,9 +404,9 @@ public class GroupPane extends BorderPane {
toggleForCategory.getStyleClass().add("toggle-button"); toggleForCategory.getStyleClass().add("toggle-button");
toggleForCategory.selectedProperty().addListener((ov, wasSelected, toggleSelected) -> { toggleForCategory.selectedProperty().addListener((ov, wasSelected, toggleSelected) -> {
if (toggleSelected && slideShowPane != null) { if (toggleSelected && slideShowPane != null) {
slideShowPane.getFileID().ifPresent((fileID) -> { slideShowPane.getFileID().ifPresent(fileID -> {
selectionModel.clearAndSelect(fileID); selectionModel.clearAndSelect(fileID);
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), ""); new CategorizeAction(controller, cat, ImmutableSet.of(fileID)).handle(null);
}); });
} }
}); });
@ -416,15 +417,15 @@ public class GroupPane extends BorderPane {
flashAnimation.setAutoReverse(true); flashAnimation.setAutoReverse(true);
//configure gridView cell properties //configure gridView cell properties
DoubleBinding cellSize = Toolbar.getDefault(controller).sizeSliderValue().add(75); DoubleBinding cellSize = controller.thumbnailSizeProperty().add(75);
gridView.cellHeightProperty().bind(cellSize); gridView.cellHeightProperty().bind(cellSize);
gridView.cellWidthProperty().bind(cellSize); gridView.cellWidthProperty().bind(cellSize);
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell()); gridView.setCellFactory((GridView<Long> param) -> new DrawableCell());
BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected()); BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected());
catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
Platform.runLater(() -> { Platform.runLater(() -> {
try { try {
TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller);
@ -441,9 +442,9 @@ public class GroupPane extends BorderPane {
tagSelectedSplitMenu.getItems().setAll(selTagMenues); tagSelectedSplitMenu.getItems().setAll(selTagMenues);
} }
}); });
}); });
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(Category.FIVE, controller); CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(Category.FIVE, controller);
catSelectedSplitMenu.setOnAction(cat5SelectedAction); catSelectedSplitMenu.setOnAction(cat5SelectedAction);
catSelectedSplitMenu.setText(cat5SelectedAction.getText()); catSelectedSplitMenu.setText(cat5SelectedAction.getText());
@ -455,12 +456,12 @@ public class GroupPane extends BorderPane {
catSelectedSplitMenu.getItems().setAll(categoryMenues); catSelectedSplitMenu.getItems().setAll(categoryMenues);
} }
}); });
slideShowToggle.getStyleClass().remove("radio-button"); slideShowToggle.getStyleClass().remove("radio-button");
slideShowToggle.getStyleClass().add("toggle-button"); slideShowToggle.getStyleClass().add("toggle-button");
tileToggle.getStyleClass().remove("radio-button"); tileToggle.getStyleClass().remove("radio-button");
tileToggle.getStyleClass().add("toggle-button"); tileToggle.getStyleClass().add("toggle-button");
bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText()); bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText());
headerLabel.setText(Bundle.GroupPane_hederLabel_displayText()); headerLabel.setText(Bundle.GroupPane_hederLabel_displayText());
catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText()); catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText());
@ -480,12 +481,12 @@ public class GroupPane extends BorderPane {
//listen to toggles and update view state //listen to toggles and update view state
slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get())); slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get()));
tileToggle.setOnAction(onAction -> activateTileViewer()); tileToggle.setOnAction(onAction -> activateTileViewer());
controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState)); controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState));
addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler); addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler);
gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler()); gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler());
ActionUtils.configureButton(undoAction, undoButton); ActionUtils.configureButton(undoAction, undoButton);
ActionUtils.configureButton(redoAction, redoButton); ActionUtils.configureButton(redoAction, redoButton);
ActionUtils.configureButton(forwardAction, forwardButton); ActionUtils.configureButton(forwardAction, forwardButton);
@ -501,7 +502,7 @@ public class GroupPane extends BorderPane {
nextButton.setEffect(null); nextButton.setEffect(null);
onAction.handle(actionEvent); onAction.handle(actionEvent);
}); });
nextGroupAction.disabledProperty().addListener((Observable observable) -> { nextGroupAction.disabledProperty().addListener((Observable observable) -> {
boolean newValue = nextGroupAction.isDisabled(); boolean newValue = nextGroupAction.isDisabled();
nextButton.setEffect(newValue ? null : DROP_SHADOW); nextButton.setEffect(newValue ? null : DROP_SHADOW);
@ -521,7 +522,7 @@ public class GroupPane extends BorderPane {
scrollToFileID(newFileId); scrollToFileID(newFileId);
} }
}); });
setViewState(controller.viewState().get()); setViewState(controller.viewState().get());
} }
@ -531,16 +532,16 @@ public class GroupPane extends BorderPane {
if (newFileID == null) { if (newFileID == null) {
return; //scrolling to no file doesn't make sense, so abort. return; //scrolling to no file doesn't make sense, so abort.
} }
final ObservableList<Long> fileIds = gridView.getItems(); final ObservableList<Long> fileIds = gridView.getItems();
int selectedIndex = fileIds.indexOf(newFileID); int selectedIndex = fileIds.indexOf(newFileID);
if (selectedIndex == -1) { if (selectedIndex == -1) {
//somehow we got passed a file id that isn't in the curent group. //somehow we got passed a file id that isn't in the curent group.
//this should never happen, but if it does everything is going to fail, so abort. //this should never happen, but if it does everything is going to fail, so abort.
return; return;
} }
getScrollBar().ifPresent(scrollBar -> { getScrollBar().ifPresent(scrollBar -> {
DrawableCell cell = cellMap.get(newFileID); DrawableCell cell = cellMap.get(newFileID);
@ -567,14 +568,14 @@ public class GroupPane extends BorderPane {
} }
cell = cellMap.get(newFileID); cell = cellMap.get(newFileID);
} }
final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal()); final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal());
Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal()); Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal());
//while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates //while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates
int i = 0; int i = 0;
while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) { while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) {
if (tileBounds.getMinY() < gridViewBounds.getMinY()) { if (tileBounds.getMinY() < gridViewBounds.getMinY()) {
scrollBar.decrement(); scrollBar.decrement();
} else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) { } else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) {
@ -592,13 +593,13 @@ public class GroupPane extends BorderPane {
* @param grouping the new grouping assigned to this group * @param grouping the new grouping assigned to this group
*/ */
void setViewState(GroupViewState viewState) { void setViewState(GroupViewState viewState) {
if (isNull(viewState) || isNull(viewState.getGroup())) { if (isNull(viewState) || isNull(viewState.getGroup())) {
if (nonNull(getGroup())) { if (nonNull(getGroup())) {
getGroup().getFileIDs().removeListener(filesSyncListener); getGroup().getFileIDs().removeListener(filesSyncListener);
} }
this.grouping.set(null); this.grouping.set(null);
Platform.runLater(() -> { Platform.runLater(() -> {
gridView.getItems().setAll(Collections.emptyList()); gridView.getItems().setAll(Collections.emptyList());
setCenter(null); setCenter(null);
@ -610,18 +611,18 @@ public class GroupPane extends BorderPane {
cellMap.clear(); cellMap.clear();
} }
}); });
} else { } else {
if (getGroup() != viewState.getGroup()) { if (getGroup() != viewState.getGroup()) {
if (nonNull(getGroup())) { if (nonNull(getGroup())) {
getGroup().getFileIDs().removeListener(filesSyncListener); getGroup().getFileIDs().removeListener(filesSyncListener);
} }
this.grouping.set(viewState.getGroup()); this.grouping.set(viewState.getGroup());
getGroup().getFileIDs().addListener(filesSyncListener); getGroup().getFileIDs().addListener(filesSyncListener);
final String header = getHeaderString(); final String header = getHeaderString();
Platform.runLater(() -> { Platform.runLater(() -> {
gridView.getItems().setAll(getGroup().getFileIDs()); gridView.getItems().setAll(getGroup().getFileIDs());
slideShowToggle.setDisable(gridView.getItems().isEmpty()); slideShowToggle.setDisable(gridView.getItems().isEmpty());
@ -636,14 +637,14 @@ public class GroupPane extends BorderPane {
} }
} }
} }
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
private void resetScrollBar() { private void resetScrollBar() {
getScrollBar().ifPresent((scrollBar) -> { getScrollBar().ifPresent((scrollBar) -> {
scrollBar.setValue(0); scrollBar.setValue(0);
}); });
} }
@ThreadConfined(type = ThreadType.JFX) @ThreadConfined(type = ThreadType.JFX)
private Optional<ScrollBar> getScrollBar() { private Optional<ScrollBar> getScrollBar() {
if (gridView == null || gridView.getSkin() == null) { if (gridView == null || gridView.getSkin() == null) {
@ -651,16 +652,16 @@ public class GroupPane extends BorderPane {
} }
return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); //NON-NLS return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); //NON-NLS
} }
void makeSelection(Boolean shiftDown, Long newFileID) { void makeSelection(Boolean shiftDown, Long newFileID) {
if (shiftDown) { if (shiftDown) {
//TODO: do more hear to implement slicker multiselect //TODO: do more hear to implement slicker multiselect
int endIndex = grouping.get().getFileIDs().indexOf(newFileID); int endIndex = grouping.get().getFileIDs().indexOf(newFileID);
int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt(); int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt();
endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt(); endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt();
List<Long> subList = grouping.get().getFileIDs().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().getFileIDs().size()) + 1); List<Long> subList = grouping.get().getFileIDs().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().getFileIDs().size()) + 1);
selectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()])); selectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()]));
selectionModel.select(newFileID); selectionModel.select(newFileID);
} else { } else {
@ -668,11 +669,11 @@ public class GroupPane extends BorderPane {
selectionModel.clearAndSelect(newFileID); selectionModel.clearAndSelect(newFileID);
} }
} }
private class DrawableCell extends GridCell<Long> { private class DrawableCell extends GridCell<Long> {
private final DrawableTile tile = new DrawableTile(GroupPane.this, controller); private final DrawableTile tile = new DrawableTile(GroupPane.this, controller);
DrawableCell() { DrawableCell() {
itemProperty().addListener((ObservableValue<? extends Long> observable, Long oldValue, Long newValue) -> { itemProperty().addListener((ObservableValue<? extends Long> observable, Long oldValue, Long newValue) -> {
if (oldValue != null) { if (oldValue != null) {
@ -688,19 +689,19 @@ public class GroupPane extends BorderPane {
} }
} }
cellMap.put(newValue, DrawableCell.this); cellMap.put(newValue, DrawableCell.this);
} }
}); });
setGraphic(tile); setGraphic(tile);
} }
@Override @Override
protected void updateItem(Long item, boolean empty) { protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
tile.setFile(item); tile.setFile(item);
} }
void resetItem() { void resetItem() {
tile.setFile(null); tile.setFile(null);
} }
@ -711,10 +712,10 @@ public class GroupPane extends BorderPane {
* arrows) * arrows)
*/ */
private class KeyboardHandler implements EventHandler<KeyEvent> { private class KeyboardHandler implements EventHandler<KeyEvent> {
@Override @Override
public void handle(KeyEvent t) { public void handle(KeyEvent t) {
if (t.getEventType() == KeyEvent.KEY_PRESSED) { if (t.getEventType() == KeyEvent.KEY_PRESSED) {
switch (t.getCode()) { switch (t.getCode()) {
case SHIFT: case SHIFT:
@ -757,51 +758,56 @@ public class GroupPane extends BorderPane {
t.consume(); t.consume();
break; break;
} }
if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) { if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) {
selectAllFiles(); selectAllFiles();
t.consume(); t.consume();
} }
if (selectionModel.getSelected().isEmpty() == false) { ObservableSet<Long> selected = selectionModel.getSelected();
switch (t.getCode()) { if (selected.isEmpty() == false) {
case NUMPAD0: Category cat = keyCodeToCat(t.getCode());
case DIGIT0: if (cat != null) {
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.ZERO), ""); new CategorizeAction(controller, cat, selected).handle(null);
break;
case NUMPAD1:
case DIGIT1:
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.ONE), "");
break;
case NUMPAD2:
case DIGIT2:
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.TWO), "");
break;
case NUMPAD3:
case DIGIT3:
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.THREE), "");
break;
case NUMPAD4:
case DIGIT4:
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.FOUR), "");
break;
case NUMPAD5:
case DIGIT5:
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.FIVE), "");
break;
} }
} }
} }
} }
private Category keyCodeToCat(KeyCode t) {
if (t != null) {
switch (t) {
case NUMPAD0:
case DIGIT0:
return Category.ZERO;
case NUMPAD1:
case DIGIT1:
return Category.ONE;
case NUMPAD2:
case DIGIT2:
return Category.TWO;
case NUMPAD3:
case DIGIT3:
return Category.THREE;
case NUMPAD4:
case DIGIT4:
return Category.FOUR;
case NUMPAD5:
case DIGIT5:
return Category.FIVE;
}
}
return null;
}
private void handleArrows(KeyEvent t) { private void handleArrows(KeyEvent t) {
Long lastSelectFileId = selectionModel.lastSelectedProperty().get(); Long lastSelectFileId = selectionModel.lastSelectedProperty().get();
int lastSelectedIndex = lastSelectFileId != null int lastSelectedIndex = lastSelectFileId != null
? grouping.get().getFileIDs().indexOf(lastSelectFileId) ? grouping.get().getFileIDs().indexOf(lastSelectFileId)
: Optional.ofNullable(selectionAnchorIndex).orElse(0); : Optional.ofNullable(selectionAnchorIndex).orElse(0);
final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1); final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1);
final Map<KeyCode, Integer> tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1); final Map<KeyCode, Integer> tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1);
// implement proper keyboard based multiselect // implement proper keyboard based multiselect
@ -820,17 +826,19 @@ public class GroupPane extends BorderPane {
} }
} }
} }
private class MouseHandler implements EventHandler<MouseEvent> { private class MouseHandler implements EventHandler<MouseEvent> {
private ContextMenu buildContextMenu() { private ContextMenu buildContextMenu() {
ArrayList<MenuItem> menuItems = new ArrayList<>(); ArrayList<MenuItem> menuItems = new ArrayList<>();
menuItems.add(new CategorizeAction(controller).getPopupMenu());
menuItems.add(new AddDrawableTagAction(controller).getPopupMenu()); menuItems.add(CategorizeAction.getCategoriesMenu(controller));
menuItems.add(AddTagAction.getTagMenu(controller));
Collection<? extends ContextMenuActionsProvider> menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class); Collection<? extends ContextMenuActionsProvider> menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class);
for (ContextMenuActionsProvider provider : menuProviders) { for (ContextMenuActionsProvider provider : menuProviders) {
for (final Action act : provider.getActions()) { for (final Action act : provider.getActions()) {
if (act instanceof Presenter.Popup) { if (act instanceof Presenter.Popup) {
@ -847,12 +855,12 @@ public class GroupPane extends BorderPane {
}); });
}); });
menuItems.add(extractMenuItem); menuItems.add(extractMenuItem);
ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{})); ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{}));
contextMenu.setAutoHide(true); contextMenu.setAutoHide(true);
return contextMenu; return contextMenu;
} }
@Override @Override
public void handle(MouseEvent t) { public void handle(MouseEvent t) {
switch (t.getButton()) { switch (t.getButton()) {
@ -873,7 +881,7 @@ public class GroupPane extends BorderPane {
if (contextMenu == null) { if (contextMenu == null) {
contextMenu = buildContextMenu(); contextMenu = buildContextMenu();
} }
contextMenu.hide(); contextMenu.hide();
contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY()); contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY());
} }

View File

@ -201,7 +201,7 @@ public class MetaDataPane extends DrawableUIBase {
} }
@Override @Override
Task<Image> newReadImageTask(DrawableFile<?> file) { Task<Image> newReadImageTask(DrawableFile file) {
return file.getThumbnailTask(); return file.getThumbnailTask();
} }

View File

@ -56,7 +56,6 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile;
import org.sleuthkit.autopsy.imagegallery.gui.VideoPlayer; import org.sleuthkit.autopsy.imagegallery.gui.VideoPlayer;
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableUIBase.exec; import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableUIBase.exec;
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableView.CAT_BORDER_WIDTH; import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableView.CAT_BORDER_WIDTH;
import org.sleuthkit.datamodel.AbstractContent;
/** /**
* Displays the files of a group one at a time. Designed to be embedded in a * Displays the files of a group one at a time. Designed to be embedded in a
@ -186,16 +185,16 @@ public class SlideShowView extends DrawableTileBase {
synchronized protected void updateContent() { synchronized protected void updateContent() {
disposeContent(); disposeContent();
if (getFile().isPresent()) { if (getFile().isPresent()) {
DrawableFile<?> file = getFile().get(); DrawableFile file = getFile().get();
if (file.isVideo()) { if (file.isVideo()) {
doMediaLoadTask((VideoFile<?>) file); doMediaLoadTask((VideoFile) file);
} else { } else {
doReadImageTask(file); doReadImageTask(file);
} }
} }
} }
synchronized private Node doMediaLoadTask(VideoFile<?> file) { synchronized private Node doMediaLoadTask(VideoFile file) {
//specially handling for videos //specially handling for videos
MediaLoadTask myTask = new MediaLoadTask(file); MediaLoadTask myTask = new MediaLoadTask(file);
@ -225,7 +224,7 @@ public class SlideShowView extends DrawableTileBase {
} }
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void showMedia(DrawableFile<?> file, Task<Node> mediaTask) { private void showMedia(DrawableFile file, Task<Node> mediaTask) {
//Note that all error conditions are allready logged in readImageTask.succeeded() //Note that all error conditions are allready logged in readImageTask.succeeded()
try { try {
Node mediaNode = mediaTask.get(); Node mediaNode = mediaTask.get();
@ -265,7 +264,7 @@ public class SlideShowView extends DrawableTileBase {
*/ */
@Override @Override
protected String getTextForLabel() { protected String getTextForLabel() {
return getFile().map(AbstractContent::getName).orElse("") + " " + getSupplementalText(); return getFile().map(DrawableFile::getName).orElse("") + " " + getSupplementalText();
} }
/** /**
@ -308,7 +307,7 @@ public class SlideShowView extends DrawableTileBase {
@Override @Override
@ThreadConfined(type = ThreadType.ANY) @ThreadConfined(type = ThreadType.ANY)
public Category updateCategory() { public Category updateCategory() {
Optional<DrawableFile<?>> file = getFile(); Optional<DrawableFile> file = getFile();
if (file.isPresent()) { if (file.isPresent()) {
Category updateCategory = super.updateCategory(); Category updateCategory = super.updateCategory();
Platform.runLater(() -> getGroupPane().syncCatToggle(file.get())); Platform.runLater(() -> getGroupPane().syncCatToggle(file.get()));
@ -319,7 +318,7 @@ public class SlideShowView extends DrawableTileBase {
} }
@Override @Override
Task<Image> newReadImageTask(DrawableFile<?> file) { Task<Image> newReadImageTask(DrawableFile file) {
return file.getReadFullSizeImageTask(); return file.getReadFullSizeImageTask();
} }
@ -328,9 +327,9 @@ public class SlideShowView extends DrawableTileBase {
"MediaLoadTask.messageText=Reading video: {0}"}) "MediaLoadTask.messageText=Reading video: {0}"})
private class MediaLoadTask extends Task<Node> { private class MediaLoadTask extends Task<Node> {
private final VideoFile<?> file; private final VideoFile file;
MediaLoadTask(VideoFile<?> file) { MediaLoadTask(VideoFile file) {
updateMessage(Bundle.MediaLoadTask_messageText(file.getName())); updateMessage(Bundle.MediaLoadTask_messageText(file.getName()));
this.file = file; this.file = file;
} }

View File

@ -1,4 +1,4 @@
.groupTreeCell{ .groupCell{
-fx-indent:5; /* default indent is 10 */ -fx-indent:5; /* default indent is 10 */
} }

View File

@ -0,0 +1,306 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
import static java.util.Objects.isNull;
import java.util.Optional;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.Cell;
import javafx.scene.control.Control;
import javafx.scene.control.Labeled;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.OverrunStyle;
import javafx.scene.control.Tooltip;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.datamodel.TagName;
/**
* A Factory for Cells to use in a ListView<DrawableGroup> or
* TreeView<GroupTreeNode>
*/
class GroupCellFactory {
/**
* icon to use if a cell doesn't represent a group but just a folder(with no
* DrawableFiles) in the file system hierarchy.
*/
private static final Image EMPTY_FOLDER_ICON = new Image("/org/sleuthkit/autopsy/imagegallery/images/folder.png"); //NON-NLS
private final ReadOnlyObjectProperty<GroupComparators<?>> sortOrder;
private final ImageGalleryController controller;
GroupCellFactory(ImageGalleryController controller, ReadOnlyObjectProperty<GroupComparators<?>> sortOrderProperty) {
this.controller = controller;
this.sortOrder = sortOrderProperty;
}
GroupListCell getListCell(ListView<DrawableGroup> listview) {
return initCell(new GroupListCell());
}
GroupTreeCell getTreeCell(TreeView<?> treeView) {
return initCell(new GroupTreeCell());
}
/**
* remove the listener when it is not needed any more
*
* @param listener
* @param oldGroup
*/
private void removeListeners(InvalidationListener listener, DrawableGroup oldGroup) {
sortOrder.removeListener(listener);
oldGroup.getFileIDs().removeListener(listener);
oldGroup.seenProperty().removeListener(listener);
oldGroup.uncatCountProperty().removeListener(listener);
oldGroup.hashSetHitsCountProperty().removeListener(listener);
}
private void addListeners(InvalidationListener listener, DrawableGroup group) {
//if the sort order changes, update the counts displayed to match the sorted by property
sortOrder.addListener(listener);
//if number of files in this group changes (eg a file is recategorized), update counts via listener
group.getFileIDs().addListener(listener);
group.uncatCountProperty().addListener(listener);
group.hashSetHitsCountProperty().addListener(listener);
//if the seen state of this group changes update its style
group.seenProperty().addListener(listener);
}
private <X extends Cell<?> & GroupCell<?>> X initCell(X cell) {
/*
* reduce indent of TreeCells to 5, default is 10 which uses up a lot of
* space. Define seen and unseen styles
*/
cell.getStylesheets().add(GroupCellFactory.class.getResource("GroupCell.css").toExternalForm()); //NON-NLS
cell.getStyleClass().add("groupCell"); //NON-NLS
//since end of path is probably more interesting put ellipsis at front
cell.setTextOverrun(OverrunStyle.LEADING_ELLIPSIS);
Platform.runLater(() -> cell.prefWidthProperty().bind(cell.getView().widthProperty().subtract(15)));
return cell;
}
private <X extends Cell<?> & GroupCell<?>> void updateGroup(X cell, DrawableGroup group) {
addListeners(cell.getGroupListener(), group);
//and use icon corresponding to group type
final Node graphic = (group.getGroupByAttribute() == DrawableAttribute.TAGS)
? controller.getTagsManager().getGraphic((TagName) group.getGroupByValue())
: group.getGroupKey().getGraphic();
final String text = getCellText(cell);
final String style = getSeenStyleClass(cell);
Platform.runLater(() -> {
cell.setTooltip(new Tooltip(text));
cell.setGraphic(graphic);
cell.setText(text);
cell.setStyle(style);
});
}
private <X extends Labeled & GroupCell<?>> void clearCell(X cell) {
Platform.runLater(() -> {
cell.setTooltip(null);
cell.setText(null);
cell.setGraphic(null);
cell.setStyle("");
});
}
/**
* return the styleClass to apply based on the assigned group's seen status
*
* @return the style class to apply
*/
private String getSeenStyleClass(GroupCell<?> cell) {
return cell.getGroup()
.map(DrawableGroup::isSeen)
.map(seen -> seen ? "" : "-fx-font-weight:bold;") //NON-NLS
.orElse(""); //if item is null or group is null
}
/**
* get the counts part of the text to apply to this cell, including
* parentheses
*
* @return get the counts part of the text to apply to this cell
*/
private String getCountsText(GroupCell<?> cell) {
return cell.getGroup()
.map(group ->
" (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
? group.getSize()
: sortOrder.get().getFormattedValueOfGroup(group)) + ")"
).orElse(""); //if item is null or group is null
}
private String getCellText(GroupCell<?> cell) {
return cell.getGroupName() + getCountsText(cell);
}
private class GroupTreeCell extends TreeCell<GroupTreeNode> implements GroupCell<TreeView<GroupTreeNode>> {
private final InvalidationListener groupListener = new GroupListener<>(this);
/**
* reference to group files listener that allows us to remove it from a
* group when a new group is assigned to this Cell
*/
@Override
public InvalidationListener getGroupListener() {
return groupListener;
}
@Override
public TreeView<GroupTreeNode> getView() {
return getTreeView();
}
@Override
public String getGroupName() {
return Optional.ofNullable(getItem())
.map(treeNode -> StringUtils.defaultIfBlank(treeNode.getPath(), DrawableGroup.getBlankGroupName()))
.orElse("");
}
@Override
public Optional<DrawableGroup> getGroup() {
return Optional.ofNullable(getItem())
.map(GroupTreeNode::getGroup);
}
@Override
protected synchronized void updateItem(final GroupTreeNode newItem, boolean empty) {
//if there was a previous group, remove the listeners
getGroup().ifPresent(oldGroup -> removeListeners(getGroupListener(), oldGroup));
super.updateItem(newItem, empty);
if (isNull(newItem) || empty) {
clearCell(this);
} else {
DrawableGroup newGroup = newItem.getGroup();
if (isNull(newGroup)) {
//this cod epath should only be invoked for non-group Tree
final String groupName = getGroupName();
//"dummy" group in file system tree <=> a folder with no drawables
Platform.runLater(() -> {
setTooltip(new Tooltip(groupName));
setText(groupName);
setGraphic(new ImageView(EMPTY_FOLDER_ICON));
setStyle("");
});
} else {
updateGroup(this, newGroup);
}
}
}
}
private class GroupListCell extends ListCell<DrawableGroup> implements GroupCell<ListView<DrawableGroup>> {
private final InvalidationListener groupListener = new GroupListener<>(this);
/**
* reference to group files listener that allows us to remove it from a
* group when a new group is assigned to this Cell
*/
@Override
public InvalidationListener getGroupListener() {
return groupListener;
}
@Override
public ListView<DrawableGroup> getView() {
return getListView();
}
@Override
public String getGroupName() {
return Optional.ofNullable(getItem())
.map(group -> group.getGroupByValueDislpayName())
.orElse("");
}
@Override
public Optional<DrawableGroup> getGroup() {
return Optional.ofNullable(getItem());
}
@Override
protected synchronized void updateItem(final DrawableGroup newGroup, boolean empty) {
//if there was a previous group, remove the listeners
getGroup().ifPresent(oldGroup -> removeListeners(getGroupListener(), oldGroup));
super.updateItem(newGroup, empty);
if (isNull(newGroup) || empty) {
clearCell(this);
} else {
updateGroup(this, newGroup);
}
}
}
private interface GroupCell<X extends Control> {
String getGroupName();
X getView();
Optional<DrawableGroup> getGroup();
InvalidationListener getGroupListener();
}
private class GroupListener<X extends Labeled & GroupCell<?>> implements InvalidationListener {
private final X cell;
GroupListener(X cell) {
this.cell = cell;
}
@Override
public void invalidated(Observable o) {
final String text = getCellText(cell);
final String style = getSeenStyleClass(cell);
Platform.runLater(() -> {
cell.setText(text);
cell.setTooltip(new Tooltip(text));
cell.setStyle(style);
});
}
}
}

View File

@ -18,21 +18,18 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.gui.navpanel; package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
import com.google.common.collect.ImmutableList;
import java.util.Comparator; import java.util.Comparator;
import java.util.function.Function; import java.util.function.Function;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
/**
*
*/
@NbBundle.Messages({"GroupComparators.uncategorizedCount=Uncategorized Count", @NbBundle.Messages({"GroupComparators.uncategorizedCount=Uncategorized Count",
"GroupComparators.groupName=Group Name", "GroupComparators.groupName=Group Name",
"GroupComparators.hitCount=Hit Count", "GroupComparators.hitCount=Hit Count",
"GroupComparators.groupSize=Group Size", "GroupComparators.groupSize=Group Size",
"GroupComparators.hitDensity=Hit Density"}) "GroupComparators.hitDensity=Hit Density"})
final class GroupComparators<T extends Comparable<T>> implements Comparator<DrawableGroup> { final class GroupComparators<T extends Comparable<T>> implements Comparator<DrawableGroup> {
static final GroupComparators<Long> UNCATEGORIZED_COUNT = static final GroupComparators<Long> UNCATEGORIZED_COUNT =
@ -50,10 +47,10 @@ final class GroupComparators<T extends Comparable<T>> implements Comparator<Draw
static final GroupComparators<Double> HIT_FILE_RATIO = static final GroupComparators<Double> HIT_FILE_RATIO =
new GroupComparators<>(Bundle.GroupComparators_hitDensity(), DrawableGroup::getHashHitDensity, density -> String.format("%.2f", density) + "%", true); //NON-NLS new GroupComparators<>(Bundle.GroupComparators_hitDensity(), DrawableGroup::getHashHitDensity, density -> String.format("%.2f", density) + "%", true); //NON-NLS
private final static ImmutableList<GroupComparators<?>> values = ImmutableList.of(UNCATEGORIZED_COUNT, ALPHABETICAL, HIT_COUNT, FILE_COUNT, HIT_FILE_RATIO); private final static ObservableList<GroupComparators<?>> values = FXCollections.observableArrayList(UNCATEGORIZED_COUNT, ALPHABETICAL, HIT_COUNT, FILE_COUNT, HIT_FILE_RATIO);
public static ImmutableList<GroupComparators<?>> getValues() { public static ObservableList<GroupComparators<?>> getValues() {
return values; return FXCollections.unmodifiableObservableList(values);
} }
private final Function<DrawableGroup, T> extractor; private final Function<DrawableGroup, T> extractor;

View File

@ -1,174 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
import static java.util.Objects.isNull;
import java.util.Optional;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.ListCell;
import javafx.scene.control.OverrunStyle;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javax.annotation.Nonnull;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.datamodel.TagName;
/**
*
*/
class GroupListCell extends ListCell<DrawableGroup> {
/**
* icon to use if this cell's TreeNode doesn't represent a group but just a
* folder(with no DrawableFiles) in the file system hierarchy.
*/
private static final Image EMPTY_FOLDER_ICON =
new Image(GroupTreeCell.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/folder.png")); //NON-NLS
/**
* reference to group files listener that allows us to remove it from a
* group when a new group is assigned to this Cell
*/
private final InvalidationListener fileCountListener = (Observable o) -> {
final String text = getGroupName() + getCountsText();
Platform.runLater(() -> {
setText(text);
setTooltip(new Tooltip(text));
});
};
/**
* reference to group seen listener that allows us to remove it from a group
* when a new group is assigned to this Cell
*/
private final InvalidationListener seenListener = (Observable o) -> {
final String style = getSeenStyleClass();
Platform.runLater(() -> setStyle(style));
};
private final ReadOnlyObjectProperty<GroupComparators<?>> sortOrder;
private final ImageGalleryController controller;
GroupListCell(ImageGalleryController controller, ReadOnlyObjectProperty<GroupComparators<?>> sortOrderProperty) {
this.controller = controller;
this.sortOrder = sortOrderProperty;
getStylesheets().add(GroupTreeCell.class.getResource("GroupTreeCell.css").toExternalForm()); //NON-NLS
getStyleClass().add("groupTreeCell"); //reduce indent to 5, default is 10 which uses up a lot of space. NON-NLS
//since end of path is probably more interesting put ellipsis at front
setTextOverrun(OverrunStyle.LEADING_ELLIPSIS);
Platform.runLater(() -> prefWidthProperty().bind(getListView().widthProperty().subtract(15)));
}
@Override
protected synchronized void updateItem(final DrawableGroup group, boolean empty) {
//if there was a previous group, remove the listeners
Optional.ofNullable(getItem())
.ifPresent(oldGroup -> {
sortOrder.removeListener(fileCountListener);
oldGroup.getFileIDs().removeListener(fileCountListener);
oldGroup.seenProperty().removeListener(seenListener);
oldGroup.uncatCountProperty().removeListener(fileCountListener);
oldGroup.hashSetHitsCountProperty().removeListener(fileCountListener);
});
super.updateItem(group, empty);
if (isNull(group) || empty) {
Platform.runLater(() -> {
setTooltip(null);
setText(null);
setGraphic(null);
setStyle("");
});
} else {
final String text = getGroupName() + getCountsText();
String style;
Node icon;
if (isNull(group)) {
//"dummy" group in file system tree <=> a folder with no drawables
icon = new ImageView(EMPTY_FOLDER_ICON);
style = "";
} else {
//if number of files in this group changes (eg a file is recategorized), update counts via listener
group.getFileIDs().addListener(fileCountListener);
group.uncatCountProperty().addListener(fileCountListener);
group.hashSetHitsCountProperty().addListener(fileCountListener);
sortOrder.addListener(fileCountListener);
//if the seen state of this group changes update its style
group.seenProperty().addListener(seenListener);
//and use icon corresponding to group type
icon = (group.getGroupByAttribute() == DrawableAttribute.TAGS)
? controller.getTagsManager().getGraphic((TagName) group.getGroupByValue())
: group.getGroupKey().getGraphic();
style = getSeenStyleClass();
}
Platform.runLater(() -> {
setTooltip(new Tooltip(text));
setGraphic(icon);
setText(text);
setStyle(style);
});
}
}
private String getGroupName() {
return Optional.ofNullable(getItem())
.map(group -> group.getGroupByValueDislpayName())
.orElse("");
}
/**
* return the styleClass to apply based on the assigned group's seen status
*
* @return the style class to apply
*/
@Nonnull
private String getSeenStyleClass() {
return Optional.ofNullable(getItem())
.map(DrawableGroup::isSeen)
.map(seen -> seen ? "" : "-fx-font-weight:bold;") //NON-NLS
.orElse(""); //if item is null or group is null
}
/**
* get the counts part of the text to apply to this cell, including
* parentheses
*
* @return get the counts part of the text to apply to this cell
*/
@Nonnull
private String getCountsText() {
return Optional.ofNullable(getItem())
.map(group ->
" (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
? group.getSize()
: sortOrder.get().getFormattedValueOfGroup(group)) + ")"
).orElse(""); //if item is null or group is null
}
}

View File

@ -72,7 +72,8 @@ final public class GroupTree extends NavPanel<TreeItem<GroupTreeNode>> {
getToolBar().visibleProperty().bind(groupedByPath.not()); getToolBar().visibleProperty().bind(groupedByPath.not());
getToolBar().managedProperty().bind(groupedByPath.not()); getToolBar().managedProperty().bind(groupedByPath.not());
groupTree.setCellFactory(treeView -> new GroupTreeCell(getController(), getSortByBox().getSelectionModel().selectedItemProperty())); GroupCellFactory groupCellFactory = new GroupCellFactory(getController(), comparatorProperty());
groupTree.setCellFactory(groupCellFactory::getTreeCell);
groupTree.setShowRoot(false); groupTree.setShowRoot(false);
getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> { getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
@ -151,4 +152,5 @@ final public class GroupTree extends NavPanel<TreeItem<GroupTreeNode>> {
return Arrays.asList(stripStart); return Arrays.asList(stripStart);
} }
} }
} }

View File

@ -1,193 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-16 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
import static java.util.Objects.isNull;
import java.util.Optional;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.Node;
import javafx.scene.control.OverrunStyle;
import javafx.scene.control.Tooltip;
import javafx.scene.control.TreeCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.datamodel.TagName;
/**
* A cell in the NavPanel tree that listens to its associated group's fileids
* and seen status,and updates GUI to reflect them.
*
* TODO: we should use getStyleClass().add() rather than setStyle but it didn't
* seem to work properly
*/
class GroupTreeCell extends TreeCell<GroupTreeNode> {
/**
* icon to use if this cell's TreeNode doesn't represent a group but just a
* folder(with no DrawableFiles) in the file system hierarchy.
*/
private static final Image EMPTY_FOLDER_ICON =
new Image(GroupTreeCell.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/folder.png")); //NON-NLS
/**
* reference to group files listener that allows us to remove it from a
* group when a new group is assigned to this Cell
*/
private final InvalidationListener fileCountListener = (Observable o) -> {
final String text = getGroupName() + getCountsText();
Platform.runLater(() -> {
setText(text);
setTooltip(new Tooltip(text));
});
};
/**
* reference to group seen listener that allows us to remove it from a group
* when a new group is assigned to this Cell
*/
private final InvalidationListener seenListener = (Observable o) -> {
final String style = getSeenStyleClass();
Platform.runLater(() -> {
setStyle(style);
});
};
private final ReadOnlyObjectProperty<GroupComparators<?>> sortOrder;
private final ImageGalleryController controller;
GroupTreeCell(ImageGalleryController controller, ReadOnlyObjectProperty<GroupComparators<?>> sortOrderProperty) {
this.controller = controller;
this.sortOrder = sortOrderProperty;
getStylesheets().add(GroupTreeCell.class.getResource("GroupTreeCell.css").toExternalForm()); //NON-NLS
getStyleClass().add("groupTreeCell"); //reduce indent to 5, default is 10 which uses up a lot of space. NON-NLS
//since end of path is probably more interesting put ellipsis at front
setTextOverrun(OverrunStyle.LEADING_ELLIPSIS);
Platform.runLater(() -> {
prefWidthProperty().bind(getTreeView().widthProperty().subtract(15));
});
}
/**
* {@inheritDoc }
*/
@Override
protected synchronized void updateItem(final GroupTreeNode treeNode, boolean empty) {
//if there was a previous group, remove the listeners
Optional.ofNullable(getItem())
.map(GroupTreeNode::getGroup)
.ifPresent(group -> {
sortOrder.addListener(fileCountListener);
group.getFileIDs().removeListener(fileCountListener);
group.hashSetHitsCountProperty().removeListener(fileCountListener);
group.seenProperty().removeListener(seenListener);
group.uncatCountProperty().removeListener(fileCountListener);
});
super.updateItem(treeNode, empty);
if (isNull(treeNode) || empty) {
Platform.runLater(() -> {
setTooltip(null);
setText(null);
setGraphic(null);
setStyle("");
});
} else {
DrawableGroup group = treeNode.getGroup();
if (isNull(group)) {
final String text = getGroupName();
//"dummy" group in file system tree <=> a folder with no drawables
Platform.runLater(() -> {
setTooltip(new Tooltip(text));
setText(text);
setGraphic(new ImageView(EMPTY_FOLDER_ICON));
setStyle("");
});
} else {
//if number of files in this group changes (eg a file is recategorized), update counts via listener
group.getFileIDs().addListener(fileCountListener);
group.uncatCountProperty().addListener(fileCountListener);
group.hashSetHitsCountProperty().addListener(fileCountListener);
sortOrder.addListener(fileCountListener);
//if the seen state of this group changes update its style
group.seenProperty().addListener(seenListener);
//and use icon corresponding to group type
Node icon = (group.getGroupByAttribute() == DrawableAttribute.TAGS)
? controller.getTagsManager().getGraphic((TagName) group.getGroupByValue())
: group.getGroupKey().getGraphic();
final String text = getGroupName() + getCountsText();
final String style = getSeenStyleClass();
Platform.runLater(() -> {
setTooltip(new Tooltip(text));
setGraphic(icon);
setText(text);
setStyle(style);
});
}
}
}
private String getGroupName() {
return Optional.ofNullable(getItem())
.map(treeNode -> StringUtils.defaultIfBlank(treeNode.getPath(), DrawableGroup.getBlankGroupName()))
.orElse("");
}
/**
* return the styleClass to apply based on the assigned group's seen status
*
* @return the style class to apply
*/
@Nonnull
private String getSeenStyleClass() {
return Optional.ofNullable(getItem())
.map(GroupTreeNode::getGroup)
.map(DrawableGroup::isSeen)
.map(seen -> seen ? "" : "-fx-font-weight:bold;") //NON-NLS
.orElse(""); //if item is null or group is null
}
/**
* get the counts part of the text to apply to this cell, including
* parentheses
*
* @return get the counts part of the text to apply to this cell
*/
@Nonnull
private String getCountsText() {
return Optional.ofNullable(getItem())
.map(GroupTreeNode::getGroup)
.map(group ->
" (" + (sortOrder.get() == GroupComparators.ALPHABETICAL
? group.getSize()
: sortOrder.get().getFormattedValueOfGroup(group)) + ")"
).orElse(""); //if item is null or group is null
}
}

View File

@ -79,8 +79,8 @@ final public class HashHitGroupList extends NavPanel<DrawableGroup> {
getBorderPane().setCenter(groupList); getBorderPane().setCenter(groupList);
sorted = getController().getGroupManager().getAnalyzedGroups().filtered((DrawableGroup t) -> t.getHashSetHitsCount() > 0).sorted(getDefaultComparator()); sorted = getController().getGroupManager().getAnalyzedGroups().filtered((DrawableGroup t) -> t.getHashSetHitsCount() > 0).sorted(getDefaultComparator());
groupList.setCellFactory(treeView -> new GroupListCell(getController(), getSortByBox().getSelectionModel().selectedItemProperty())); GroupCellFactory groupCellFactory = new GroupCellFactory(getController(), comparatorProperty());
groupList.setCellFactory(groupCellFactory::getListCell);
groupList.setItems(sorted); groupList.setItems(sorted);
} }

View File

@ -1,63 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.String?>
<?import javafx.collections.FXCollections?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tab?> <?import javafx.scene.control.Tab?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.control.ToolBar?> <?import javafx.scene.control.ToolBar?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.BorderPane?> <?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<fx:root closable="false" type="Tab" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<fx:root type="Tab" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" closable="false" >
<content> <content>
<BorderPane prefHeight="200.0" prefWidth="200.0" fx:id="borderPane"> <BorderPane fx:id="borderPane">
<top> <top>
<ToolBar fx:id="toolBar" BorderPane.alignment="CENTER"> <ToolBar fx:id="toolBar" minWidth="-Infinity" BorderPane.alignment="CENTER" />
<items>
<Label fx:id="sortByBoxLabel" text="Sort By:" />
<ComboBox fx:id="sortByBox" maxWidth="-Infinity" minWidth="-Infinity">
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:value="Item 1" />
<String fx:value="Item 2" />
<String fx:value="Item 3" />
</FXCollections>
</items>
</ComboBox>
<VBox alignment="CENTER_LEFT" prefHeight="-1.0" prefWidth="-1.0" spacing="2.0">
<children>
<RadioButton fx:id="ascRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="true" text="Ascending">
<graphic>
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../images/arrow_up.png" />
</image>
</ImageView>
</graphic>
<toggleGroup>
<ToggleGroup fx:id="orderGroup" />
</toggleGroup>
</RadioButton>
<RadioButton fx:id="descRadio" contentDisplay="LEFT" mnemonicParsing="false" selected="false" text="Descending" toggleGroup="$orderGroup">
<graphic>
<ImageView fitHeight="0.0" fitWidth="0.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../images/arrow_down.png" />
</image>
</ImageView>
</graphic>
</RadioButton>
</children>
</VBox>
</items>
</ToolBar>
</top> </top>
</BorderPane> </BorderPane>
</content> </content>

View File

@ -22,15 +22,13 @@ import com.google.common.eventbus.Subscribe;
import java.util.Comparator; import java.util.Comparator;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.SelectionModel; import javafx.scene.control.SelectionModel;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar; import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javax.swing.SortOrder;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
@ -38,6 +36,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.SortChooser;
/** /**
* Base class for Tabs in the left hand Navigation/Context area. * Base class for Tabs in the left hand Navigation/Context area.
@ -50,24 +49,10 @@ abstract class NavPanel<X> extends Tab {
@FXML @FXML
private ToolBar toolBar; private ToolBar toolBar;
@FXML
private ComboBox<GroupComparators<?>> sortByBox;
@FXML
private RadioButton ascRadio;
@FXML
private ToggleGroup orderGroup;
@FXML
private RadioButton descRadio;
@FXML
private Label sortByBoxLabel;
private final ImageGalleryController controller; private final ImageGalleryController controller;
private final GroupManager groupManager; private final GroupManager groupManager;
private final CategoryManager categoryManager; private final CategoryManager categoryManager;
private SortChooser<DrawableGroup, GroupComparators<?>> sortChooser;
NavPanel(ImageGalleryController controller) { NavPanel(ImageGalleryController controller) {
this.controller = controller; this.controller = controller;
@ -75,34 +60,35 @@ abstract class NavPanel<X> extends Tab {
this.categoryManager = controller.getCategoryManager(); this.categoryManager = controller.getCategoryManager();
} }
public ReadOnlyObjectProperty<GroupComparators<?>> comparatorProperty() {
return sortChooser.comparatorProperty();
}
@FXML @FXML
@NbBundle.Messages({"NavPanel.ascRadio.text=Ascending", @NbBundle.Messages({"NavPanel.ascRadio.text=Ascending",
"NavPanel.descRadio.text=Descending", "NavPanel.descRadio.text=Descending",
"NavPanel.sortByBoxLabel.text=Sort By:"}) "NavPanel.sortByBoxLabel.text=Sort By:"})
void initialize() { void initialize() {
assert borderPane != null : "fx:id=\"borderPane\" was not injected: check your FXML file 'NavPanel.fxml'."; assert borderPane != null : "fx:id=\"borderPane\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert toolBar != null : "fx:id=\"toolBar\" was not injected: check your FXML file 'NavPanel.fxml'."; assert toolBar != null : "fx:id=\"toolBar\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert ascRadio != null : "fx:id=\"ascRadio\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert orderGroup != null : "fx:id=\"orderGroup\" was not injected: check your FXML file 'NavPanel.fxml'.";
assert descRadio != null : "fx:id=\"descRadio\" was not injected: check your FXML file 'NavPanel.fxml'.";
sortByBox.getItems().setAll(GroupComparators.getValues()); sortChooser = new SortChooser<>(GroupComparators.getValues());
sortByBox.getSelectionModel().select(getDefaultComparator()); sortChooser.setComparator(getDefaultComparator());
orderGroup.selectedToggleProperty().addListener(order -> sortGroups()); sortChooser.sortOrderProperty().addListener(order -> sortGroups());
sortByBox.getSelectionModel().selectedItemProperty().addListener(observable -> { sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> {
sortGroups(); sortGroups();
//only need to listen to changes in category if we are sorting by/ showing the uncategorized count //only need to listen to changes in category if we are sorting by/ showing the uncategorized count
if (sortByBox.getSelectionModel().getSelectedItem() == GroupComparators.UNCATEGORIZED_COUNT) { if (newComparator == GroupComparators.UNCATEGORIZED_COUNT) {
categoryManager.registerListener(NavPanel.this); categoryManager.registerListener(NavPanel.this);
} else { } else {
categoryManager.unregisterListener(NavPanel.this); categoryManager.unregisterListener(NavPanel.this);
} }
});
ascRadio.setText(Bundle.NavPanel_ascRadio_text()); final SortChooser.ValueType valueType = newComparator == GroupComparators.ALPHABETICAL ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC;
descRadio.setText(Bundle.NavPanel_descRadio_text()); sortChooser.setValueType(valueType);
sortByBoxLabel.setText(Bundle.NavPanel_sortByBoxLabel_text()); });
toolBar.getItems().add(sortChooser);
//keep selection in sync with controller //keep selection in sync with controller
controller.viewState().addListener(observable -> { controller.viewState().addListener(observable -> {
Optional.ofNullable(controller.viewState().get()) Optional.ofNullable(controller.viewState().get())
@ -129,8 +115,8 @@ abstract class NavPanel<X> extends Tab {
*/ */
@ThreadConfined(type = ThreadConfined.ThreadType.JFX) @ThreadConfined(type = ThreadConfined.ThreadType.JFX)
Comparator<DrawableGroup> getComparator() { Comparator<DrawableGroup> getComparator() {
Comparator<DrawableGroup> comparator = sortByBox.getSelectionModel().getSelectedItem(); Comparator<DrawableGroup> comparator = sortChooser.getComparator();
return (orderGroup.getSelectedToggle() == ascRadio) return (sortChooser.getSortOrder() == SortOrder.ASCENDING)
? comparator ? comparator
: comparator.reversed(); : comparator.reversed();
} }
@ -194,22 +180,6 @@ abstract class NavPanel<X> extends Tab {
return toolBar; return toolBar;
} }
ComboBox<GroupComparators<?>> getSortByBox() {
return sortByBox;
}
RadioButton getAscRadio() {
return ascRadio;
}
ToggleGroup getOrderGroup() {
return orderGroup;
}
RadioButton getDescRadio() {
return descRadio;
}
ImageGalleryController getController() { ImageGalleryController getController() {
return controller; return controller;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

View File

@ -274,9 +274,10 @@ public class ExtractedContentViewer implements DataContentViewer {
} else { } else {
try { try {
// Get the associated artifact attribute and return its value as the ID // Get the associated artifact attribute and return its value as the ID
List<BlackboardAttribute> blackboardAttributes = artifact.getAttributes(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT); BlackboardAttribute blackboardAttribute = artifact.getAttribute(
if (!blackboardAttributes.isEmpty()) { new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
return blackboardAttributes.get(0).getValueLong(); if (blackboardAttribute != null) {
return blackboardAttribute.getValueLong();
} }
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting associated artifact attributes", ex); //NON-NLS logger.log(Level.SEVERE, "Error getting associated artifact attributes", ex); //NON-NLS

View File

@ -142,7 +142,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
try { try {
fileTypeDetector = new FileTypeDetector(); fileTypeDetector = new FileTypeDetector();
} catch (FileTypeDetector.FileTypeDetectorInitException ex) { } catch (FileTypeDetector.FileTypeDetectorInitException ex) {
throw new IngestModuleException(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.startUp.fileTypeDetectorInitializationException.msg")); throw new IngestModuleException(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.startUp.fileTypeDetectorInitializationException.msg"), ex);
} }
ingester = Server.getIngester(); ingester = Server.getIngester();
this.context = context; this.context = context;
@ -162,7 +162,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
String details = NbBundle.getMessage(this.getClass(), "SolrConnectionCheck.Port"); String details = NbBundle.getMessage(this.getClass(), "SolrConnectionCheck.Port");
logger.log(Level.SEVERE, "{0}: {1} {2}", new Object[]{msg, details, ex.toString()}); logger.log(Level.SEVERE, "{0}: {1} {2}", new Object[]{msg, details, ex.toString()});
services.postMessage(IngestMessage.createErrorMessage(KeywordSearchModuleFactory.getModuleName(), msg, details)); services.postMessage(IngestMessage.createErrorMessage(KeywordSearchModuleFactory.getModuleName(), msg, details));
throw new IngestModuleException(msg); throw new IngestModuleException(msg, ex);
} }
try { try {
kwsService.tryConnect(UserPreferences.getIndexingServerHost(), port); kwsService.tryConnect(UserPreferences.getIndexingServerHost(), port);
@ -171,7 +171,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
String details = ex.getMessage(); String details = ex.getMessage();
logger.log(Level.SEVERE, "{0}: {1} {2}", new Object[]{msg, details, ex.toString()}); logger.log(Level.SEVERE, "{0}: {1} {2}", new Object[]{msg, details, ex.toString()});
services.postMessage(IngestMessage.createErrorMessage(KeywordSearchModuleFactory.getModuleName(), msg, details)); services.postMessage(IngestMessage.createErrorMessage(KeywordSearchModuleFactory.getModuleName(), msg, details));
throw new IngestModuleException(msg); throw new IngestModuleException(msg, ex);
} }
} else { } else {
// for single-user cases need to verify connection to local SOLR service // for single-user cases need to verify connection to local SOLR service
@ -189,7 +189,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
String msg = NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.init.badInitMsg"); String msg = NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.init.badInitMsg");
String details = NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.init.tryStopSolrMsg", msg); String details = NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.init.tryStopSolrMsg", msg);
services.postMessage(IngestMessage.createErrorMessage(KeywordSearchModuleFactory.getModuleName(), msg, details)); services.postMessage(IngestMessage.createErrorMessage(KeywordSearchModuleFactory.getModuleName(), msg, details));
throw new IngestModuleException(msg); throw new IngestModuleException(msg, ex);
} }
try { try {
// make an actual query to verify that server is responding // make an actual query to verify that server is responding
@ -198,7 +198,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
} catch (KeywordSearchModuleException | NoOpenCoreException ex) { } catch (KeywordSearchModuleException | NoOpenCoreException ex) {
throw new IngestModuleException( throw new IngestModuleException(
NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.init.exception.errConnToSolr.msg", NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.init.exception.errConnToSolr.msg",
ex.getMessage())); ex.getMessage()), ex);
} }
// check if this job has any searchable keywords // check if this job has any searchable keywords

View File

@ -161,11 +161,11 @@ class SearchEngineURLQueryAnalyzer extends Extract {
} }
} catch (IOException e) { } catch (IOException e) {
throw new IngestModuleException("Was not able to load SEUQAMappings.xml: " + e.getLocalizedMessage()); //NON-NLS throw new IngestModuleException("Was not able to load SEUQAMappings.xml: " + e.getLocalizedMessage(), e); //NON-NLS
} catch (ParserConfigurationException pce) { } catch (ParserConfigurationException pce) {
throw new IngestModuleException("Unable to build XML parser: " + pce.getLocalizedMessage()); //NON-NLS throw new IngestModuleException("Unable to build XML parser: " + pce.getLocalizedMessage(), pce); //NON-NLS
} catch (SAXException sxe) { } catch (SAXException sxe) {
throw new IngestModuleException("Unable to parse XML file: " + sxe.getLocalizedMessage()); //NON-NLS throw new IngestModuleException("Unable to parse XML file: " + sxe.getLocalizedMessage(), sxe); //NON-NLS
} }
NodeList nlist = xmlinput.getElementsByTagName("SearchEngine"); //NON-NLS NodeList nlist = xmlinput.getElementsByTagName("SearchEngine"); //NON-NLS
@ -394,7 +394,7 @@ class SearchEngineURLQueryAnalyzer extends Extract {
String message = NbBundle String message = NbBundle
.getMessage(this.getClass(), "SearchEngineURLQueryAnalyzer.init.exception.msg", XMLFILE); .getMessage(this.getClass(), "SearchEngineURLQueryAnalyzer.init.exception.msg", XMLFILE);
logger.log(Level.SEVERE, message, e); logger.log(Level.SEVERE, message, e);
throw new IngestModuleException(message); throw new IngestModuleException(message, e);
} }
loadConfigFile(); loadConfigFile();

Some files were not shown because too many files have changed in this diff Show More