mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-15 09:17:42 +00:00
Merge branch 'release-4.1.0' into bb_user_defs_in_reports
This commit is contained in:
commit
d36fb5b913
@ -6,10 +6,6 @@
|
||||
<description>Builds, tests, and runs the project org.sleuthkit.autopsy.core</description>
|
||||
<import file="nbproject/build-impl.xml"/>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Verify that the TSK_HOME env variable is set -->
|
||||
<target name="findTSK">
|
||||
<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"/>
|
||||
</target>
|
||||
|
||||
|
||||
<target name="init" depends="basic-init,files-init,build-init,-javac-init">
|
||||
<!-- get additional deps -->
|
||||
<antcall target="getTSKJars" />
|
||||
|
@ -19,15 +19,13 @@
|
||||
package org.sleuthkit.autopsy.casemodule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.SleuthkitJNI;
|
||||
@ -35,66 +33,220 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskDataException;
|
||||
|
||||
/*
|
||||
* A background task that adds the given image to database using the Sleuthkit
|
||||
* JNI interface.
|
||||
*
|
||||
* It updates the given ProgressMonitor as it works through adding the image,
|
||||
* and et the end, calls the specified Callback.
|
||||
* A runnable that adds an image data source to the case database.
|
||||
*/
|
||||
class AddImageTask implements Runnable {
|
||||
|
||||
private final Logger logger = Logger.getLogger(AddImageTask.class.getName());
|
||||
|
||||
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 deviceId;
|
||||
private final String imagePath;
|
||||
String timeZone;
|
||||
boolean noFatOrphans;
|
||||
|
||||
private final String dataSourceId;
|
||||
private final String timeZone;
|
||||
private final boolean ignoreFatOrphanFiles;
|
||||
private final DataSourceProcessorProgressMonitor progressMonitor;
|
||||
private final DataSourceProcessorCallback callback;
|
||||
private boolean criticalErrorOccurred;
|
||||
|
||||
/*
|
||||
* A thread that updates the progressMonitor with the name of the directory
|
||||
* currently being processed by the AddImageTask
|
||||
* The cancellation requested flag and SleuthKit add image process are
|
||||
* 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;
|
||||
this.process = proc;
|
||||
/**
|
||||
* Adds the image to the case database.
|
||||
*/
|
||||
@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
|
||||
public void run() {
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
String currDir = process.currentDirectory();
|
||||
String currDir = tskAddImageProcess.currentDirectory();
|
||||
if (currDir != null) {
|
||||
if (!currDir.isEmpty()) {
|
||||
progressMonitor.setProgressText(
|
||||
@ -102,227 +254,20 @@ class AddImageTask implements Runnable {
|
||||
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);
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
// nothing to do, thread was interrupted externally
|
||||
// signaling the end of AddImageProcess
|
||||
} catch (InterruptedException expected) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,6 +146,7 @@ final class AddImageWizardChooseDataSourceVisual extends JPanel {
|
||||
*
|
||||
* @param panel instance of ImageTypePanel to change to
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void updateCurrentPanel(JPanel panel) {
|
||||
currentPanel = panel;
|
||||
typePanel.removeAll();
|
||||
|
@ -68,6 +68,7 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel<WizardDe
|
||||
private final AddImageWizardChooseDataSourcePanel dataSourcePanel;
|
||||
|
||||
private DataSourceProcessor dsProcessor;
|
||||
private boolean cancelled;
|
||||
|
||||
AddImageWizardIngestConfigPanel(AddImageWizardChooseDataSourcePanel dsPanel, AddImageAction action, AddImageWizardAddingProgressPanel proPanel) {
|
||||
this.addImageAction = action;
|
||||
@ -228,6 +229,7 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel<WizardDe
|
||||
@Override
|
||||
void cleanup() throws Exception {
|
||||
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) {
|
||||
dataSourceProcessorDone(dataSourceId, result, errList, contents);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
progressPanel.setStateStarted();
|
||||
@ -258,9 +259,6 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel<WizardDe
|
||||
* Cancels the data source processing - in case the users presses 'Cancel'
|
||||
*/
|
||||
private void cancelDataSourceProcessing(UUID dataSourceId) {
|
||||
new Thread(() -> {
|
||||
Case.getCurrentCase().notifyFailedAddingDataSource(dataSourceId);
|
||||
}).start();
|
||||
dsProcessor.cancel();
|
||||
}
|
||||
|
||||
@ -303,21 +301,23 @@ class AddImageWizardIngestConfigPanel implements WizardDescriptor.Panel<WizardDe
|
||||
progressPanel.addErrors(err, critErr);
|
||||
}
|
||||
|
||||
newContents.clear();
|
||||
newContents.addAll(contents);
|
||||
|
||||
//notify the UI of the new content added to the case
|
||||
new Thread(() -> {
|
||||
if (!newContents.isEmpty()) {
|
||||
Case.getCurrentCase().notifyDataSourceAdded(newContents.get(0), dataSourceId);
|
||||
if (!contents.isEmpty()) {
|
||||
Case.getCurrentCase().notifyDataSourceAdded(contents.get(0), dataSourceId);
|
||||
} else {
|
||||
Case.getCurrentCase().notifyFailedAddingDataSource(dataSourceId);
|
||||
}
|
||||
}).start();
|
||||
|
||||
// Start ingest if we can
|
||||
progressPanel.setStateStarted();
|
||||
startIngest();
|
||||
if (!cancelled) {
|
||||
newContents.clear();
|
||||
newContents.addAll(contents);
|
||||
progressPanel.setStateStarted();
|
||||
startIngest();
|
||||
} else {
|
||||
cancelled = false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -219,11 +219,11 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
* multi-user (using PostgreSql)
|
||||
*/
|
||||
@NbBundle.Messages({"Case_caseType_singleUser=Single-user case",
|
||||
"Case_caseType_multiUser=Multi-user case"})
|
||||
"Case_caseType_multiUser=Multi-user case"})
|
||||
public enum CaseType {
|
||||
|
||||
SINGLE_USER_CASE(Bundle.Case_caseType_singleUser()),
|
||||
MULTI_USER_CASE(Bundle.Case_caseType_multiUser());
|
||||
SINGLE_USER_CASE("Single-user case"), //NON-NLS
|
||||
MULTI_USER_CASE("Multi-user case"); //NON-NLS
|
||||
|
||||
private final String caseType;
|
||||
|
||||
@ -250,6 +250,14 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
public String toString() {
|
||||
return caseType;
|
||||
}
|
||||
|
||||
String getLocalizedDisplayName() {
|
||||
if (fromString(caseType) == SINGLE_USER_CASE) {
|
||||
return Bundle.Case_caseType_singleUser();
|
||||
} else {
|
||||
return Bundle.Case_caseType_multiUser();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private String name;
|
||||
|
@ -92,7 +92,7 @@ class CasePropertiesForm extends javax.swing.JPanel {
|
||||
CaseMetadata caseMetadata = new CaseMetadata(Paths.get(currentCase.getConfigFilePath()));
|
||||
tbDbName.setText(caseMetadata.getCaseDatabaseName());
|
||||
Case.CaseType caseType = caseMetadata.getCaseType();
|
||||
tbDbType.setText(caseType.toString());
|
||||
tbDbType.setText(caseType.getLocalizedDisplayName());
|
||||
if (caseType == Case.CaseType.SINGLE_USER_CASE) {
|
||||
deleteCaseButton.setEnabled(true);
|
||||
} else {
|
||||
|
@ -466,7 +466,11 @@ final class CollaborationMonitor {
|
||||
*/
|
||||
@Override
|
||||
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
|
||||
public void run() {
|
||||
remoteTasksManager.finishStaleTasks();
|
||||
try {
|
||||
remoteTasksManager.finishStaleTasks();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Unexpected exception in StaleTaskDetectionTask", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback
|
||||
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
|
||||
* wizard. It also provides a run method overload to allow it to be used
|
||||
* 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
|
||||
* 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
|
||||
* selection UI component (e.g., a combo box).
|
||||
* @return A data source type display string for this data source processor.
|
||||
*/
|
||||
public static String getType() {
|
||||
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
|
||||
* 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
|
||||
* selection UI component (e.g., a combo box).
|
||||
* @return A data source type display string for this data source processor.
|
||||
*/
|
||||
@Override
|
||||
public String getDataSourceType() {
|
||||
return DATA_SOURCE_TYPE;
|
||||
return getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
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
|
||||
* ready to have its run method called; false otherwise.
|
||||
* ready to have its run method called, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
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
|
||||
* settings provided by the panel. Returns as soon as the background task is
|
||||
* started and uses the callback object to signal task completion and return
|
||||
* results.
|
||||
* Adds a data source to the case database using a background task in a
|
||||
* separate thread and the settings provided by the selection and
|
||||
* configuration panel. Returns as soon as the background task is started.
|
||||
* 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
|
||||
* processing.
|
||||
* @param callback Callback to call when processing is done.
|
||||
* @param progressMonitor Progress monitor that will be used by the
|
||||
* background task to report progress.
|
||||
* @param callback Callback that will be used by the background task
|
||||
* to return results.
|
||||
*/
|
||||
@Override
|
||||
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
|
||||
* given settings instead of those provided by the panel. Returns as soon as
|
||||
* the background task is started and uses the callback object to signal
|
||||
* task completion and return results.
|
||||
* Adds a data source to the case database using a background task in a
|
||||
* separate thread and the given settings instead of those provided by the
|
||||
* selection and configuration panel. Returns as soon as the background task
|
||||
* is started and uses the callback object to signal task completion and
|
||||
* return results.
|
||||
*
|
||||
* @param deviceId An ASCII-printable identifier for the device
|
||||
* 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
|
||||
* started using the run method. Cancellation is not guaranteed.
|
||||
* Requests cancellation of the background task that adds a data source to
|
||||
* 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
|
||||
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
|
||||
public void reset() {
|
||||
@ -199,7 +209,7 @@ public class ImageDSProcessor implements DataSourceProcessor {
|
||||
|
||||
/**
|
||||
* 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 timeZone The time zone to use when processing dates
|
||||
|
@ -190,7 +190,7 @@ public class ImageFilePanel extends JPanel implements DocumentListener {
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
|
||||
String oldText = pathTextField.getText();
|
||||
// set the current directory of the FileChooser if the ImagePath Field is valid
|
||||
|
@ -61,10 +61,10 @@ public class LocalDiskDSProcessor implements DataSourceProcessor {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* selection UI component (e.g., a combo box).
|
||||
* @return A data source type display string for this data source processor.
|
||||
*/
|
||||
public static String getType() {
|
||||
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
|
||||
* 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
|
||||
* selection UI component (e.g., a combo box).
|
||||
* @return A data source type display string for this data source processor.
|
||||
*/
|
||||
@Override
|
||||
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
|
||||
* configuration the data source processor may require.
|
||||
* Gets the panel that allows a user to select a data source and do any
|
||||
* 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
|
||||
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
|
||||
* ready to have its run method called; false otherwise.
|
||||
* ready to have its run method called, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
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
|
||||
* settings provided by the panel. Returns as soon as the background task is
|
||||
* started and uses the callback object to signal task completion and return
|
||||
* results.
|
||||
* Adds a data source to the case database using a background task in a
|
||||
* separate thread and the settings provided by the selection and
|
||||
* configuration panel. Returns as soon as the background task is started.
|
||||
* 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
|
||||
* processing.
|
||||
* @param callback Callback to call when processing is done.
|
||||
* @param progressMonitor Progress monitor that will be used by the
|
||||
* background task to report progress.
|
||||
* @param callback Callback that will be used by the background task
|
||||
* to return results.
|
||||
*/
|
||||
@Override
|
||||
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
|
||||
* given settings instead of those provided by the panel. Returns as soon as
|
||||
* the background task is started and uses the callback object to signal
|
||||
* task completion and return results.
|
||||
* Adds a data source to the case database using a background task in a
|
||||
* separate thread and the given settings instead of those provided by the
|
||||
* selection and configuration panel. Returns as soon as the background task
|
||||
* is started and uses the callback object to signal task completion and
|
||||
* return results.
|
||||
*
|
||||
* @param deviceId An ASCII-printable identifier for the device
|
||||
* 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
|
||||
* started using the run method. Cancellation is not guaranteed.
|
||||
* Requests cancellation of the background task that adds a data source to
|
||||
* 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
|
||||
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
|
||||
public void reset() {
|
||||
|
@ -59,10 +59,10 @@ public class LocalFilesDSProcessor implements DataSourceProcessor {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* selection UI component (e.g., a combo box).
|
||||
* @return A data source type display string for this data source processor.
|
||||
*/
|
||||
public static String getType() {
|
||||
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
|
||||
* 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
|
||||
* selection UI component (e.g., a combo box).
|
||||
* @return A data source type display string for this data source processor.
|
||||
*/
|
||||
@Override
|
||||
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
|
||||
* 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
|
||||
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
|
||||
* ready to have its run method called; false otherwise.
|
||||
* ready to have its run method called, false otherwise.
|
||||
*/
|
||||
@Override
|
||||
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
|
||||
* settings provided by the panel. Returns as soon as the background task is
|
||||
* started and uses the callback object to signal task completion and return
|
||||
* results.
|
||||
* Adds a data source to the case database using a background task in a
|
||||
* separate thread and the settings provided by the selection and
|
||||
* configuration panel. Returns as soon as the background task is started.
|
||||
* 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
|
||||
* processing.
|
||||
* @param callback Callback to call when processing is done.
|
||||
* @param progressMonitor Progress monitor that will be used by the
|
||||
* background task to report progress.
|
||||
* @param callback Callback that will be used by the background task
|
||||
* to return results.
|
||||
*/
|
||||
@Override
|
||||
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
|
||||
* given settings instead of those provided by the panel. Returns as soon as
|
||||
* the background task is started and uses the callback object to signal
|
||||
* task completion and return results.
|
||||
* Adds a data source to the case database using a background task in a
|
||||
* separate thread and the given settings instead of those provided by the
|
||||
* selection and configuration panel. Returns as soon as the background task
|
||||
* is started and uses the callback object to signal task completion and
|
||||
* return results.
|
||||
*
|
||||
* @param deviceId An ASCII-printable identifier for the
|
||||
* 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
|
||||
* started using the run method. Cancellation is not guaranteed.
|
||||
* Requests cancellation of the background task that adds a data source to
|
||||
* 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
|
||||
public void cancel() {
|
||||
/*
|
||||
* Cancellation is not currently supported.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the panel.
|
||||
* Resets the selection and configuration panel for this data source
|
||||
* processor.
|
||||
*/
|
||||
@Override
|
||||
public void reset() {
|
||||
|
@ -239,7 +239,7 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel<WizardDesc
|
||||
|
||||
Object res2 = DialogDisplayer.getDefault().notify(d2);
|
||||
if (res2 != null && res2 == DialogDescriptor.YES_OPTION) {
|
||||
// if user say yes
|
||||
// if user says yes
|
||||
try {
|
||||
createDirectory(caseDirPath, getComponent().getCaseType());
|
||||
} catch (Exception ex) {
|
||||
@ -251,7 +251,7 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel<WizardDesc
|
||||
}
|
||||
}
|
||||
if (res2 != null && res2 == DialogDescriptor.NO_OPTION) {
|
||||
// if user say no
|
||||
// if user says no
|
||||
validationError(NbBundle.getMessage(this.getClass(),
|
||||
"NewCaseWizardPanel1.validate.errMsg.prevCreateBaseDir.msg",
|
||||
caseDirPath));
|
||||
|
@ -389,7 +389,11 @@ public class ServicesMonitor {
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
checkAllServices();
|
||||
try {
|
||||
checkAllServices();
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Unexpected exception in CrashDetectionTask", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,18 +22,24 @@ import javax.swing.JPanel;
|
||||
|
||||
/**
|
||||
* Interface implemented by classes that add data sources of a particular type
|
||||
* (e.g., images, local disks, virtual directories of local/logical files, etc.)
|
||||
* to a case database. A data source processor is NOT responsible for analyzing
|
||||
* the data source (running ingest modules on the data source and its contents).
|
||||
* (e.g., images, local disks, virtual directories of local/logical files) to a
|
||||
* case database. A data source processor is NOT responsible for analyzing the
|
||||
* 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
|
||||
* provide a JPanel to allow a user to select a data source and do any
|
||||
* configuration the data source processor may require. The panel should support
|
||||
* addition of the add data source wizard as a property change listener and
|
||||
* should fire DSP_PANEL_EVENT property changes to communicate with the wizard.
|
||||
* provide a UI panel to allow a user to select a data source and do any
|
||||
* configuration required by the data source processor. The selection and
|
||||
* configuration panel should support addition of the add data source wizard as
|
||||
* 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,
|
||||
* reporting results using a callback object.
|
||||
* Data source processors should perform all processing in a background task in
|
||||
* 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 {
|
||||
|
||||
@ -48,64 +54,76 @@ public interface DataSourceProcessor {
|
||||
enum DSP_PANEL_EVENT {
|
||||
|
||||
/**
|
||||
* Fire this event when the user changes something in the panel to
|
||||
* notify the add data source wizard that it should call isPanelValid.
|
||||
* This event is fired when the user changes something in the selection
|
||||
* and configuration panel. It notifies the add data source wizard that
|
||||
* it should call isPanelValid.
|
||||
*/
|
||||
UPDATE_UI,
|
||||
/**
|
||||
* Fire this event to make the add data source wizard move focus to the
|
||||
* next button.
|
||||
* This event is fired to make the add data source wizard move focus to
|
||||
* the wizard's next button.
|
||||
* @deprecated Use UPDATE_UI.
|
||||
*/
|
||||
@Deprecated
|
||||
FOCUS_NEXT
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* selection UI component (e.g., a combo box).
|
||||
* @return A data source type display string for this data source processor.
|
||||
*/
|
||||
String getDataSourceType();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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
|
||||
* ready to have its run method called; false otherwise.
|
||||
* ready to have its run method called, false otherwise.
|
||||
*/
|
||||
boolean isPanelValid();
|
||||
|
||||
/**
|
||||
* Adds a data source to the case database using a separate thread and the
|
||||
* settings provided by the panel. Returns as soon as the background task is
|
||||
* started and uses the callback object to signal task completion and return
|
||||
* results.
|
||||
* Adds a data source to the case database using a background task in a
|
||||
* separate thread and the settings provided by the selection and
|
||||
* configuration panel. Returns as soon as the background task is started.
|
||||
* 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
|
||||
* processing.
|
||||
* @param callback Callback to call when processing is done.
|
||||
* @param progressMonitor Progress monitor that will be used by the
|
||||
* background task to report progress.
|
||||
* @param callback Callback that will be used by the background task
|
||||
* to return results.
|
||||
*/
|
||||
void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback);
|
||||
|
||||
/**
|
||||
* Requests cancellation of the data source processing task after it is
|
||||
* started using the run method. Cancellation is not guaranteed.
|
||||
* Requests cancellation of the background task that adds a data source to
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* Resets the panel.
|
||||
* Resets the selection and configuration panel for this data source
|
||||
* processor.
|
||||
*/
|
||||
void reset();
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2014 Basis Technology Corp.
|
||||
* Copyright 2013-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -23,56 +23,72 @@ import java.util.List;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
|
||||
/**
|
||||
* Abstract class for a callback for a DataSourceProcessor.
|
||||
*
|
||||
* Ensures that DSP invokes the caller overridden method, doneEDT(), in the EDT
|
||||
* thread.
|
||||
* 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
|
||||
* database. The callback objects are used to signal task completion and return
|
||||
* results.
|
||||
*
|
||||
* Concrete implementations of DataSourceProcessorCallback should override
|
||||
* either the done method or the doneEDT method, but not both.
|
||||
*/
|
||||
public abstract class DataSourceProcessorCallback {
|
||||
|
||||
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).
|
||||
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 occurred while ading the data source to the case database.
|
||||
*/
|
||||
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
|
||||
* the database. Users of the DSP can override this method if they do not
|
||||
* want to be notified on the EDT. Otherwise, this method will call
|
||||
* doneEDT() with the same arguments.
|
||||
* Called by a data source processor when it is done adding a data source to
|
||||
* the case database, this method adds a task to call the doneEDT method to
|
||||
* the EDT task queue.
|
||||
*
|
||||
* @param result Code for status
|
||||
* @param errList List of error strings
|
||||
* @param newContents List of root Content objects that were added to
|
||||
* database. Typically only one is given.
|
||||
* IMPORTANT: Concrete implementations of DataSourceProcessorCallback should
|
||||
* override this method if the callback SHOULD NOT 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 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 List<String> errListf = errList;
|
||||
final List<Content> newContentsf = newContents;
|
||||
|
||||
// Invoke doneEDT() that runs on the EDT .
|
||||
EventQueue.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
doneEDT(resultf, errListf, newContentsf);
|
||||
}
|
||||
final List<Content> newContentsf = newDataSources;
|
||||
EventQueue.invokeLater(() -> {
|
||||
doneEDT(resultf, errListf, newContentsf);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by done() if the default implementation is used. Users of DSPs
|
||||
* that have UI updates to do after the DSP is finished adding the DS can
|
||||
* implement this method to receive the updates on the EDT.
|
||||
* Called by a data source processor when it is done adding a data source to
|
||||
* the case database, if the default done method has not been overridden.
|
||||
*
|
||||
* @param result Code for status
|
||||
* @param errList List of error strings
|
||||
* @param newContents List of root Content objects that were added to
|
||||
* database. Typically only one is given.
|
||||
* IMPORTANT: Concrete implementations of DataSourceProcessorCallback should
|
||||
* override the done method and provide an implementation of this method
|
||||
* that throws an UnsupportedOperationException if the callback SHOULD NOT
|
||||
* 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);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* 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 {
|
||||
|
||||
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 Icon about;
|
||||
|
||||
private final Icon about;
|
||||
private boolean verboseLogging;
|
||||
|
||||
public AboutWindowPanel() {
|
||||
about = new ImageIcon(ImageUtilities.loadImage("org/sleuthkit/autopsy/images/splash.png"));
|
||||
init();
|
||||
}
|
||||
|
||||
public AboutWindowPanel(String pathToBrandingImage) {
|
||||
about = new ImageIcon(ImageUtilities.loadImage(pathToBrandingImage));
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
initComponents();
|
||||
logoLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
description.setText(org.openide.util.NbBundle.getMessage(AboutWindowPanel.class,
|
||||
@ -68,7 +74,6 @@ public final class AboutWindowPanel extends JPanel implements HyperlinkListener
|
||||
if (verboseLoggingIsSet()) {
|
||||
disableVerboseLoggingButton();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
|
@ -148,7 +148,7 @@ FXVideoPanel.progress.bufferingCancelled=media buffering was canceled
|
||||
FXVideoPanel.progress.bufferingInterrupted=media buffering was interrupted
|
||||
FXVideoPanel.progress.errorWritingVideoToDisk=Error writing video to disk
|
||||
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.cbEnableMultiUser.text=Enable Multi-user cases
|
||||
MultiUserSettingsPanel.lbDatabaseSettings.text=Database Settings
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -23,38 +23,67 @@ import org.openide.nodes.FilterNode;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
super(arg);
|
||||
}
|
||||
|
||||
@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)};
|
||||
TableFilterChildren(Node wrappedNode) {
|
||||
super(wrappedNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (createChild) {
|
||||
return new TableFilterChildren(arg);
|
||||
@Override
|
||||
protected Node copyNode(Node nodeToCopy) {
|
||||
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 {
|
||||
return Children.LEAF;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -23,48 +23,40 @@ import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
/**
|
||||
* This class is used to filter the nodes that we want to show on the
|
||||
* "TreeTableView". So basically we just want to show one layer of nodes from
|
||||
* it's parent.
|
||||
*
|
||||
* @author jantonius
|
||||
* 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 Autopsy table views.
|
||||
*/
|
||||
public class TableFilterNode extends FilterNode {
|
||||
|
||||
private boolean createChild;
|
||||
private String itemType;
|
||||
private final boolean createChildren;
|
||||
|
||||
/**
|
||||
* the constructor
|
||||
*/
|
||||
public TableFilterNode(Node arg, boolean crChild) {
|
||||
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".
|
||||
* 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
|
||||
* Autopsy table views.
|
||||
*
|
||||
* @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
|
||||
public String getDisplayName() {
|
||||
if (createChild) {
|
||||
if (createChildren) {
|
||||
return NbBundle.getMessage(this.getClass(), "TableFilterNode.displayName.text");
|
||||
} else {
|
||||
return super.getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
public String getItemType() {
|
||||
return itemType;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011 Basis Technology Corp.
|
||||
* Copyright 2011-16 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -95,16 +95,18 @@ class ThumbnailViewNode extends FilterNode {
|
||||
super.done();
|
||||
try {
|
||||
iconCache = new SoftReference<>(super.get());
|
||||
progressHandle.finish();
|
||||
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) {
|
||||
timer.stop();
|
||||
timer = null;
|
||||
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
Logger.getLogger(ThumbnailViewNode.class.getName()).log(Level.SEVERE, "Error getting thumbnail icon", ex); //NON-NLS
|
||||
swingWorker = null;
|
||||
}
|
||||
swingWorker = null;
|
||||
}
|
||||
};
|
||||
swingWorker.execute();
|
||||
|
@ -21,7 +21,3 @@ PlatformUtil.getAllMemUsageInfo.usageText={0}\n\
|
||||
{1}\n\
|
||||
Process Virtual Memory\: {2}
|
||||
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}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2012-15 Basis Technology Corp.
|
||||
* Copyright 2012-16 Basis Technology Corp.
|
||||
*
|
||||
* Copyright 2012 42six Solutions.
|
||||
* Contact: aebadirad <at> 42six <dot> com
|
||||
@ -30,6 +30,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
@ -74,10 +75,6 @@ public class ImageUtils {
|
||||
|
||||
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
|
||||
*/
|
||||
@ -594,7 +591,7 @@ public class ImageUtils {
|
||||
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
|
||||
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
|
||||
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));
|
||||
throw iioException;
|
||||
}
|
||||
@ -613,7 +610,7 @@ public class ImageUtils {
|
||||
reader.dispose();
|
||||
}
|
||||
} 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));
|
||||
|
||||
throw iioException;
|
||||
@ -646,18 +643,19 @@ public class ImageUtils {
|
||||
*/
|
||||
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 File cacheFile;
|
||||
private final boolean defaultOnFailure;
|
||||
|
||||
// @NbBundle.Messages({"# {0} - file name",
|
||||
// "GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}", "# {0} - file name",
|
||||
// "GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
|
||||
@NbBundle.Messages({"# {0} - file name",
|
||||
"GetOrGenerateThumbnailTask.loadingThumbnailFor=Loading thumbnail for {0}",
|
||||
"# {0} - file name",
|
||||
"GetOrGenerateThumbnailTask.generatingPreviewFor=Generating preview for {0}"})
|
||||
private GetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
|
||||
super(file);
|
||||
updateMessage(NbBundle.getMessage(this.getClass(), "ImageUtils.GetOrGenerateThumbnailTask.loadingThumbnailFor", file.getName()));
|
||||
updateMessage(Bundle.GetOrGenerateThumbnailTask_loadingThumbnailFor(file.getName()));
|
||||
this.iconSize = iconSize;
|
||||
this.defaultOnFailure = defaultOnFailure;
|
||||
this.cacheFile = getCachedThumbnailLocation(file.getId());
|
||||
@ -678,36 +676,39 @@ public class ImageUtils {
|
||||
if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) {
|
||||
return SwingFXUtils.toFXImage(cachedThumbnail, null);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.WARNING, "ImageIO had a problem reading thumbnail for image {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
|
||||
} catch (Exception ex) {
|
||||
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()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//There was no correctly-sized cached thumbnail so make one.
|
||||
BufferedImage thumbnail = null;
|
||||
|
||||
if (VideoUtils.isVideoThumbnailSupported(file)) {
|
||||
if (openCVLoaded) {
|
||||
updateMessage(NbBundle.getMessage(this.getClass(), "ImageUtils.GetOrGenerateThumbnailTask.generatingPreviewFor", file.getName()));
|
||||
updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
|
||||
thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize);
|
||||
}
|
||||
if (null == thumbnail) {
|
||||
if (defaultOnFailure) {
|
||||
thumbnail = DEFAULT_THUMBNAIL;
|
||||
} else {
|
||||
throw new IIOException("Failed to generate thumbnail for video file.");
|
||||
throw new IIOException("Failed to generate a thumbnail for " + getContentPathSafe(file));
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
//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);
|
||||
if (null == bufferedImage) {
|
||||
LOGGER.log(Level.WARNING, FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION);
|
||||
throw new IIOException(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION);
|
||||
String msg = MessageFormat.format(FAILED_TO_READ_IMAGE_FOR_THUMBNAIL_GENERATION, getContentPathSafe(file));
|
||||
LOGGER.log(Level.WARNING, msg);
|
||||
throw new IIOException(msg);
|
||||
}
|
||||
updateProgress(-1, 1);
|
||||
|
||||
@ -716,23 +717,21 @@ public class ImageUtils {
|
||||
thumbnail = ScalrWrapper.resizeFast(bufferedImage, iconSize);
|
||||
} catch (IllegalArgumentException | OutOfMemoryError e) {
|
||||
// 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 width = bufferedImage.getWidth();
|
||||
if (iconSize < height || iconSize < width) {
|
||||
final int cropHeight = Math.min(iconSize, height);
|
||||
final int cropWidth = Math.min(iconSize, width);
|
||||
|
||||
try {
|
||||
thumbnail = ScalrWrapper.cropImage(bufferedImage, cropWidth, cropHeight);
|
||||
} catch (Exception cropException) {
|
||||
LOGGER.log(Level.WARNING, "Could not crop image {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
|
||||
throw cropException;
|
||||
LOGGER.log(Level.WARNING, "Could not crop {0}: " + cropException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N NON-NLS
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@ -744,7 +743,7 @@ public class ImageUtils {
|
||||
updateProgress(-1, 1);
|
||||
|
||||
//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);
|
||||
}
|
||||
|
||||
@ -790,16 +789,16 @@ public class ImageUtils {
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
ReadImageTask(AbstractFile 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
|
||||
protected javafx.scene.image.Image call() throws Exception {
|
||||
return readImage();
|
||||
@ -811,70 +810,55 @@ public class ImageUtils {
|
||||
*/
|
||||
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;
|
||||
private ImageReader reader;
|
||||
// private ImageReader reader;
|
||||
|
||||
ReadImageTaskBase(AbstractFile file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
protected javafx.scene.image.Image readImage() throws IOException {
|
||||
try (InputStream inputStream = new BufferedInputStream(new ReadContentInputStream(file));) {
|
||||
if (ImageUtils.isGIF(file)) {
|
||||
//use JavaFX to directly read GIF to preserve potential animation,
|
||||
javafx.scene.image.Image image = new javafx.scene.image.Image(new BufferedInputStream(inputStream));
|
||||
if (image.isError() == false) {
|
||||
return image;
|
||||
}
|
||||
//fall through to default image reading code if there was an error
|
||||
if (ImageUtils.isGIF(file)) {
|
||||
//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)));
|
||||
if (image.isError() == false) {
|
||||
return image;
|
||||
}
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
try (ImageInputStream input = ImageIO.createImageInputStream(inputStream)) {
|
||||
if (input == null) {
|
||||
throw new IIOException(COULD_NOT_CREATE_IMAGE_INPUT_STREAM);
|
||||
}
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
|
||||
//fall through to default image reading code if there was an error
|
||||
}
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//we use the first ImageReader, is there any point to trying the others?
|
||||
if (readers.hasNext()) {
|
||||
reader = readers.next();
|
||||
reader.addIIOReadProgressListener(this);
|
||||
reader.setInput(input);
|
||||
return getImageProperty(file, "ImageIO could not read {0}: ",
|
||||
imageReader -> {
|
||||
imageReader.addIIOReadProgressListener(ReadImageTaskBase.this);
|
||||
/*
|
||||
* This is the important part, get or create a
|
||||
* ImageReadParam, create a destination image to hold
|
||||
* the decoded result, then pass that image with the
|
||||
* param.
|
||||
*/
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
|
||||
BufferedImage bufferedImage = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
|
||||
ImageReadParam param = imageReader.getDefaultReadParam();
|
||||
BufferedImage bufferedImage = imageReader.getImageTypes(0).next().createBufferedImage(imageReader.getWidth(0), imageReader.getHeight(0));
|
||||
param.setDestination(bufferedImage);
|
||||
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) {
|
||||
// Ignore this exception or display a warning or similar, for exceptions happening during decoding
|
||||
LOGGER.log(Level.WARNING, IMAGE_UTILS_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N
|
||||
LOGGER.log(Level.WARNING, IMAGEIO_COULD_NOT_READ_UNSUPPORTE_OR_CORRUPT + ": " + iOException.toString(), ImageUtils.getContentPathSafe(file)); //NOI18N
|
||||
} finally {
|
||||
reader.removeIIOReadProgressListener(this);
|
||||
reader.dispose();
|
||||
imageReader.removeIIOReadProgressListener(ReadImageTaskBase.this);
|
||||
}
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
return SwingFXUtils.toFXImage(bufferedImage, null);
|
||||
} else {
|
||||
throw new IIOException(NO_IMAGE_READER_FOUND_FOR_ + ImageUtils.getContentPathSafe(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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
|
||||
updateProgress(percentageDone, 100);
|
||||
if (isCancelled()) {
|
||||
@ -890,11 +874,11 @@ public class ImageUtils {
|
||||
try {
|
||||
javafx.scene.image.Image fxImage = get();
|
||||
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 {
|
||||
if (fxImage.isError()) {
|
||||
//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) {
|
||||
@ -905,7 +889,7 @@ public class ImageUtils {
|
||||
@Override
|
||||
protected void 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
|
||||
@ -950,7 +934,7 @@ public class ImageUtils {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static String getContentPathSafe(Content content) {
|
||||
static String getContentPathSafe(Content content) {
|
||||
try {
|
||||
return content.getUniquePath();
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015 Basis Technology Corp.
|
||||
* Copyright 2015-16 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.coreutils;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -91,33 +92,33 @@ public class VideoUtils {
|
||||
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) {
|
||||
java.io.File tempFile = getTempVideoFile(file);
|
||||
|
||||
try {
|
||||
if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
|
||||
com.google.common.io.Files.createParentDirs(tempFile);
|
||||
ProgressHandle progress = ProgressHandleFactory.createHandle(NbBundle.getMessage(VideoUtils.class, "VideoUtils.genVideoThumb.progress.text", file.getName()));
|
||||
progress.start(100);
|
||||
try {
|
||||
ContentUtils.writeToFile(file, tempFile, progress, null, true);
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.WARNING, "Error buffering file", ex); //NON-NLS
|
||||
}
|
||||
if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
|
||||
ProgressHandle progress = ProgressHandleFactory.createHandle(Bundle.VideoUtils_genVideoThumb_progress_text(file.getName()));
|
||||
progress.start(100);
|
||||
try {
|
||||
Files.createParentDirs(tempFile);
|
||||
ContentUtils.writeToFile(file, tempFile, progress, null, true);
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.WARNING, "Error extracting temporary file for " + ImageUtils.getContentPathSafe(file), ex); //NON-NLS
|
||||
} finally {
|
||||
progress.finish();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
VideoCapture videoFile = new VideoCapture(); // will contain the video
|
||||
|
||||
if (!videoFile.open(tempFile.toString())) {
|
||||
LOGGER.log(Level.WARNING, "Error opening {0} for preview generation.", ImageUtils.getContentPathSafe(file)); //NON-NLS
|
||||
return null;
|
||||
}
|
||||
double fps = videoFile.get(CV_CAP_PROP_FPS); // gets frame per second
|
||||
double totalFrames = videoFile.get(CV_CAP_PROP_FRAME_COUNT); // gets total frames
|
||||
if (fps <= 0 || totalFrames <= 0) {
|
||||
LOGGER.log(Level.WARNING, "Error getting fps or total frames for {0}", ImageUtils.getContentPathSafe(file)); //NON-NLS
|
||||
return null;
|
||||
}
|
||||
double milliseconds = 1000 * (totalFrames / fps); //total milliseconds
|
||||
@ -132,10 +133,12 @@ public class VideoUtils {
|
||||
for (int x = 0; x < THUMB_COLUMNS; x++) {
|
||||
for (int y = 0; y < THUMB_ROWS; y++) {
|
||||
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
|
||||
}
|
||||
//read the frame into the image/matrix
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ public final class AutopsyEventPublisher {
|
||||
* Composed of thread-safe objects.
|
||||
*/
|
||||
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 RemoteEventPublisher remotePublisher;
|
||||
private String currentChannelName;
|
||||
@ -86,15 +86,8 @@ public final class AutopsyEventPublisher {
|
||||
* events from other Autopsy nodes.
|
||||
*/
|
||||
public void closeRemoteEventChannel() {
|
||||
stopRemotePublisher();
|
||||
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.
|
||||
*/
|
||||
public void publishRemotely(AutopsyEvent event) {
|
||||
/*
|
||||
* This is a no-op if a remote channel has not been opened.
|
||||
*/
|
||||
if (null != currentChannelName) {
|
||||
boolean published = false;
|
||||
int tryCount = 1;
|
||||
|
||||
while (false == published && tryCount <= MAX_REMOTE_EVENT_PUBLISH_TRIES) {
|
||||
try {
|
||||
if (null == remotePublisher) {
|
||||
@ -175,16 +166,28 @@ public final class AutopsyEventPublisher {
|
||||
}
|
||||
remotePublisher.publish(event);
|
||||
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
|
||||
closeRemoteEventChannel();
|
||||
++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
|
||||
stopRemotePublisher();
|
||||
++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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -115,5 +115,5 @@ IngestJob.cancelReason.notCancelled.text=Not cancelled
|
||||
IngestJob.cancelReason.cancelledByUser.text=Cancelled by user
|
||||
IngestJob.cancelReason.ingestModStartFail.text=Ingest modules startup failed
|
||||
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
|
||||
|
@ -1,116 +1,115 @@
|
||||
CTL_IngestMessageTopComponent=\u30E1\u30C3\u30BB\u30FC\u30B8
|
||||
HINT_IngestMessageTopComponent=\u30E1\u30C3\u30BB\u30FC\u30B8\u30A6\u30A3\u30F3\u30C9\u30A6
|
||||
IngestDialog.closeButton.title=\u9589\u3058\u308B
|
||||
IngestDialog.startButton.title=\u958B\u59CB
|
||||
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.dataSourceIngest.displayName={1}\u306E{0}
|
||||
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.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.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.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.toString.data.text=\ \u30C7\u30FC\u30BF\uFF1A{0}
|
||||
IngestMessage.toString.date.text=\ \u65E5\u4ED8\uFF1A{0}
|
||||
IngestMessage.toString.details.text=\ \u8A73\u7D30\uFF1A{0}
|
||||
IngestMessage.toString.subject.text=\ \u30B5\u30D6\u30B8\u30A7\u30AF\u30C8\uFF1A{0}
|
||||
IngestMessage.toString.type.text=\u30BF\u30A4\u30D7\uFF1A{0}
|
||||
IngestMessageDetailsPanel.copyMenuItem.text=\u30B3\u30D4\u30FC
|
||||
IngestMessageDetailsPanel.messageDetailsPane.contentType=\u30C6\u30AD\u30B9\u30C8\uFF0Fhtml
|
||||
IngestMessageDetailsPanel.selectAllMenuItem.text=\u3059\u3079\u3066\u9078\u629E
|
||||
IngestMessageDetailsPanel.viewArtifactButton.text=\u7D50\u679C\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.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.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.new=\u65B0\u898F\uFF1F
|
||||
IngestMessagePanel.MsgTableMod.colNames.num=\u756A\u53F7
|
||||
IngestMessagePanel.MsgTableMod.colNames.subject=\u30B5\u30D6\u30B8\u30A7\u30AF\u30C8
|
||||
IngestMessagePanel.MsgTableMod.colNames.timestamp=\u30BF\u30A4\u30E0\u30B9\u30BF\u30F3\u30D7
|
||||
IngestMessagePanel.sortByComboBox.model.priority=\u512A\u5148\u5EA6
|
||||
CTL_IngestMessageTopComponent=\u30e1\u30c3\u30bb\u30fc\u30b8
|
||||
HINT_IngestMessageTopComponent=\u30e1\u30c3\u30bb\u30fc\u30b8\u30a6\u30a3\u30f3\u30c9\u30a6
|
||||
IngestDialog.closeButton.title=\u9589\u3058\u308b
|
||||
IngestDialog.startButton.title=\u958b\u59cb
|
||||
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.dataSourceIngest.displayName={1}\u306e{0}
|
||||
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.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.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.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.toString.data.text=\ \u30c7\u30fc\u30bf\uff1a{0}
|
||||
IngestMessage.toString.date.text=\ \u65e5\u4ed8\uff1a{0}
|
||||
IngestMessage.toString.details.text=\ \u8a73\u7d30\uff1a{0}
|
||||
IngestMessage.toString.subject.text=\ \u30b5\u30d6\u30b8\u30a7\u30af\u30c8\uff1a{0}
|
||||
IngestMessage.toString.type.text=\u30bf\u30a4\u30d7\uff1a{0}
|
||||
IngestMessageDetailsPanel.copyMenuItem.text=\u30b3\u30d4\u30fc
|
||||
IngestMessageDetailsPanel.messageDetailsPane.contentType=\u30c6\u30ad\u30b9\u30c8\uff0fhtml
|
||||
IngestMessageDetailsPanel.selectAllMenuItem.text=\u3059\u3079\u3066\u9078\u629e
|
||||
IngestMessageDetailsPanel.viewArtifactButton.text=\u7d50\u679c\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.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.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.new=\u65b0\u898f\uff1f
|
||||
IngestMessagePanel.MsgTableMod.colNames.num=\u756a\u53f7
|
||||
IngestMessagePanel.MsgTableMod.colNames.subject=\u30b5\u30d6\u30b8\u30a7\u30af\u30c8
|
||||
IngestMessagePanel.MsgTableMod.colNames.timestamp=\u30bf\u30a4\u30e0\u30b9\u30bf\u30f3\u30d7
|
||||
IngestMessagePanel.sortByComboBox.model.priority=\u512a\u5148\u5ea6
|
||||
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.sortByLabel.text=\u6B21\u3067\u30BD\u30FC\u30C8\uFF1A
|
||||
IngestMessagePanel.totalMessagesNameLabel.text=\u5408\u8A08\uFF1A
|
||||
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.totalMessagesNameLabel.text=\u5408\u8a08\uff1a
|
||||
IngestMessagePanel.totalMessagesNameVal.text=-
|
||||
IngestMessagePanel.totalUniqueMessagesNameLabel.text=\u30E6\u30CB\u30FC\u30AF\uFF1A
|
||||
IngestMessagePanel.totalUniqueMessagesNameLabel.text=\u30e6\u30cb\u30fc\u30af\uff1a
|
||||
IngestMessagePanel.totalUniqueMessagesNameVal.text=-
|
||||
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.displayReport.option.GenRpt=\u30EC\u30DD\u30FC\u30C8\u3092\u751F\u6210
|
||||
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.displayReport.option.GenRpt=\u30ec\u30dd\u30fc\u30c8\u3092\u751f\u6210
|
||||
IngestMessageTopComponent.displayReport.option.OK=OK
|
||||
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
|
||||
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
|
||||
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.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.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
|
||||
IngestJob.progress.dataSourceIngest.initialDisplayName={0}\u3092\u89E3\u6790\u4E2D
|
||||
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.file=\u30D5\u30A1\u30A4\u30EB
|
||||
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.startTime=\u958B\u59CB\u6642\u9593
|
||||
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.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.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
|
||||
IngestProgressSnapshotPanel.closeButton.text=\u9589\u3058\u308B
|
||||
IngestProgressSnapshotPanel.refreshButton.text=\u30EA\u30D5\u30EC\u30C3\u30B7\u30E5
|
||||
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.activity=\u30A2\u30AF\u30C6\u30A3\u30D3\u30C6\u30A3
|
||||
IngestJobSettingsPanel.advancedButton.actionCommand=\u30A2\u30C9\u30D0\u30F3\u30B9
|
||||
IngestJobSettingsPanel.advancedButton.text=\u30A2\u30C9\u30D0\u30F3\u30B9
|
||||
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
|
||||
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
|
||||
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.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.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
|
||||
IngestJob.progress.dataSourceIngest.initialDisplayName={0}\u3092\u89e3\u6790\u4e2d
|
||||
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.file=\u30d5\u30a1\u30a4\u30eb
|
||||
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.startTime=\u958b\u59cb\u6642\u9593
|
||||
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.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.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
|
||||
IngestProgressSnapshotPanel.closeButton.text=\u9589\u3058\u308b
|
||||
IngestProgressSnapshotPanel.refreshButton.text=\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5
|
||||
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.activity=\u30a2\u30af\u30c6\u30a3\u30d3\u30c6\u30a3
|
||||
IngestJobSettingsPanel.advancedButton.actionCommand=\u30a2\u30c9\u30d0\u30f3\u30b9
|
||||
IngestJobSettingsPanel.advancedButton.text=\u30a2\u30c9\u30d0\u30f3\u30b9
|
||||
ModuleTableModel.colName.duration=\u6240\u8981\u6642\u9593
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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.filesPerSec=\u30D5\u30A1\u30A4\u30EB\uFF0F\u79D2
|
||||
IngestJobTableModel.colName.filesQueued=\u30AD\u30E5\u30FC\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB
|
||||
IngestJobTableModel.colName.inProgress=\u51E6\u7406\u4E2D
|
||||
IngestJobTableModel.colName.jobID=\u30B8\u30E7\u30D6ID
|
||||
IngestJobTableModel.colName.numProcessed=\u51E6\u7406\u3055\u308C\u305F\u6570
|
||||
IngestJobTableModel.colName.rootQueued=\u30AD\u30E5\u30FC\u3055\u308C\u305F\u30EB\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
|
||||
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.jobID=\u30B8\u30E7\u30D6ID
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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.filesPerSec=\u30d5\u30a1\u30a4\u30eb\uff0f\u79d2
|
||||
IngestJobTableModel.colName.filesQueued=\u30ad\u30e5\u30fc\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb
|
||||
IngestJobTableModel.colName.inProgress=\u51e6\u7406\u4e2d
|
||||
IngestJobTableModel.colName.jobID=\u30b8\u30e7\u30d6ID
|
||||
IngestJobTableModel.colName.numProcessed=\u51e6\u7406\u3055\u308c\u305f\u6570
|
||||
IngestJobTableModel.colName.rootQueued=\u30ad\u30e5\u30fc\u3055\u308c\u305f\u30eb\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
|
||||
IngestProgressSnapshotPanel.SnapshotsTableModel.colNames.jobID=\u30b8\u30e7\u30d6ID
|
||||
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.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
|
||||
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.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
|
||||
IngestJobTableModel.colName.dsQueued=\u30AD\u30E5\u30FC\u3055\u308C\u305FDS
|
||||
IngestJobSettingsPanel.jButtonSelectAll.text=\u5168\u3066\u9078\u629E
|
||||
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.serviceIsDown.msgDlg.text={0}\u304C\u30C0\u30A6\u30F3\u3057\u3066\u3044\u307E\u3059
|
||||
RunIngestSubMenu.menuItem.empty=\u30FC\u7A7A\u767D\u30FC
|
||||
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
|
||||
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.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.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
|
||||
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
|
||||
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.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
|
||||
IngestJobTableModel.colName.dsQueued=\u30ad\u30e5\u30fc\u3055\u308c\u305fDS
|
||||
IngestJobSettingsPanel.jButtonSelectAll.text=\u5168\u3066\u9078\u629e
|
||||
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.serviceIsDown.msgDlg.text={0}\u304c\u30c0\u30a6\u30f3\u3057\u3066\u3044\u307e\u3059
|
||||
RunIngestSubMenu.menuItem.empty=\u30fc\u7a7a\u767d\u30fc
|
||||
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
|
||||
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.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.outOfDiskSpace.text=\u30c7\u30a3\u30b9\u30af\u30b9\u30da\u30fc\u30b9\u304c\u8db3\u308a\u307e\u305b\u3093
|
||||
IngestJob.cancelReason.caseClosed.text=\u30b1\u30fc\u30b9\u3092\u9589\u3058\u307e\u3057\u305f
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Encapsulates the ingest job settings for a particular context such as the Add
|
||||
* Data Source wizard or the Run Ingest Modules dialog.
|
||||
* Encapsulates the ingest job settings for a particular 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.
|
||||
*/
|
||||
public class IngestJobSettings {
|
||||
|
||||
@ -60,19 +62,30 @@ public class IngestJobSettings {
|
||||
private boolean processUnallocatedSpace;
|
||||
private final List<String> warnings;
|
||||
|
||||
// Determines which modeules to run
|
||||
/**
|
||||
* The type of ingest modules to run.
|
||||
*/
|
||||
public enum IngestType {
|
||||
|
||||
// Run all modules
|
||||
/**
|
||||
* Run both data source level and file-level ingest modules.
|
||||
*/
|
||||
ALL_MODULES,
|
||||
// Run only data source modules
|
||||
/**
|
||||
* Run only data source level ingest modules.
|
||||
*/
|
||||
DATA_SOURCE_ONLY,
|
||||
// Run only files modules
|
||||
/**
|
||||
* Run only file level ingest modules.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -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 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() {
|
||||
return this.executionContext;
|
||||
|
@ -71,28 +71,27 @@ public class IngestManager {
|
||||
private static IngestManager instance;
|
||||
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.
|
||||
*/
|
||||
private final ConcurrentHashMap<Long, IngestJob> jobsById;
|
||||
private final Map<Long, IngestJob> jobsById;
|
||||
|
||||
/**
|
||||
/*
|
||||
* Each runnable/callable task the ingest manager submits to its thread
|
||||
* pools is given a unique thread/task ID.
|
||||
*/
|
||||
private final AtomicLong nextThreadId;
|
||||
|
||||
/**
|
||||
* Ingest jobs may be queued to be started on a pool thread by ingest job
|
||||
* starters. A mapping of thread/task IDs to the result objects associated
|
||||
* with each ingest job starter is maintained to provide handles that can be
|
||||
* used to cancel the ingest job starter.
|
||||
/*
|
||||
* Ingest jobs may be queued to be started on a pool thread by start ingest
|
||||
* job tasks. A mapping of task ids to the Future objects for each task is
|
||||
* maintained to allow for task cancellation.
|
||||
*/
|
||||
private final ConcurrentHashMap<Long, Future<Void>> ingestJobStarters;
|
||||
private final Map<Long, Future<Void>> startIngestJobTasks;
|
||||
private final ExecutorService startIngestJobsThreadPool;
|
||||
|
||||
/**
|
||||
/*
|
||||
* Ingest jobs use an ingest task scheduler to break themselves down into
|
||||
* data source level and file level tasks. The ingest scheduler puts these
|
||||
* ingest tasks into queues for execution on ingest manager pool threads by
|
||||
@ -118,14 +117,14 @@ public class IngestManager {
|
||||
private AutopsyEventPublisher moduleEventPublisher;
|
||||
private final ExecutorService eventPublishingExecutor;
|
||||
|
||||
/**
|
||||
/*
|
||||
* The ingest manager uses an ingest monitor to determine when system
|
||||
* 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.
|
||||
*/
|
||||
private final IngestMonitor ingestMonitor;
|
||||
|
||||
/**
|
||||
/*
|
||||
* 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
|
||||
* as a cap to avoid bogging down the application.
|
||||
@ -134,7 +133,7 @@ public class IngestManager {
|
||||
private volatile IngestMessageTopComponent ingestMessageBox;
|
||||
private final AtomicLong ingestErrorMessagePosts;
|
||||
|
||||
/**
|
||||
/*
|
||||
* The ingest manager supports reporting of ingest processing progress by
|
||||
* collecting snapshots of the activities of the ingest threads, ingest job
|
||||
* progress, and ingest module run times.
|
||||
@ -142,13 +141,13 @@ public class IngestManager {
|
||||
private final ConcurrentHashMap<Long, IngestThreadActivitySnapshot> ingestThreadActivitySnapshots;
|
||||
private final ConcurrentHashMap<String, Long> ingestModuleRunTimes;
|
||||
|
||||
/**
|
||||
/*
|
||||
* The ingest job creation capability of the ingest manager can be turned on
|
||||
* and off to support an orderly shut down of the application.
|
||||
*/
|
||||
private volatile boolean jobCreationIsEnabled;
|
||||
|
||||
/**
|
||||
/*
|
||||
* Ingest manager subscribes to service outage notifications. If key
|
||||
* 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.startIngestJobsThreadPool = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IM-start-ingest-jobs-%d").build()); //NON-NLS
|
||||
this.nextThreadId = new AtomicLong(0L);
|
||||
this.jobsById = new ConcurrentHashMap<>();
|
||||
this.ingestJobStarters = new ConcurrentHashMap<>();
|
||||
this.jobsById = new HashMap<>();
|
||||
this.startIngestJobTasks = new ConcurrentHashMap<>();
|
||||
|
||||
this.servicesMonitor = ServicesMonitor.getInstance();
|
||||
subscribeToServiceMonitorEvents();
|
||||
@ -329,7 +328,7 @@ public class IngestManager {
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
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 {
|
||||
if (!Case.isCaseOpen() || Case.getCurrentCase().getCaseType() != Case.CaseType.MULTI_USER_CASE) {
|
||||
return;
|
||||
@ -474,13 +473,13 @@ public class IngestManager {
|
||||
* @param dataSources The data sources to process.
|
||||
* @param settings The settings for the ingest job.
|
||||
*/
|
||||
public synchronized void queueIngestJob(Collection<Content> dataSources, IngestJobSettings settings) {
|
||||
if (this.jobCreationIsEnabled) {
|
||||
public void queueIngestJob(Collection<Content> dataSources, IngestJobSettings settings) {
|
||||
if (jobCreationIsEnabled) {
|
||||
IngestJob job = new IngestJob(dataSources, settings, RuntimeProperties.coreComponentsAreActive());
|
||||
if (job.hasIngestPipeline()) {
|
||||
long taskId = nextThreadId.incrementAndGet();
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the job to the jobs map now so that isIngestRunning() will
|
||||
* 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);
|
||||
synchronized (jobsById) {
|
||||
jobsById.put(job.getId(), job);
|
||||
}
|
||||
List<IngestModuleError> errors = job.start();
|
||||
if (errors.isEmpty()) {
|
||||
this.fireIngestJobStarted(job.getId());
|
||||
IngestManager.logger.log(Level.INFO, "Ingest job {0} started", job.getId()); //NON-NLS
|
||||
success = true;
|
||||
} else {
|
||||
this.jobsById.remove(job.getId());
|
||||
synchronized (jobsById) {
|
||||
this.jobsById.remove(job.getId());
|
||||
}
|
||||
for (IngestModuleError error : errors) {
|
||||
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) {
|
||||
long jobId = job.getId();
|
||||
this.jobsById.remove(jobId);
|
||||
synchronized (jobsById) {
|
||||
jobsById.remove(jobId);
|
||||
}
|
||||
if (!job.isCancelled()) {
|
||||
IngestManager.logger.log(Level.INFO, "Ingest job {0} completed", jobId); //NON-NLS
|
||||
this.fireIngestJobCompleted(jobId);
|
||||
fireIngestJobCompleted(jobId);
|
||||
} else {
|
||||
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.
|
||||
*/
|
||||
public boolean isIngestRunning() {
|
||||
return !this.jobsById.isEmpty();
|
||||
synchronized (jobsById) {
|
||||
return !jobsById.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -625,7 +626,7 @@ public class IngestManager {
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public synchronized void cancelAllIngestJobs() {
|
||||
public void cancelAllIngestJobs() {
|
||||
cancelAllIngestJobs(IngestJob.CancellationReason.USER_CANCELLED);
|
||||
}
|
||||
|
||||
@ -634,15 +635,21 @@ public class IngestManager {
|
||||
*
|
||||
* @param reason The cancellation reason.
|
||||
*/
|
||||
public synchronized void cancelAllIngestJobs(IngestJob.CancellationReason reason) {
|
||||
// Stop creating new ingest jobs.
|
||||
for (Future<Void> handle : ingestJobStarters.values()) {
|
||||
public void cancelAllIngestJobs(IngestJob.CancellationReason reason) {
|
||||
/*
|
||||
* Cancel the start job tasks.
|
||||
*/
|
||||
for (Future<Void> handle : startIngestJobTasks.values()) {
|
||||
handle.cancel(true);
|
||||
}
|
||||
|
||||
// Cancel all the jobs already created.
|
||||
for (IngestJob job : this.jobsById.values()) {
|
||||
job.cancel(reason);
|
||||
/*
|
||||
* Cancel the jobs in progress.
|
||||
*/
|
||||
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> snapShots = new ArrayList<>();
|
||||
for (IngestJob job : this.jobsById.values()) {
|
||||
snapShots.addAll(job.getDataSourceIngestJobSnapshots());
|
||||
synchronized (jobsById) {
|
||||
for (IngestJob job : jobsById.values()) {
|
||||
snapShots.addAll(job.getDataSourceIngestJobSnapshots());
|
||||
}
|
||||
}
|
||||
return snapShots;
|
||||
}
|
||||
@ -941,7 +950,9 @@ public class IngestManager {
|
||||
public Void call() {
|
||||
try {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
jobsById.remove(job.getId());
|
||||
synchronized (jobsById) {
|
||||
jobsById.remove(job.getId());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -953,7 +964,7 @@ public class IngestManager {
|
||||
if (progress != null) {
|
||||
progress.setDisplayName(NbBundle.getMessage(this.getClass(), "IngestManager.StartIngestJobsTask.run.cancelling", displayName));
|
||||
}
|
||||
Future<?> handle = ingestJobStarters.remove(threadId);
|
||||
Future<?> handle = startIngestJobTasks.remove(threadId);
|
||||
handle.cancel(true);
|
||||
return true;
|
||||
}
|
||||
@ -968,7 +979,7 @@ public class IngestManager {
|
||||
if (null != progress) {
|
||||
progress.finish();
|
||||
}
|
||||
ingestJobStarters.remove(threadId);
|
||||
startIngestJobTasks.remove(threadId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2015 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -39,11 +39,14 @@ import org.sleuthkit.datamodel.Content;
|
||||
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 {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TITLE = NbBundle.getMessage(RunIngestModulesDialog.class, "IngestDialog.title.text");
|
||||
private final IngestType ingestType;
|
||||
private static Dimension DIMENSIONS = new Dimension(500, 300);
|
||||
@ -51,13 +54,13 @@ public final class RunIngestModulesDialog extends JDialog {
|
||||
private IngestJobSettingsPanel ingestJobSettingsPanel;
|
||||
|
||||
/**
|
||||
* Construct a dialog box that allows a user to configure and run an ingest
|
||||
* job on one or more data sources.
|
||||
* 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.
|
||||
* @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) {
|
||||
super(frame, title, modal);
|
||||
@ -66,8 +69,8 @@ public final class RunIngestModulesDialog extends JDialog {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a dialog box that allows a user to configure and run an ingest
|
||||
* job on one or more data sources.
|
||||
* Constructs a dialog box that allows a user to configure and execute
|
||||
* analysis of one or more data sources with ingest modules.
|
||||
*
|
||||
* @param dataSources The data sources to be processed.
|
||||
*/
|
||||
@ -75,51 +78,17 @@ public final class RunIngestModulesDialog extends JDialog {
|
||||
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) {
|
||||
this.dataSources.add(dir);
|
||||
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.
|
||||
*/
|
||||
@ -227,4 +196,45 @@ public final class RunIngestModulesDialog extends JDialog {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ public final class EmbeddedFileExtractorIngestModule implements FileIngestModule
|
||||
} catch (SecurityException ex) {
|
||||
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
|
||||
throw new IngestModuleException(ex.getMessage());
|
||||
throw new IngestModuleException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,7 +87,7 @@ public final class EmbeddedFileExtractorIngestModule implements FileIngestModule
|
||||
try {
|
||||
fileTypeDetector = new FileTypeDetector();
|
||||
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
|
||||
throw new IngestModuleException(ex.getMessage());
|
||||
throw new IngestModuleException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
// initialize the extraction modules.
|
||||
|
@ -133,7 +133,7 @@ class SevenZipExtractor {
|
||||
String details = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errCantInitLib",
|
||||
e.getMessage());
|
||||
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
|
||||
throw new IngestModuleException(e.getMessage());
|
||||
throw new IngestModuleException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
this.context = context;
|
||||
|
@ -91,7 +91,7 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
|
||||
try {
|
||||
fileTypeDetector = new FileTypeDetector();
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2014 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,13 +18,11 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.modules.fileextmismatch;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
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.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
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 IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
|
||||
private static Blackboard blackboard;
|
||||
private FileTypeDetector detector;
|
||||
|
||||
private static class IngestJobTotals {
|
||||
|
||||
@ -94,6 +92,11 @@ public class FileExtMismatchIngestModule implements FileIngestModule {
|
||||
|
||||
FileExtMismatchXML xmlLoader = FileExtMismatchXML.getDefault();
|
||||
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()) {
|
||||
return false;
|
||||
}
|
||||
String currActualSigType = abstractFile.getMIMEType();
|
||||
String currActualSigType = detector.getFileType(abstractFile);
|
||||
if (currActualSigType == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -35,10 +35,10 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
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 {
|
||||
|
||||
@ -49,11 +49,14 @@ public class FileTypeDetector {
|
||||
private static final Logger logger = Logger.getLogger(FileTypeDetector.class.getName());
|
||||
|
||||
/**
|
||||
* Constructs an object that detects the type of a file by an inspection of
|
||||
* its contents.
|
||||
* Constructs an object that detects the MIME type of a file by an
|
||||
* inspection of its contents, using both user-defined type definitions and
|
||||
* Tika.
|
||||
*
|
||||
* @throws FileTypeDetector.FileTypeDetectorInitException if an
|
||||
* initialization error occurs.
|
||||
* @throws FileTypeDetectorInitException if an initialization error occurs,
|
||||
* e.g., user-defined file type
|
||||
* definitions exist but cannot be
|
||||
* loaded.
|
||||
*/
|
||||
public FileTypeDetector() throws FileTypeDetectorInitException {
|
||||
try {
|
||||
@ -64,12 +67,26 @@ public class FileTypeDetector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not a given MIME type is detectable by this
|
||||
* detector.
|
||||
* Gets the names of the user-defined MIME types.
|
||||
*
|
||||
* @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) {
|
||||
return isDetectableAsUserDefinedType(mimeType) || isDetectableByTika(mimeType);
|
||||
@ -77,11 +94,11 @@ public class FileTypeDetector {
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
for (FileType fileType : userDefinedFileTypes) {
|
||||
@ -95,9 +112,9 @@ public class FileTypeDetector {
|
||||
/**
|
||||
* 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) {
|
||||
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
|
||||
* already posted, detect the type of the file, posting it to the blackboard
|
||||
* if detection succeeds.
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
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
|
||||
* @throws TskCoreException if detection is required and there is a problem
|
||||
* writing the result to the case database.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public String detectAndPostToBlackboard(AbstractFile file) throws TskCoreException {
|
||||
String mimeType = detect(file);
|
||||
public String getFileType(AbstractFile file) throws TskCoreException {
|
||||
String mimeType = file.getMIMEType();
|
||||
if (null != 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.
|
||||
}
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -173,9 +173,12 @@ public class FileTypeDetector {
|
||||
* @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)
|
||||
// as octet-stream.
|
||||
/*
|
||||
* Consistently mark non-regular files (refer to
|
||||
* 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
|
||||
|| (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
|
||||
|| (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)
|
||||
@ -183,8 +186,8 @@ public class FileTypeDetector {
|
||||
return MimeTypes.OCTET_STREAM;
|
||||
}
|
||||
|
||||
String fileType = detectUserDefinedType(file);
|
||||
if (null == fileType) {
|
||||
String mimeType = detectUserDefinedType(file);
|
||||
if (null == mimeType) {
|
||||
try {
|
||||
byte buf[];
|
||||
int len = file.read(buffer, 0, BUFFER_SIZE);
|
||||
@ -195,23 +198,25 @@ public class FileTypeDetector {
|
||||
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.
|
||||
*/
|
||||
return mimetype.replace("tika-", ""); //NON-NLS
|
||||
mimeType = tikaType.replace("tika-", ""); //NON-NLS
|
||||
|
||||
} catch (Exception ignored) {
|
||||
/**
|
||||
* This exception is swallowed rather than propagated because
|
||||
* files in images are not always consistent with their file
|
||||
* system meta data making for read errors, and Tika can be a
|
||||
* bit flaky at times, making this a best effort endeavor.
|
||||
/*
|
||||
* This exception is swallowed and not logged rather than
|
||||
* propagated because files in data sources are not always
|
||||
* consistent with their file system metadata, making for read
|
||||
* 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());
|
||||
artifact.addAttribute(setNameAttribute);
|
||||
|
||||
/**
|
||||
/*
|
||||
* Use the MIME type as the category, i.e., the rule that
|
||||
* determined this file belongs to the interesting files
|
||||
* set.
|
||||
@ -259,6 +264,7 @@ public class FileTypeDetector {
|
||||
}
|
||||
|
||||
public static class FileTypeDetectorInitException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
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() {
|
||||
List<String> list = new ArrayList<>();
|
||||
if (userDefinedFileTypes != null) {
|
||||
for (FileType fileType : userDefinedFileTypes) {
|
||||
list.add(fileType.getMimeType());
|
||||
}
|
||||
}
|
||||
return list;
|
||||
@Deprecated
|
||||
@SuppressWarnings("deprecation")
|
||||
public String detectAndPostToBlackboard(AbstractFile file) throws TskCoreException {
|
||||
return getFileType(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, "{key}")"/>
|
||||
</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, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="skipKnownCheckBoxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
@ -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
|
||||
}
|
@ -27,7 +27,6 @@ import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||
import org.sleuthkit.autopsy.ingest.IngestMessage;
|
||||
import org.sleuthkit.autopsy.ingest.IngestServices;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.TskData.FileKnown;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModule.ProcessResult;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
|
||||
|
||||
@ -38,7 +37,6 @@ import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
|
||||
public class FileTypeIdIngestModule implements FileIngestModule {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(FileTypeIdIngestModule.class.getName());
|
||||
private final FileTypeIdModuleSettings settings;
|
||||
private long jobId;
|
||||
private static final HashMap<Long, IngestJobTotals> totalsForIngestJobs = new HashMap<>();
|
||||
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
|
||||
* signature (magic) values. Posts results to the blackboard.
|
||||
*
|
||||
* @param settings The ingest module settings.
|
||||
*/
|
||||
FileTypeIdIngestModule(FileTypeIdModuleSettings settings) {
|
||||
this.settings = settings;
|
||||
FileTypeIdIngestModule() {
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,7 +78,7 @@ public class FileTypeIdIngestModule implements FileIngestModule {
|
||||
try {
|
||||
fileTypeDetector = new FileTypeDetector();
|
||||
} 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
|
||||
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,
|
||||
* 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
|
||||
* this ingest job.
|
||||
*
|
||||
* @param jobId The ingest job identifier.
|
||||
* @param jobId The ingest job identifier.
|
||||
* @param matchTimeInc Amount of time to add.
|
||||
*/
|
||||
private static synchronized void addToTotals(long jobId, long matchTimeInc) {
|
||||
|
@ -26,7 +26,6 @@ import org.sleuthkit.autopsy.ingest.IngestModuleFactory;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel;
|
||||
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.
|
||||
@ -92,35 +91,6 @@ public class FileTypeIdModuleFactory extends IngestModuleFactoryAdapter {
|
||||
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
|
||||
*/
|
||||
@ -134,11 +104,6 @@ public class FileTypeIdModuleFactory extends IngestModuleFactoryAdapter {
|
||||
*/
|
||||
@Override
|
||||
public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) {
|
||||
assert settings instanceof FileTypeIdModuleSettings;
|
||||
if (!(settings instanceof FileTypeIdModuleSettings)) {
|
||||
throw new IllegalArgumentException(
|
||||
NbBundle.getMessage(this.getClass(), "FileTypeIdModuleFactory.createFileIngestModule.exception.msg"));
|
||||
}
|
||||
return new FileTypeIdIngestModule((FileTypeIdModuleSettings) settings);
|
||||
return new FileTypeIdIngestModule();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -152,7 +152,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
|
||||
// Initialize job totals
|
||||
initTotalsForIngestJob(jobId);
|
||||
} 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) {
|
||||
// No worries.
|
||||
} 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;
|
||||
}
|
||||
|
@ -983,8 +983,8 @@ class ReportHTML implements TableReportModule {
|
||||
StringBuilder head = new StringBuilder();
|
||||
head.append("<html>\n<head>\n<title>").append( //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("<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("#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
|
||||
|
@ -440,6 +440,7 @@ public class TimeLineController {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // TODO (EUR-733): Do not use SleuthkitCase.getLastObjectId
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
||||
@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.",
|
||||
|
@ -461,6 +461,7 @@ public class EventsRepository {
|
||||
updateProgress(workDone, total);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // TODO (EUR-733): Do not use SleuthkitCase.getLastObjectId
|
||||
@Override
|
||||
@NbBundle.Messages({"progressWindow.msg.refreshingFileTags=Refreshing file tags",
|
||||
"progressWindow.msg.refreshingResultTags=Refreshing result tags",
|
||||
|
@ -6,8 +6,8 @@ OpenIDE-Module-Name=ImageGallery
|
||||
OpenIDE-Module-Short-Description=Advanced image and video gallery
|
||||
ImageGalleryOptionsPanel.enabledForCaseBox.text=Enable Image Gallery updates for the current case.
|
||||
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.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.infoIconLabel.text=
|
||||
ImageGalleryOptionsPanel.groupCategorizationWarningBox.text=Don't show a warning when overwriting categories, by acting on an entire group.
|
||||
|
@ -107,6 +107,8 @@ public enum FileTypeUtils {
|
||||
, "sn", "ras" //sun raster NON-NLS
|
||||
, "ico" //windows icons 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
|
||||
@ -129,6 +131,8 @@ public enum FileTypeUtils {
|
||||
* mime types.
|
||||
*/
|
||||
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
|
||||
supportedMimeTypes.addAll(Stream.of(ImageIO.getReaderMIMETypes())
|
||||
|
@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.imagegallery;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@ -34,6 +35,7 @@ import javafx.beans.Observable;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
@ -54,7 +56,6 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.netbeans.api.progress.ProgressHandleFactory;
|
||||
import org.openide.util.Cancellable;
|
||||
@ -85,6 +86,7 @@ import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.VirtualDirectory;
|
||||
|
||||
/**
|
||||
* 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 Runnable showTree;
|
||||
private Toolbar toolbar;
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
@ -127,14 +130,13 @@ public final class ImageGalleryController implements Executor {
|
||||
*/
|
||||
private final SimpleBooleanProperty listeningEnabled = new SimpleBooleanProperty(false);
|
||||
|
||||
private final ReadOnlyIntegerWrapper queueSizeProperty = new ReadOnlyIntegerWrapper(0);
|
||||
|
||||
private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false);
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private final ReadOnlyBooleanWrapper stale = new ReadOnlyBooleanWrapper(false);
|
||||
|
||||
private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false);
|
||||
private final ReadOnlyDoubleWrapper thumbnailSize = new ReadOnlyDoubleWrapper(100);
|
||||
|
||||
private final FileIDSelectionModel selectionModel = new FileIDSelectionModel(this);
|
||||
|
||||
@ -153,7 +155,6 @@ public final class ImageGalleryController implements Executor {
|
||||
|
||||
private Node infoOverlay;
|
||||
private SleuthkitCase sleuthKitCase;
|
||||
// private NavPanel navPanel;
|
||||
|
||||
public ReadOnlyBooleanProperty getMetaDataCollapsed() {
|
||||
return metaDataCollapsed.getReadOnlyProperty();
|
||||
@ -163,6 +164,10 @@ public final class ImageGalleryController implements Executor {
|
||||
this.metaDataCollapsed.set(metaDataCollapsed);
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleProperty thumbnailSizeProperty() {
|
||||
return thumbnailSize.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
private GroupViewState getViewState() {
|
||||
return historyManager.getCurrentState();
|
||||
}
|
||||
@ -175,7 +180,7 @@ public final class ImageGalleryController implements Executor {
|
||||
return historyManager.currentState();
|
||||
}
|
||||
|
||||
public synchronized FileIDSelectionModel getSelectionModel() {
|
||||
public FileIDSelectionModel getSelectionModel() {
|
||||
return selectionModel;
|
||||
}
|
||||
|
||||
@ -187,12 +192,16 @@ public final class ImageGalleryController implements Executor {
|
||||
return db;
|
||||
}
|
||||
|
||||
synchronized public void setListeningEnabled(boolean enabled) {
|
||||
listeningEnabled.set(enabled);
|
||||
public void setListeningEnabled(boolean enabled) {
|
||||
synchronized (listeningEnabled) {
|
||||
listeningEnabled.set(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized boolean isListeningEnabled() {
|
||||
return listeningEnabled.get();
|
||||
boolean isListeningEnabled() {
|
||||
synchronized (listeningEnabled) {
|
||||
return listeningEnabled.get();
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
||||
@ -248,12 +257,14 @@ public final class ImageGalleryController implements Executor {
|
||||
checkForGroups();
|
||||
});
|
||||
|
||||
IngestManager.getInstance().addIngestModuleEventListener((PropertyChangeEvent evt) -> {
|
||||
Platform.runLater(this::updateRegroupDisabled);
|
||||
});
|
||||
IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> {
|
||||
Platform.runLater(this::updateRegroupDisabled);
|
||||
});
|
||||
IngestManager ingestManager = IngestManager.getInstance();
|
||||
PropertyChangeListener ingestEventHandler =
|
||||
propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled);
|
||||
|
||||
ingestManager.addIngestModuleEventListener(ingestEventHandler);
|
||||
ingestManager.addIngestJobEventListener(ingestEventHandler);
|
||||
|
||||
queueSizeProperty.addListener(obs -> this.updateRegroupDisabled());
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty getCanAdvance() {
|
||||
@ -280,8 +291,9 @@ public final class ImageGalleryController implements Executor {
|
||||
return historyManager.retreat();
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
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.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
@NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. " +
|
||||
" No groups will be available until ingest is finished and listening is re-enabled.",
|
||||
"ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.",
|
||||
"ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.",
|
||||
"ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. " +
|
||||
" No groups will be available until ingest is finished and listening is re-enabled.",
|
||||
"ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.",
|
||||
"ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:" +
|
||||
" the current Group By setting resulted in no groups, " +
|
||||
"or no groups are fully analyzed but ingest is not running."})
|
||||
@NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. "
|
||||
+ " No groups will be available until ingest is finished and listening is re-enabled.",
|
||||
"ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.",
|
||||
"ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.",
|
||||
"ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. "
|
||||
+ " No groups will be available until ingest is finished and listening is re-enabled.",
|
||||
"ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.",
|
||||
"ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:"
|
||||
+ " the current Group By setting resulted in no groups, "
|
||||
+ "or no groups are fully analyzed but ingest is not running."})
|
||||
public void checkForGroups() {
|
||||
if (groupManager.getAnalyzedGroups().isEmpty()) {
|
||||
if (IngestManager.getInstance().isIngestRunning()) {
|
||||
@ -312,7 +324,7 @@ public final class ImageGalleryController implements Executor {
|
||||
new ProgressIndicator()));
|
||||
}
|
||||
|
||||
} else if (getFileUpdateQueueSizeProperty().get() > 0) {
|
||||
} else if (queueSizeProperty.get() > 0) {
|
||||
replaceNotification(fullUIStackPane,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(),
|
||||
new ProgressIndicator()));
|
||||
@ -357,20 +369,14 @@ public final class ImageGalleryController implements Executor {
|
||||
}
|
||||
}
|
||||
|
||||
private void restartWorker() {
|
||||
if (dbWorkerThread != null) {
|
||||
synchronized private DBWorkerThread restartWorker() {
|
||||
if (dbWorkerThread == null) {
|
||||
dbWorkerThread = new DBWorkerThread(this);
|
||||
dbWorkerThread.start();
|
||||
} else {
|
||||
// Keep using the same worker thread if one exists
|
||||
return;
|
||||
}
|
||||
dbWorkerThread = new DBWorkerThread();
|
||||
|
||||
getFileUpdateQueueSizeProperty().addListener((Observable o) -> {
|
||||
Platform.runLater(this::updateRegroupDisabled);
|
||||
});
|
||||
|
||||
Thread th = new Thread(dbWorkerThread, "DB-Worker-Thread");
|
||||
th.setDaemon(false); // we want it to go away when it is done
|
||||
th.start();
|
||||
return dbWorkerThread;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -411,15 +417,18 @@ public final class ImageGalleryController implements Executor {
|
||||
setListeningEnabled(false);
|
||||
ThumbnailCache.getDefault().clearCache();
|
||||
historyManager.clear();
|
||||
groupManager.clear();
|
||||
tagsManager.clearFollowUpTagName();
|
||||
tagsManager.unregisterListener(groupManager);
|
||||
tagsManager.unregisterListener(categoryManager);
|
||||
dbWorkerThread.cancelAllTasks();
|
||||
dbWorkerThread.cancel();
|
||||
dbWorkerThread = null;
|
||||
restartWorker();
|
||||
dbWorkerThread = restartWorker();
|
||||
|
||||
if (toolbar != null) {
|
||||
toolbar.reset();
|
||||
}
|
||||
|
||||
Toolbar.getDefault(this).reset();
|
||||
groupManager.clear();
|
||||
if (db != null) {
|
||||
db.closeDBCon();
|
||||
}
|
||||
@ -431,17 +440,15 @@ public final class ImageGalleryController implements Executor {
|
||||
*
|
||||
* @param innerTask
|
||||
*/
|
||||
public void queueDBWorkerTask(InnerTask innerTask) {
|
||||
|
||||
// @@@ We could make a lock for the worker thread
|
||||
public synchronized void queueDBWorkerTask(BackgroundTask innerTask) {
|
||||
if (dbWorkerThread == null) {
|
||||
restartWorker();
|
||||
dbWorkerThread = restartWorker();
|
||||
}
|
||||
dbWorkerThread.addTask(innerTask);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
synchronized public DrawableFile<?> getFileFromId(Long fileID) throws TskCoreException {
|
||||
synchronized public DrawableFile getFileFromId(Long fileID) throws TskCoreException {
|
||||
if (Objects.isNull(db)) {
|
||||
LOGGER.log(Level.WARNING, "Could not get file from id, no DB set. The case is probably closed."); //NON-NLS
|
||||
return null;
|
||||
@ -455,8 +462,12 @@ public final class ImageGalleryController implements Executor {
|
||||
Platform.runLater(this::checkForGroups);
|
||||
}
|
||||
|
||||
public ReadOnlyIntegerProperty getFileUpdateQueueSizeProperty() {
|
||||
return queueSizeProperty.getReadOnlyProperty();
|
||||
public synchronized void setToolbar(Toolbar toolbar) {
|
||||
if (this.toolbar != null) {
|
||||
throw new IllegalStateException("Can not set the toolbar a second time!");
|
||||
}
|
||||
this.toolbar = toolbar;
|
||||
thumbnailSize.bind(toolbar.thumbnailSizeProperty());
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleProperty regroupProgress() {
|
||||
@ -496,29 +507,43 @@ public final class ImageGalleryController implements Executor {
|
||||
return undoManager;
|
||||
}
|
||||
|
||||
// @@@ REVIEW IF THIS SHOLD BE STATIC...
|
||||
//TODO: concept seems like the controller deal with how much work to do at a given time
|
||||
public ReadOnlyIntegerProperty getDBTasksQueueSizeProperty() {
|
||||
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.)
|
||||
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
|
||||
private volatile boolean cancelled = false;
|
||||
|
||||
// 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.
|
||||
* Note that after you cancel, you cannot submit new jobs to this
|
||||
* thread.
|
||||
*/
|
||||
public void cancelAllTasks() {
|
||||
@Override
|
||||
public boolean cancel() {
|
||||
cancelled = true;
|
||||
for (InnerTask it : workQueue) {
|
||||
for (BackgroundTask it : workQueue) {
|
||||
it.cancel();
|
||||
}
|
||||
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
|
||||
*/
|
||||
public void addTask(InnerTask it) {
|
||||
public void addTask(BackgroundTask it) {
|
||||
workQueue.add(it);
|
||||
Platform.runLater(() -> {
|
||||
queueSizeProperty.set(workQueue.size());
|
||||
});
|
||||
int size = workQueue.size();
|
||||
Platform.runLater(() -> controller.queueSizeProperty.set(size));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -538,19 +562,17 @@ public final class ImageGalleryController implements Executor {
|
||||
|
||||
// nearly infinite loop waiting for tasks
|
||||
while (true) {
|
||||
if (cancelled) {
|
||||
if (cancelled || isInterrupted()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
InnerTask it = workQueue.take();
|
||||
BackgroundTask it = workQueue.take();
|
||||
|
||||
if (it.isCancelled() == false) {
|
||||
it.run();
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
queueSizeProperty.set(workQueue.size());
|
||||
});
|
||||
int size = workQueue.size();
|
||||
Platform.runLater(() -> controller.queueSizeProperty.set(size));
|
||||
|
||||
} catch (InterruptedException ex) {
|
||||
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}
|
||||
*/
|
||||
@NbBundle.Messages({"ImageGalleryController.InnerTask.progress.name=progress",
|
||||
"ImageGalleryController.InnerTask.message.name=status"})
|
||||
static public abstract class InnerTask implements Runnable, Cancellable {
|
||||
"ImageGalleryController.InnerTask.message.name=status"})
|
||||
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() {
|
||||
return progress.get();
|
||||
@ -585,9 +614,6 @@ public final class ImageGalleryController implements Executor {
|
||||
public final void updateMessage(String 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() {
|
||||
return progress;
|
||||
@ -601,24 +627,21 @@ public final class ImageGalleryController implements Executor {
|
||||
return state.get();
|
||||
}
|
||||
|
||||
protected void updateState(Worker.State newState) {
|
||||
state.set(newState);
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<Worker.State> stateProperty() {
|
||||
return new ReadOnlyObjectWrapper<>(state.get());
|
||||
}
|
||||
|
||||
protected InnerTask() {
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public boolean cancel() {
|
||||
public synchronized boolean cancel() {
|
||||
updateState(Worker.State.CANCELLED);
|
||||
return true;
|
||||
}
|
||||
|
||||
synchronized protected boolean isCancelled() {
|
||||
protected void updateState(Worker.State newState) {
|
||||
state.set(newState);
|
||||
}
|
||||
|
||||
protected synchronized boolean isCancelled() {
|
||||
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
|
||||
*/
|
||||
static public abstract class FileTask extends InnerTask {
|
||||
static public abstract class FileTask extends BackgroundTask {
|
||||
|
||||
private final AbstractFile file;
|
||||
private final DrawableDB taskDB;
|
||||
@ -644,7 +667,6 @@ public final class ImageGalleryController implements Executor {
|
||||
this.file = f;
|
||||
this.taskDB = taskDB;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -662,7 +684,7 @@ public final class ImageGalleryController implements Executor {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
DrawableFile<?> drawableFile = DrawableFile.create(getFile(), true, getTaskDB().isVideoFile(getFile()));
|
||||
DrawableFile drawableFile = DrawableFile.create(getFile(), true, false);
|
||||
getTaskDB().updateFile(drawableFile);
|
||||
} catch (NullPointerException ex) {
|
||||
// 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 {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.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 {
|
||||
@NbBundle.Messages({"BulkTask.committingDb.status=commiting image/video database",
|
||||
"BulkTask.stopCopy.status=Stopping copy to drawable db task.",
|
||||
"BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."})
|
||||
abstract static private class BulkTransferTask extends BackgroundTask {
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
private final DrawableDB taskDB;
|
||||
private final SleuthkitCase tskCase;
|
||||
static private final String FILE_EXTENSION_CLAUSE =
|
||||
"(name LIKE '%." //NON-NLS
|
||||
+ 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.taskDB = taskDB;
|
||||
this.tskCase = tskCase;
|
||||
}
|
||||
|
||||
static private final String FILE_EXTENSION_CLAUSE =
|
||||
"(name LIKE '%." //NON-NLS
|
||||
+ StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") //NON-NLS
|
||||
+ "')";
|
||||
abstract void cleanup(boolean success);
|
||||
|
||||
static private final String MIMETYPE_CLAUSE =
|
||||
"(mime_type LIKE '" //NON-NLS
|
||||
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(), "' OR mime_type LIKE '") //NON-NLS
|
||||
+ "') ";
|
||||
abstract List<AbstractFile> getFiles() throws TskCoreException;
|
||||
|
||||
static private 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
|
||||
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle(Bundle.CopyAnalyzedFiles_populatingDb_status());
|
||||
abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) throws TskCoreException;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
progressHandle = getInitialProgressHandle();
|
||||
progressHandle.start();
|
||||
updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status());
|
||||
|
||||
try {
|
||||
//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());
|
||||
|
||||
updateProgress(0.0);
|
||||
|
||||
//do in transaction
|
||||
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
|
||||
int units = 0;
|
||||
int workDone = 0;
|
||||
for (final AbstractFile f : files) {
|
||||
if (isCancelled()) {
|
||||
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;
|
||||
}
|
||||
|
||||
final boolean known = f.getKnown() == TskData.FileKnown.KNOWN;
|
||||
processFile(f, tr);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
units++;
|
||||
final int prog = units;
|
||||
progressHandle.progress(f.getName(), units);
|
||||
updateProgress(prog - 1 / (double) files.size());
|
||||
workDone++;
|
||||
progressHandle.progress(f.getName(), workDone);
|
||||
updateProgress(workDone - 1 / (double) files.size());
|
||||
updateMessage(f.getName());
|
||||
}
|
||||
|
||||
progressHandle.finish();
|
||||
|
||||
progressHandle = ProgressHandleFactory.createHandle(Bundle.CopyAnalyzedFiles_committingDb_status());
|
||||
updateMessage(Bundle.CopyAnalyzedFiles_committingDb_status());
|
||||
progressHandle = ProgressHandleFactory.createHandle(Bundle.BulkTask_committingDb_status());
|
||||
updateMessage(Bundle.BulkTask_committingDb_status());
|
||||
updateProgress(1.0);
|
||||
|
||||
progressHandle.start();
|
||||
taskDB.commitTransaction(tr, true);
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
progressHandle.progress(Bundle.CopyAnalyzedFiles_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
|
||||
MessageNotifyUtil.Notify.warn(Bundle.CopyAnalyzedFiles_errPopulating_errMsg(), ex.getMessage());
|
||||
progressHandle.progress(Bundle.BulkTask_stopCopy_status());
|
||||
LOGGER.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.warn(Bundle.BulkTask_errPopulating_errMsg(), ex.getMessage());
|
||||
cleanup(false);
|
||||
return;
|
||||
} finally {
|
||||
progressHandle.finish();
|
||||
updateMessage("");
|
||||
updateProgress(-1.0);
|
||||
controller.setStale(true);
|
||||
return;
|
||||
}
|
||||
cleanup(true);
|
||||
}
|
||||
|
||||
progressHandle.finish();
|
||||
updateMessage("");
|
||||
updateProgress(-1.0);
|
||||
controller.setStale(false);
|
||||
abstract ProgressHandle getInitialProgressHandle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* fs_obj_id to identify files from new datasources)
|
||||
* Copy files from a newly added data source into the DB. Get all "drawable"
|
||||
* 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
|
||||
* netbeans and ImageGallery progress/status
|
||||
*/
|
||||
@NbBundle.Messages({"PrePopulateDataSourceFiles.prepopulatingDb.status=prepopulating image/video database",
|
||||
"PrePopulateDataSourceFiles.committingDb.status=commiting image/video database"})
|
||||
private class PrePopulateDataSourceFiles extends InnerTask {
|
||||
@NbBundle.Messages({"PrePopulateDataSourceFiles.committingDb.status=commiting image/video database"})
|
||||
static private class PrePopulateDataSourceFiles extends BulkTransferTask {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(PrePopulateDataSourceFiles.class.getName());
|
||||
|
||||
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
|
||||
*/
|
||||
PrePopulateDataSourceFiles(Content dataSource) {
|
||||
super();
|
||||
PrePopulateDataSourceFiles(Content dataSource, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||
super(controller, taskDB, tskCase);
|
||||
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
|
||||
public void run() {
|
||||
progressHandle.start();
|
||||
updateMessage(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status());
|
||||
protected void cleanup(boolean success) {
|
||||
}
|
||||
|
||||
try {
|
||||
String fsQuery = "(fs_obj_id IS NULL) "; //default clause NON-NLS
|
||||
@Override
|
||||
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 in DB. for them, we will not specify a fs_obj_id,
|
||||
* which means we will grab files from another data source, but
|
||||
* the drawable DB is smart enough to de-dupe them. For Images
|
||||
* we can do better.
|
||||
* fs_obj_id is set only for file system files, so we will match
|
||||
* the VirtualDirectory's name in the parent path.
|
||||
*
|
||||
* TODO: A future database schema could probably make this
|
||||
* cleaner. If we had a datasource_id column in the files table
|
||||
* we could just match agains that.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
//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
|
||||
return tskCase.findAllFilesWhere(" parent_path LIKE '/" + dataSource.getName() + "/%' AND " + DRAWABLE_QUERY); //NON-NLS
|
||||
} else {
|
||||
String msg = "Uknown datasource type: " + dataSource.getClass().getName();
|
||||
LOGGER.log(Level.SEVERE, msg);
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Content newDataSource = (Content) evt.getNewValue();
|
||||
if (isListeningEnabled()) {
|
||||
queueDBWorkerTask(new PrePopulateDataSourceFiles(newDataSource));
|
||||
queueDBWorkerTask(new PrePopulateDataSourceFiles(newDataSource, ImageGalleryController.this, getDatabase(), getSleuthKitCase()));
|
||||
} else {//TODO: keep track of what we missed for later
|
||||
setStale(true);
|
||||
}
|
||||
@ -1028,7 +1061,6 @@ public final class ImageGalleryController implements Executor {
|
||||
getTagsManager().fireTagDeletedEvent(tagDeletedEvent);
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,42 +27,53 @@
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="21" pref="21" max="-2" attributes="0"/>
|
||||
<Component id="unavailableDuringInjestLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="103" groupAlignment="0" max="-2" 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 type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="21" pref="21" max="-2" attributes="0"/>
|
||||
<Component id="unavailableDuringInjestLabel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="enabledByDefaultBox" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="enabledForCaseBox" alignment="0" min="-2" max="-2" 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>
|
||||
<Component id="enabledByDefaultBox" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="enabledForCaseBox" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="descriptionLabel" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="36" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jSeparator1" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace pref="46" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<EmptySpace 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">
|
||||
<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>
|
||||
<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>
|
||||
</DimensionLayout>
|
||||
@ -145,9 +156,6 @@
|
||||
<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"/>
|
||||
</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, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="unavailableDuringInjestLabel">
|
||||
@ -165,5 +173,14 @@
|
||||
</Property>
|
||||
</Properties>
|
||||
</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, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,17 +18,17 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
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
|
||||
*
|
||||
* Uses {@link ImageGalleryPreferences} and {@link PerCaseProperties} to
|
||||
* persist settings
|
||||
* Uses {@link ImageGalleryPreferences} and {@link PerCaseProperties} to persist
|
||||
* settings
|
||||
*/
|
||||
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.
|
||||
* WARNING: Do NOT modify this code. The content of this method is
|
||||
* always regenerated by the Form Editor.
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
@ -64,6 +64,8 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
furtherDescriptionArea = new javax.swing.JTextArea();
|
||||
infoIconLabel = 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));
|
||||
|
||||
@ -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.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.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(groupCategorizationWarningBox, NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.groupCategorizationWarningBox.text")); // NOI18N
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
@ -112,18 +115,23 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(21, 21, 21)
|
||||
.addComponent(unavailableDuringInjestLabel))
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(21, 21, 21)
|
||||
.addComponent(infoIconLabel)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(furtherDescriptionArea, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addComponent(enabledByDefaultBox)
|
||||
.addComponent(enabledForCaseBox)
|
||||
.addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
.addContainerGap(46, Short.MAX_VALUE))
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(21, 21, 21)
|
||||
.addComponent(unavailableDuringInjestLabel))
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(enabledByDefaultBox)
|
||||
.addComponent(enabledForCaseBox)
|
||||
.addComponent(descriptionLabel, javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(21, 21, 21)
|
||||
.addComponent(infoIconLabel)
|
||||
.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.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
@ -139,8 +147,12 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
.addGap(18, 18, 18)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(infoIconLabel)
|
||||
.addComponent(furtherDescriptionArea, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addGap(45, 45, 45))
|
||||
.addComponent(furtherDescriptionArea, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.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
|
||||
|
||||
@ -152,7 +164,6 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
// TODO add your handling code here:
|
||||
}//GEN-LAST:event_enabledForCaseBoxActionPerformed
|
||||
|
||||
/** {@inheritDoc} */
|
||||
void load() {
|
||||
enabledByDefaultBox.setSelected(ImageGalleryPreferences.isEnabledByDefault());
|
||||
if (Case.isCaseOpen() && IngestManager.getInstance().isIngestRunning() == false) {
|
||||
@ -162,20 +173,21 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
enabledForCaseBox.setEnabled(false);
|
||||
enabledForCaseBox.setSelected(enabledByDefaultBox.isSelected());
|
||||
}
|
||||
groupCategorizationWarningBox.setSelected(ImageGalleryPreferences.isGroupCategorizationWarningDisabled());
|
||||
}
|
||||
|
||||
/** {@inheritDoc } */
|
||||
void store() {
|
||||
ImageGalleryPreferences.setEnabledByDefault(enabledByDefaultBox.isSelected());
|
||||
ImageGalleryController.getDefault().setListeningEnabled(enabledForCaseBox.isSelected());
|
||||
if (Case.isCaseOpen()) {
|
||||
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() {
|
||||
// TODO check whether form is consistent and complete
|
||||
return true;
|
||||
@ -186,7 +198,9 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
private javax.swing.JCheckBox enabledByDefaultBox;
|
||||
private javax.swing.JCheckBox enabledForCaseBox;
|
||||
private javax.swing.JTextArea furtherDescriptionArea;
|
||||
private javax.swing.JCheckBox groupCategorizationWarningBox;
|
||||
private javax.swing.JLabel infoIconLabel;
|
||||
private javax.swing.JSeparator jSeparator1;
|
||||
private javax.swing.JLabel unavailableDuringInjestLabel;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 Basis Technology Corp.
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* 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 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);
|
||||
|
||||
/** 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 GROUP_CATEGORIZATION_WARNING_DISABLED = "group_categorization_warning_disabled"; //NON-NLS
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static boolean isEnabledByDefault() {
|
||||
public static boolean isEnabledByDefault() {
|
||||
final boolean aBoolean = preferences.getBoolean(ENABLED_BY_DEFAULT, true);
|
||||
return aBoolean;
|
||||
}
|
||||
|
||||
static void setEnabledByDefault(boolean b) {
|
||||
public static void setEnabledByDefault(boolean 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) {
|
||||
preferences.addPreferenceChangeListener(l);
|
||||
}
|
||||
|
@ -148,7 +148,8 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
||||
fullUIStack.getChildren().add(borderPane);
|
||||
splitPane = new SplitPane();
|
||||
borderPane.setCenter(splitPane);
|
||||
borderPane.setTop(Toolbar.getDefault(controller));
|
||||
Toolbar toolbar = new Toolbar(controller);
|
||||
borderPane.setTop(toolbar);
|
||||
borderPane.setBottom(new StatusBar(controller));
|
||||
|
||||
metaDataTable = new MetaDataPane(controller);
|
||||
@ -157,16 +158,18 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
||||
hashHitList = new HashHitGroupList(controller);
|
||||
|
||||
TabPane tabPane = new TabPane(groupTree, hashHitList);
|
||||
|
||||
tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE);
|
||||
tabPane.setMinWidth(TabPane.USE_PREF_SIZE);
|
||||
VBox.setVgrow(tabPane, Priority.ALWAYS);
|
||||
leftPane = new VBox(tabPane, new SummaryTablePane(controller));
|
||||
SplitPane.setResizableWithParent(leftPane, Boolean.FALSE);
|
||||
SplitPane.setResizableWithParent(groupPane, Boolean.TRUE);
|
||||
SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE);
|
||||
splitPane.getItems().addAll(leftPane, centralStack, metaDataTable);
|
||||
splitPane.setDividerPositions(0.0, 1.0);
|
||||
splitPane.setDividerPositions(0.1, 1.0);
|
||||
|
||||
ImageGalleryController.getDefault().setStacks(fullUIStack, centralStack);
|
||||
ImageGalleryController.getDefault().setToolbar(toolbar);
|
||||
ImageGalleryController.getDefault().setShowTree(() -> tabPane.getSelectionModel().select(groupTree));
|
||||
});
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ public enum ThumbnailCache {
|
||||
* could not be generated
|
||||
*/
|
||||
@Nullable
|
||||
public Image get(DrawableFile<?> file) {
|
||||
public Image get(DrawableFile file) {
|
||||
try {
|
||||
return cache.get(file.getId(), () -> load(file));
|
||||
} catch (UncheckedExecutionException | CacheLoader.InvalidCacheLoadException | ExecutionException ex) {
|
||||
@ -124,9 +124,9 @@ public enum ThumbnailCache {
|
||||
*
|
||||
* @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,
|
||||
//NOTE: not saved to disk!
|
||||
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
|
||||
* empty optional if there was a problem.
|
||||
*/
|
||||
private static Optional<File> getCacheFile(DrawableFile<?> file) {
|
||||
private static Optional<File> getCacheFile(DrawableFile file) {
|
||||
try {
|
||||
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());
|
||||
if (thumbnail != null) {
|
||||
return TaskUtils.taskFrom(() -> thumbnail);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 Basis Technology Corp.
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* 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.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
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.MenuItem;
|
||||
import javafx.scene.image.ImageView;
|
||||
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.windows.TopComponent;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog;
|
||||
import org.sleuthkit.autopsy.actions.GetTagNameDialog;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
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.TskCoreException;
|
||||
|
||||
/**
|
||||
* An abstract base class for actions that allow users to tag SleuthKit data
|
||||
* 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.
|
||||
* Instances of this Action allow users to apply tags to content.
|
||||
*/
|
||||
abstract class AddTagAction {
|
||||
public class AddTagAction extends Action {
|
||||
|
||||
protected static final String NO_COMMENT = "";
|
||||
private static final Logger LOGGER = Logger.getLogger(AddTagAction.class.getName());
|
||||
|
||||
/**
|
||||
* Template method to allow derived classes to provide a string for a menu
|
||||
* item label.
|
||||
*/
|
||||
abstract protected String getActionDisplayName();
|
||||
private final ImageGalleryController controller;
|
||||
private final Set<Long> selectedFileIDs;
|
||||
private final TagName tagName;
|
||||
|
||||
/**
|
||||
* Template method to allow derived classes to add the indicated tag and
|
||||
* comment to one or more a SleuthKit data model objects.
|
||||
*/
|
||||
abstract protected void addTag(TagName tagName, String comment);
|
||||
public AddTagAction(ImageGalleryController controller, TagName tagName, Set<Long> selectedFileIDs) {
|
||||
super(tagName.getDisplayName());
|
||||
this.controller = controller;
|
||||
this.selectedFileIDs = selectedFileIDs;
|
||||
this.tagName = tagName;
|
||||
setGraphic(controller.getTagsManager().getGraphic(tagName));
|
||||
setText(tagName.getDisplayName());
|
||||
setEventHandler(actionEvent -> addTagWithComment(""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method to allow derived classes to add the indicated tag and
|
||||
* comment to a list of one or more file IDs.
|
||||
*/
|
||||
abstract protected void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles);
|
||||
static public Menu getTagMenu(ImageGalleryController controller) {
|
||||
return new TagMenu(controller);
|
||||
}
|
||||
|
||||
private void addTagWithComment(String comment) {
|
||||
addTagsToFiles(tagName, comment, selectedFileIDs);
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"# {0} - fileID",
|
||||
"AddDrawableTagAction.addTagsToFiles.alert=Unable to tag file {0}."})
|
||||
private void addTagsToFiles(TagName tagName, String comment, Set<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",
|
||||
"AddTagAction.menuItem.noTags=No tags",
|
||||
"AddTagAction.menuItem.newTag=New Tag...",
|
||||
"AddTagAction.menuItem.tagAndComment=Tag and Comment..."})
|
||||
protected class TagMenu extends Menu {
|
||||
"AddTagAction.menuItem.noTags=No tags",
|
||||
"AddTagAction.menuItem.newTag=New Tag...",
|
||||
"AddTagAction.menuItem.tagAndComment=Tag and Comment...",
|
||||
"AddDrawableTagAction.displayName.plural=Tag Files",
|
||||
"AddDrawableTagAction.displayName.singular=Tag File"})
|
||||
private static class TagMenu extends Menu {
|
||||
|
||||
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.
|
||||
Menu quickTagMenu = new Menu(Bundle.AddTagAction_menuItem_quickTag());
|
||||
@ -98,10 +159,8 @@ abstract class AddTagAction {
|
||||
quickTagMenu.getItems().add(empty);
|
||||
} else {
|
||||
for (final TagName tagName : tagNames) {
|
||||
MenuItem tagNameItem = new MenuItem(tagName.getDisplayName());
|
||||
tagNameItem.setOnAction((ActionEvent t) -> {
|
||||
addTag(tagName, NO_COMMENT);
|
||||
});
|
||||
AddTagAction addDrawableTagAction = new AddTagAction(controller, tagName, selectedFileIDs);
|
||||
MenuItem tagNameItem = ActionUtils.createMenuItem(addDrawableTagAction);
|
||||
quickTagMenu.getItems().add(tagNameItem);
|
||||
}
|
||||
}
|
||||
@ -112,14 +171,13 @@ abstract class AddTagAction {
|
||||
* or select a tag name and adds a tag with the resulting name.
|
||||
*/
|
||||
MenuItem newTagMenuItem = new MenuItem(Bundle.AddTagAction_menuItem_newTag());
|
||||
newTagMenuItem.setOnAction((ActionEvent t) -> {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
TagName tagName = GetTagNameDialog.doDialog(getIGWindow());
|
||||
if (tagName != null) {
|
||||
addTag(tagName, NO_COMMENT);
|
||||
}
|
||||
});
|
||||
});
|
||||
newTagMenuItem.setOnAction(actionEvent ->
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
TagName tagName = GetTagNameDialog.doDialog(getIGWindow());
|
||||
if (tagName != null) {
|
||||
new AddTagAction(controller, tagName, selectedFileIDs).handle(actionEvent);
|
||||
}
|
||||
}));
|
||||
quickTagMenu.getItems().add(newTagMenuItem);
|
||||
|
||||
/*
|
||||
@ -129,26 +187,17 @@ abstract class AddTagAction {
|
||||
* name.
|
||||
*/
|
||||
MenuItem tagAndCommentItem = new MenuItem(Bundle.AddTagAction_menuItem_tagAndComment());
|
||||
tagAndCommentItem.setOnAction((ActionEvent t) -> {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow());
|
||||
if (null != tagNameAndComment) {
|
||||
if (CategoryManager.isCategoryTagName(tagNameAndComment.getTagName())) {
|
||||
new CategorizeAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
|
||||
} else {
|
||||
new AddDrawableTagAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
|
||||
tagAndCommentItem.setOnAction(actionEvent ->
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog(getIGWindow());
|
||||
if (null != tagNameAndComment) {
|
||||
new AddTagAction(controller, tagNameAndComment.getTagName(), selectedFileIDs).addTagWithComment(tagNameAndComment.getComment());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
getItems().add(tagAndCommentItem);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Window containing the ImageGalleryTopComponent
|
||||
*/
|
||||
static private Window getIGWindow() {
|
||||
TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
|
||||
return SwingUtilities.getWindowAncestor(etc);
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 Basis Technology Corp.
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* 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 java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.collections.ObservableSet;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
@ -49,48 +51,43 @@ import org.sleuthkit.datamodel.TagName;
|
||||
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"})
|
||||
public class CategorizeAction extends AddTagAction {
|
||||
public class CategorizeAction extends Action {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CategorizeAction.class.getName());
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
private final UndoRedoManager undoManager;
|
||||
private final Category cat;
|
||||
private final Set<Long> selectedFileIDs;
|
||||
private final Boolean createUndo;
|
||||
|
||||
public CategorizeAction(ImageGalleryController controller) {
|
||||
super();
|
||||
this.controller = controller;
|
||||
undoManager = controller.getUndoManager();
|
||||
public CategorizeAction(ImageGalleryController controller, Category cat, Set<Long> selectedFileIDs) {
|
||||
this(controller, cat, selectedFileIDs, true);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getActionDisplayName() {
|
||||
return Bundle.CategorizeAction_displayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTag(TagName tagName, String comment) {
|
||||
Set<Long> selectedFiles = new HashSet<>(controller.getSelectionModel().getSelected());
|
||||
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));
|
||||
final void addCatToFiles(Set<Long> ids) {
|
||||
Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{ids.toString(), cat.getDisplayName()}); //NON-NLS
|
||||
controller.queueDBWorkerTask(new CategorizeTask(ids, cat, createUndo));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,61 +98,55 @@ public class CategorizeAction extends AddTagAction {
|
||||
|
||||
CategoryMenu(ImageGalleryController controller) {
|
||||
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
|
||||
// a tag with the associated category.
|
||||
for (final Category cat : Category.values()) {
|
||||
|
||||
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()))));
|
||||
MenuItem categoryItem = ActionUtils.createMenuItem(new CategorizeAction(controller, cat, selected));
|
||||
getItems().add(categoryItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"# {0} - fileID number",
|
||||
"CategorizeTask.errorUnable.msg=Unable to categorize {0}.",
|
||||
"CategorizeTask.errorUnable.title=Categorizing Error"})
|
||||
private class CategorizeTask extends ImageGalleryController.InnerTask {
|
||||
"CategorizeTask.errorUnable.msg=Unable to categorize {0}.",
|
||||
"CategorizeTask.errorUnable.title=Categorizing Error"})
|
||||
private class CategorizeTask extends ImageGalleryController.BackgroundTask {
|
||||
|
||||
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();
|
||||
this.fileIDs = fileIDs;
|
||||
java.util.Objects.requireNonNull(tagName);
|
||||
this.tagName = tagName;
|
||||
this.comment = comment;
|
||||
java.util.Objects.requireNonNull(cat);
|
||||
this.cat = cat;
|
||||
this.createUndo = createUndo;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final DrawableTagsManager tagsManager = controller.getTagsManager();
|
||||
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) {
|
||||
try {
|
||||
DrawableFile<?> file = controller.getFileFromId(fileID); //drawable db access
|
||||
DrawableFile file = controller.getFileFromId(fileID); //drawable db access
|
||||
if (createUndo) {
|
||||
Category oldCat = file.getCategory(); //drawable db access
|
||||
TagName oldCatTagName = categoryManager.getTagName(oldCat);
|
||||
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)) {
|
||||
// delete all cat tags for cat-0
|
||||
fileTags.stream()
|
||||
@ -173,7 +164,7 @@ public class CategorizeAction extends AddTagAction {
|
||||
.map(Tag::getName)
|
||||
.filter(tagName::equals)
|
||||
.collect(Collectors.toList()).isEmpty()) {
|
||||
tagsManager.addContentTag(file, tagName, comment);
|
||||
tagsManager.addContentTag(file, tagName, "");
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
@ -186,7 +177,7 @@ public class CategorizeAction extends AddTagAction {
|
||||
}
|
||||
|
||||
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
|
||||
private final class CategorizationChange implements UndoRedoManager.UndoableCommand {
|
||||
|
||||
private final TagName newCategory;
|
||||
private final ImmutableMap<Long, TagName> oldCategories;
|
||||
private final Category newCategory;
|
||||
private final ImmutableMap<Long, Category> oldCategories;
|
||||
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.newCategory = newCategory;
|
||||
this.oldCategories = ImmutableMap.copyOf(oldCategories);
|
||||
@ -213,8 +204,8 @@ public class CategorizeAction extends AddTagAction {
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
CategorizeAction categorizeAction = new CategorizeAction(controller);
|
||||
categorizeAction.addTagsToFiles(newCategory, "", this.oldCategories.keySet(), false);
|
||||
new CategorizeAction(controller, newCategory, this.oldCategories.keySet(), false)
|
||||
.handle(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,9 +214,10 @@ public class CategorizeAction extends AddTagAction {
|
||||
*/
|
||||
@Override
|
||||
public void undo() {
|
||||
CategorizeAction categorizeAction = new CategorizeAction(controller);
|
||||
for (Map.Entry<Long, TagName> entry : oldCategories.entrySet()) {
|
||||
categorizeAction.addTagsToFiles(entry.getValue(), "", Collections.singleton(entry.getKey()), false);
|
||||
|
||||
for (Map.Entry<Long, Category> entry : oldCategories.entrySet()) {
|
||||
new CategorizeAction(controller, entry.getValue(), Collections.singleton(entry.getKey()), false)
|
||||
.handle(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,21 +19,109 @@
|
||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.util.Set;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import java.util.HashMap;
|
||||
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.ImageGalleryPreferences;
|
||||
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) {
|
||||
super(cat.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> {
|
||||
Set<Long> fileIdSet = ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs());
|
||||
new CategorizeAction(controller).addTagsToFiles(controller.getTagsManager().getTagName(cat), "", fileIdSet);
|
||||
private final static Logger LOGGER = Logger.getLogger(CategorizeGroupAction.class.getName());
|
||||
|
||||
public CategorizeGroupAction(Category newCat, ImageGalleryController controller) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -18,17 +18,18 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CategorizeSelectedFilesAction extends Action {
|
||||
public class CategorizeSelectedFilesAction extends CategorizeAction {
|
||||
|
||||
public CategorizeSelectedFilesAction(Category cat, ImageGalleryController controller) {
|
||||
super(cat.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), ""));
|
||||
setGraphic(cat.getGraphic());
|
||||
super(controller, cat, null);
|
||||
setEventHandler(actionEvent ->
|
||||
addCatToFiles(controller.getSelectionModel().getSelected())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ public class DeleteFollowUpTagAction extends Action {
|
||||
private static final Logger LOGGER = Logger.getLogger(DeleteFollowUpTagAction.class.getName());
|
||||
|
||||
@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());
|
||||
setEventHandler((ActionEvent t) -> {
|
||||
new SwingWorker<Void, Void>() {
|
||||
@ -52,7 +52,7 @@ public class DeleteFollowUpTagAction extends Action {
|
||||
try {
|
||||
final TagName followUpTagName = tagsManager.getFollowUpTagName();
|
||||
|
||||
List<ContentTag> contentTagsByContent = tagsManager.getContentTagsByContent(file);
|
||||
List<ContentTag> contentTagsByContent = tagsManager.getContentTags(file);
|
||||
for (ContentTag ct : contentTagsByContent) {
|
||||
if (ct.getName().getDisplayName().equals(followUpTagName.getDisplayName())) {
|
||||
tagsManager.deleteContentTag(ct);
|
||||
|
@ -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 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());
|
||||
|
||||
/**
|
||||
|
@ -19,21 +19,19 @@
|
||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||
|
||||
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.datamodel.TagName;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class TagGroupAction extends Action {
|
||||
public class TagGroupAction extends AddTagAction {
|
||||
|
||||
public TagGroupAction(final TagName tagName, ImageGalleryController controller) {
|
||||
super(tagName.getDisplayName(), (javafx.event.ActionEvent actionEvent) -> {
|
||||
Set<Long> fileIdSet = ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs());
|
||||
new AddDrawableTagAction(controller).addTagsToFiles(tagName, "", fileIdSet);
|
||||
});
|
||||
setGraphic(controller.getTagsManager().getGraphic(tagName));
|
||||
super(controller, tagName, null);
|
||||
setEventHandler(actionEvent ->
|
||||
new AddTagAction(controller, tagName, ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs())).
|
||||
handle(actionEvent)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -18,17 +18,19 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class TagSelectedFilesAction extends Action {
|
||||
public class TagSelectedFilesAction extends AddTagAction {
|
||||
|
||||
public TagSelectedFilesAction(final TagName tagName, ImageGalleryController controller) {
|
||||
super(tagName.getDisplayName(), actionEvent -> new AddDrawableTagAction(controller).addTag(tagName, ""));
|
||||
setGraphic(controller.getTagsManager().getGraphic(tagName));
|
||||
super(controller, tagName, null);
|
||||
setEventHandler(actionEvent ->
|
||||
new AddTagAction(controller, tagName, controller.getSelectionModel().getSelected()).
|
||||
handle(actionEvent)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,9 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javafx.geometry.Insets;
|
||||
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.BackgroundFill;
|
||||
import javafx.scene.layout.Border;
|
||||
@ -94,6 +97,7 @@ public enum Category {
|
||||
private final String displayName;
|
||||
|
||||
private final int id;
|
||||
private Image snapshot;
|
||||
|
||||
private Category(Color color, int id, String name) {
|
||||
this.color = color;
|
||||
@ -118,11 +122,15 @@ public enum Category {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public Node getGraphic() {
|
||||
Region region = new Region();
|
||||
region.setBackground(new Background(new BackgroundFill(getColor(), CORNER_RADII_4, Insets.EMPTY)));
|
||||
region.setPrefSize(16, 16);
|
||||
region.setBorder(new Border(new BorderStroke(getColor().darker(), BorderStrokeStyle.SOLID, CORNER_RADII_4, BORDER_WIDTHS_2)));
|
||||
return region;
|
||||
synchronized public Node getGraphic() {
|
||||
if (snapshot == null) {
|
||||
Region region = new Region();
|
||||
region.setBackground(new Background(new BackgroundFill(getColor(), CORNER_RADII_4, Insets.EMPTY)));
|
||||
region.setPrefSize(16, 16);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -248,7 +248,7 @@ public class CategoryManager {
|
||||
final DrawableTagsManager tagsManager = controller.getTagsManager();
|
||||
try {
|
||||
//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()
|
||||
&& CategoryManager.isCategoryTagName(ct.getName())) {
|
||||
try {
|
||||
|
@ -111,13 +111,13 @@ public class DrawableAttribute<T extends Comparable<T>> {
|
||||
new DrawableAttribute<>(AttributeName.CREATED_TIME, Bundle.DrawableAttribute_createdTime(),
|
||||
true,
|
||||
"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 =
|
||||
new DrawableAttribute<>(AttributeName.MODIFIED_TIME, Bundle.DrawableAttribute_modifiedTime(),
|
||||
true,
|
||||
"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 =
|
||||
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,
|
||||
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.displayName = new ReadOnlyStringWrapper(displayName);
|
||||
this.isDBColumn = isDBColumn;
|
||||
@ -223,7 +223,7 @@ public class DrawableAttribute<T extends Comparable<T>> {
|
||||
return displayName.get();
|
||||
}
|
||||
|
||||
public Collection<T> getValue(DrawableFile<?> f) {
|
||||
public Collection<T> getValue(DrawableFile f) {
|
||||
return extractor.apply(f);
|
||||
}
|
||||
|
||||
|
@ -497,9 +497,9 @@ public final class DrawableDB {
|
||||
ArrayList<BlackboardArtifact> artifacts = tskCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, fileID);
|
||||
|
||||
for (BlackboardArtifact a : artifacts) {
|
||||
List<BlackboardAttribute> attributes = a.getAttributes(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME);
|
||||
for (BlackboardAttribute attr : attributes) {
|
||||
hashNames.add(attr.getValueString());
|
||||
BlackboardAttribute attribute = a.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
|
||||
if (attribute != null) {
|
||||
hashNames.add(attribute.getValueString());
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableSet(hashNames);
|
||||
@ -569,23 +569,23 @@ public final class DrawableDB {
|
||||
return removeFile;
|
||||
}
|
||||
|
||||
public void updateFile(DrawableFile<?> f) {
|
||||
public void updateFile(DrawableFile f) {
|
||||
DrawableTransaction trans = beginTransaction();
|
||||
updateFile(f, trans);
|
||||
commitTransaction(trans, true);
|
||||
}
|
||||
|
||||
public void insertFile(DrawableFile<?> f) {
|
||||
public void insertFile(DrawableFile f) {
|
||||
DrawableTransaction trans = beginTransaction();
|
||||
insertFile(f, trans);
|
||||
commitTransaction(trans, true);
|
||||
}
|
||||
|
||||
public void insertFile(DrawableFile<?> f, DrawableTransaction tr) {
|
||||
public void insertFile(DrawableFile f, DrawableTransaction tr) {
|
||||
insertOrUpdateFile(f, tr, insertFileStmt);
|
||||
}
|
||||
|
||||
public void updateFile(DrawableFile<?> f, DrawableTransaction tr) {
|
||||
public void updateFile(DrawableFile f, DrawableTransaction tr) {
|
||||
insertOrUpdateFile(f, tr, updateFileStmt);
|
||||
}
|
||||
|
||||
@ -602,7 +602,7 @@ public final class DrawableDB {
|
||||
* @param tr a transaction to use, must not be null
|
||||
* @param stmt the statement that does the actull inserting
|
||||
*/
|
||||
private void insertOrUpdateFile(DrawableFile<?> f, @Nonnull DrawableTransaction tr, @Nonnull PreparedStatement stmt) {
|
||||
private void insertOrUpdateFile(DrawableFile f, @Nonnull DrawableTransaction tr, @Nonnull PreparedStatement stmt) {
|
||||
|
||||
if (tr.isClosed()) {
|
||||
throw new IllegalArgumentException("can't update database with closed transaction");
|
||||
@ -686,7 +686,7 @@ public final class DrawableDB {
|
||||
tr.commit(notify);
|
||||
}
|
||||
|
||||
public Boolean isFileAnalyzed(DrawableFile<?> f) {
|
||||
public Boolean isFileAnalyzed(DrawableFile f) {
|
||||
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
|
||||
|
||||
String orderByClause = "";
|
||||
switch (sortBy) {
|
||||
case GROUP_BY_VALUE:
|
||||
orderByClause = " ORDER BY " + groupBy.attrName.toString(); //NON-NLS
|
||||
break;
|
||||
case FILE_COUNT:
|
||||
orderByClause = " ORDER BY COUNT(*)"; //NON-NLS
|
||||
break;
|
||||
case NONE:
|
||||
// case PRIORITY:
|
||||
break;
|
||||
|
||||
if (sortBy == GROUP_BY_VALUE) {
|
||||
orderByClause = " ORDER BY " + groupBy.attrName.toString();
|
||||
} else if (sortBy == GroupSortBy.FILE_COUNT) {
|
||||
orderByClause = " ORDER BY COUNT(*)";
|
||||
}
|
||||
|
||||
query.append(orderByClause);
|
||||
@ -984,7 +979,7 @@ public final class DrawableDB {
|
||||
* @throws TskCoreException if unable to get a file from the currently open
|
||||
* {@link SleuthkitCase}
|
||||
*/
|
||||
private DrawableFile<?> getFileFromID(Long id, boolean analyzed) throws TskCoreException {
|
||||
private DrawableFile getFileFromID(Long id, boolean analyzed) throws TskCoreException {
|
||||
try {
|
||||
AbstractFile f = tskCase.getAbstractFileById(id);
|
||||
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
|
||||
* {@link SleuthkitCase}
|
||||
*/
|
||||
public DrawableFile<?> getFileFromID(Long id) throws TskCoreException {
|
||||
public DrawableFile getFileFromID(Long id) throws TskCoreException {
|
||||
try {
|
||||
AbstractFile f = tskCase.getAbstractFileById(id);
|
||||
return DrawableFile.create(f,
|
||||
@ -1247,8 +1242,8 @@ public final class DrawableDB {
|
||||
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.
|
||||
String name =
|
||||
"SELECT COUNT(obj_id) FROM tsk_files where obj_id IN " + fileIdsList //NON-NLS
|
||||
String name
|
||||
= "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
|
||||
try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(name);
|
||||
ResultSet resultSet = executeQuery.getResultSet();) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@ -42,39 +43,36 @@ import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
|
||||
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentVisitor;
|
||||
import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
import org.sleuthkit.datamodel.SleuthkitItemVisitor;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.Tag;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* @TODO: There is something I don't understand or have done wrong about
|
||||
* 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
|
||||
* A file that contains visual information such as an image or video.
|
||||
*/
|
||||
public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile {
|
||||
public abstract class DrawableFile {
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
? new VideoFile<>(abstractFileById, analyzed)
|
||||
: new ImageFile<>(abstractFileById, analyzed);
|
||||
? new VideoFile(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);
|
||||
}
|
||||
|
||||
@ -82,7 +80,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
||||
|
||||
private String drawablePath;
|
||||
|
||||
private final T file;
|
||||
private final AbstractFile file;
|
||||
|
||||
private final SimpleBooleanProperty analyzed;
|
||||
|
||||
@ -92,89 +90,77 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
||||
|
||||
private String model;
|
||||
|
||||
protected DrawableFile(T file, Boolean analyzed) {
|
||||
/*
|
||||
* @TODO: the two 'new Integer(0).shortValue()' values and null are
|
||||
* placeholders because the super constructor expects values i can't get
|
||||
* easily at the moment. I assume this is related to why
|
||||
* ReadContentInputStream can't read from DrawableFiles.
|
||||
*/
|
||||
|
||||
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());
|
||||
protected DrawableFile(AbstractFile file, Boolean analyzed) {
|
||||
this.analyzed = new SimpleBooleanProperty(analyzed);
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
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() {
|
||||
return DrawableAttribute.getValues().stream()
|
||||
.map(this::makeAttributeValuePair)
|
||||
.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) {
|
||||
return new Pair<>(t, t.getValue(DrawableFile.this));
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
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;
|
||||
}
|
||||
|
||||
public String getMake() {
|
||||
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;
|
||||
}
|
||||
@ -182,18 +168,18 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
||||
public Set<TagName> getTagNames() {
|
||||
try {
|
||||
|
||||
return getSleuthkitCase().getContentTagsByContent(this).stream()
|
||||
return getContentTags().stream()
|
||||
.map(Tag::getName)
|
||||
.collect(Collectors.toSet());
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getAnonymousLogger().log(Level.WARNING, "problem looking up " + DrawableAttribute.TAGS.getDisplayName() + " for " + file.getName(), ex); //NON-NLS
|
||||
} 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();
|
||||
}
|
||||
|
||||
protected Object getValueOfBBAttribute(BlackboardArtifact.ARTIFACT_TYPE artType, BlackboardAttribute.ATTRIBUTE_TYPE attrType) {
|
||||
protected Object getValueOfBBAttribute(ARTIFACT_TYPE artType, ATTRIBUTE_TYPE attrType) {
|
||||
try {
|
||||
|
||||
//why doesn't file.getArtifacts() work?
|
||||
@ -223,7 +209,8 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
||||
}
|
||||
}
|
||||
} 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 "";
|
||||
}
|
||||
@ -247,7 +234,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
||||
*/
|
||||
private void updateCategory() {
|
||||
try {
|
||||
category.set(getSleuthkitCase().getContentTagsByContent(this).stream()
|
||||
category.set(getContentTags().stream()
|
||||
.map(Tag::getName).filter(CategoryManager::isCategoryTagName)
|
||||
.map(TagName::getDisplayName)
|
||||
.map(Category::fromDisplayName)
|
||||
@ -255,12 +242,16 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
||||
.orElse(Category.ZERO)
|
||||
);
|
||||
} 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) {
|
||||
// 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
|
||||
public Image getThumbnail() {
|
||||
try {
|
||||
@ -317,7 +308,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
||||
return analyzed.get();
|
||||
}
|
||||
|
||||
public T getAbstractFile() {
|
||||
public AbstractFile getAbstractFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
@ -333,12 +324,16 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
||||
drawablePath = StringUtils.removeEnd(getUniquePath(), getName());
|
||||
return drawablePath;
|
||||
} 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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getHashSetNames() throws TskCoreException {
|
||||
return file.getHashSetNames();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Set<String> getHashSetNamesUnchecked() {
|
||||
try {
|
||||
@ -359,7 +354,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
||||
*/
|
||||
public String getContentPathSafe() {
|
||||
try {
|
||||
return this.getUniquePath();
|
||||
return getUniquePath();
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
String contentName = this.getName();
|
||||
LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NOI18N NON-NLS
|
||||
|
@ -45,13 +45,24 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* Manages Tags, Tagging, and the relationship between Categories and Tags in
|
||||
* the autopsy Db. Delegates some work to the backing {@link TagsManager}.
|
||||
*/
|
||||
@NbBundle.Messages({"DrawableTagsManager.followUp=Follow Up"})
|
||||
@NbBundle.Messages({"DrawableTagsManager.followUp=Follow Up",
|
||||
"DrawableTagsManager.bookMark=Bookmark"})
|
||||
public class DrawableTagsManager {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DrawableTagsManager.class.getName());
|
||||
|
||||
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 BOOKMARK_IMAGE;
|
||||
|
||||
public static String getFollowUpText() {
|
||||
return FOLLOW_UP;
|
||||
}
|
||||
|
||||
public static String getBookmarkText() {
|
||||
return BOOKMARK;
|
||||
}
|
||||
|
||||
final private Object autopsyTagsManagerLock = new Object();
|
||||
private TagsManager autopsyTagsManager;
|
||||
@ -70,6 +81,7 @@ public class DrawableTagsManager {
|
||||
* The tag name corresponding to the "built-in" tag "Follow Up"
|
||||
*/
|
||||
private TagName followUpTagName;
|
||||
private TagName bookmarkTagName;
|
||||
|
||||
public DrawableTagsManager(TagsManager 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
|
||||
*
|
||||
@ -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
|
||||
* 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) {
|
||||
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 {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
try {
|
||||
@ -192,7 +227,7 @@ public class DrawableTagsManager {
|
||||
} catch (TagsManager.TagNameAlreadyExistsException 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
|
||||
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) {
|
||||
return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment);
|
||||
}
|
||||
@ -241,9 +276,11 @@ public class DrawableTagsManager {
|
||||
try {
|
||||
if (tagname.equals(getFollowUpTagName())) {
|
||||
return new ImageView(getFollowUpImage());
|
||||
} else if (tagname.equals(getBookmarkTagName())) {
|
||||
return new ImageView(getBookmarkImage());
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
@ -254,4 +291,12 @@ public class DrawableTagsManager {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||
import java.io.IOException;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.scene.image.Image;
|
||||
import javax.imageio.ImageIO;
|
||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
@ -31,17 +30,12 @@ import org.sleuthkit.datamodel.AbstractFile;
|
||||
* wrapper(/decorator?/adapter?) around {@link AbstractFile} and provides
|
||||
* 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());
|
||||
|
||||
static {
|
||||
ImageIO.scanForPlugins();
|
||||
}
|
||||
|
||||
ImageFile(T f, Boolean analyzed) {
|
||||
ImageFile(AbstractFile f, Boolean analyzed) {
|
||||
super(f, analyzed);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -36,13 +36,13 @@ import org.sleuthkit.autopsy.coreutils.VideoUtils;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
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 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);
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@ -126,7 +127,7 @@ public class GroupManager {
|
||||
private volatile DrawableAttribute<?> groupBy = DrawableAttribute.PATH;
|
||||
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<SortOrder> sortOrderProp = new ReadOnlyObjectWrapper<>(sortOrder);
|
||||
|
||||
@ -169,7 +170,7 @@ public class GroupManager {
|
||||
* file is a part of
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
synchronized public Set<GroupKey<?>> getGroupKeysForFile(DrawableFile<?> file) {
|
||||
synchronized public Set<GroupKey<?>> getGroupKeysForFile(DrawableFile file) {
|
||||
Set<GroupKey<?>> resultSet = new HashSet<>();
|
||||
for (Comparable<?> val : groupBy.getValue(file)) {
|
||||
if (groupBy == DrawableAttribute.TAGS) {
|
||||
@ -193,7 +194,7 @@ public class GroupManager {
|
||||
synchronized public Set<GroupKey<?>> getGroupKeysForFileID(Long fileID) {
|
||||
try {
|
||||
if (nonNull(db)) {
|
||||
DrawableFile<?> file = db.getFileFromID(fileID);
|
||||
DrawableFile file = db.getFileFromID(fileID);
|
||||
return getGroupKeysForFile(file);
|
||||
} else {
|
||||
Logger.getLogger(GroupManager.class.getName()).log(Level.WARNING, "Failed to load file with id: {0} from database. There is no database assigned.", fileID); //NON-NLS
|
||||
@ -274,7 +275,7 @@ public class GroupManager {
|
||||
} else if (unSeenGroups.contains(group) == false) {
|
||||
unSeenGroups.add(group);
|
||||
}
|
||||
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder));
|
||||
FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -299,11 +300,11 @@ public class GroupManager {
|
||||
Platform.runLater(() -> {
|
||||
if (analyzedGroups.contains(group)) {
|
||||
analyzedGroups.remove(group);
|
||||
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder));
|
||||
FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy));
|
||||
}
|
||||
if (unSeenGroups.contains(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;
|
||||
}
|
||||
|
||||
@ -459,7 +460,7 @@ public class GroupManager {
|
||||
Platform.runLater(() -> sortByProp.set(sortBy));
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<GroupSortBy> getSortByProperty() {
|
||||
public ReadOnlyObjectProperty< Comparator<DrawableGroup>> getSortByProperty() {
|
||||
return sortByProp.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@ -523,8 +524,8 @@ public class GroupManager {
|
||||
setSortBy(sortBy);
|
||||
setSortOrder(sortOrder);
|
||||
Platform.runLater(() -> {
|
||||
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder));
|
||||
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder));
|
||||
FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy));
|
||||
FXCollections.sort(unSeenGroups, applySortOrder(sortOrder, sortBy));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -666,7 +667,7 @@ public class GroupManager {
|
||||
group = new DrawableGroup(groupKey, fileIDs, groupSeen);
|
||||
controller.getCategoryManager().registerListener(group);
|
||||
group.seenProperty().addListener((o, oldSeen, newSeen) -> {
|
||||
markGroupSeen(group, newSeen);
|
||||
Platform.runLater(() -> markGroupSeen(group, newSeen));
|
||||
});
|
||||
groupMap.put(groupKey, group);
|
||||
}
|
||||
@ -675,7 +676,7 @@ public class GroupManager {
|
||||
if (analyzedGroups.contains(group) == false) {
|
||||
analyzedGroups.add(group);
|
||||
if (Objects.isNull(task)) {
|
||||
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder));
|
||||
FXCollections.sort(analyzedGroups, applySortOrder(sortOrder, sortBy));
|
||||
}
|
||||
}
|
||||
markGroupSeen(group, groupSeen);
|
||||
@ -719,12 +720,12 @@ public class GroupManager {
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@NbBundle.Messages({"# {0} - groupBy attribute Name",
|
||||
"# {1} - sortBy name",
|
||||
"# {2} - sort Order",
|
||||
"ReGroupTask.displayTitle=regrouping files by {0} sorted by {1} in {2} order",
|
||||
"# {0} - groupBy attribute Name",
|
||||
"# {1} - atribute value",
|
||||
"ReGroupTask.progressUpdate=regrouping files by {0} : {1}"})
|
||||
"# {1} - sortBy name",
|
||||
"# {2} - sort Order",
|
||||
"ReGroupTask.displayTitle=regrouping files by {0} sorted by {1} in {2} order",
|
||||
"# {0} - groupBy attribute Name",
|
||||
"# {1} - atribute value",
|
||||
"ReGroupTask.progressUpdate=regrouping files by {0} : {1}"})
|
||||
private class ReGroupTask<A extends Comparable<A>> extends LoggedTask<Void> {
|
||||
|
||||
private ProgressHandle groupProgress;
|
||||
@ -735,8 +736,8 @@ public class GroupManager {
|
||||
|
||||
private final SortOrder sortOrder;
|
||||
|
||||
public ReGroupTask(DrawableAttribute<A> groupBy, GroupSortBy sortBy, SortOrder sortOrder) {
|
||||
super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.name(), sortOrder.toString()), true);
|
||||
ReGroupTask(DrawableAttribute<A> groupBy, GroupSortBy sortBy, SortOrder sortOrder) {
|
||||
super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), true);
|
||||
|
||||
this.groupBy = groupBy;
|
||||
this.sortBy = sortBy;
|
||||
@ -755,7 +756,7 @@ public class GroupManager {
|
||||
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(() -> {
|
||||
analyzedGroups.clear();
|
||||
unSeenGroups.clear();
|
||||
@ -778,7 +779,7 @@ public class GroupManager {
|
||||
groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p);
|
||||
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);
|
||||
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<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,89 +18,50 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
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.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
|
||||
* down in Toolbar as well as each enum value having the stategy
|
||||
* ({@link Comparator}) for sorting the groups
|
||||
* Pseudo enum of possible properties to sort groups by.
|
||||
*/
|
||||
@NbBundle.Messages({"GroupSortBy.groupSize=Group Size",
|
||||
"GroupSortBy.groupName=Group Name",
|
||||
"GroupSortBy.none=None",
|
||||
"GroupSortBy.priority=Priority"})
|
||||
public enum GroupSortBy implements ComparatorProvider {
|
||||
"GroupSortBy.groupName=Group Name",
|
||||
"GroupSortBy.none=None",
|
||||
"GroupSortBy.priority=Priority"})
|
||||
public class GroupSortBy implements Comparator<DrawableGroup> {
|
||||
|
||||
/**
|
||||
* sort the groups by the number of files in each sort the groups by the
|
||||
* number of files in each
|
||||
* sort the groups by the number of files in each
|
||||
*/
|
||||
FILE_COUNT(Bundle.GroupSortBy_groupSize(), true, "folder-open-image.png") { //NON-NLS
|
||||
@Override
|
||||
public Comparator<DrawableGroup> getGrpComparator(final SortOrder sortOrder) {
|
||||
return applySortOrder(sortOrder, Comparator.comparingInt(DrawableGroup::getSize));
|
||||
}
|
||||
public final static GroupSortBy FILE_COUNT = new GroupSortBy(Bundle.GroupSortBy_groupSize(), "folder-open-image.png", Comparator.comparing(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
|
||||
* them by path alphabetically )
|
||||
*/
|
||||
GROUP_BY_VALUE(Bundle.GroupSortBy_groupName(), true, "folder-rename.png") { //NON-NLS
|
||||
@Override
|
||||
public Comparator<DrawableGroup> getGrpComparator(final SortOrder sortOrder) {
|
||||
return applySortOrder(sortOrder, Comparator.comparing(t -> t.getGroupByValueDislpayName()));
|
||||
}
|
||||
public final static GroupSortBy GROUP_BY_VALUE = new GroupSortBy(Bundle.GroupSortBy_groupName(), "folder-rename.png", Comparator.comparing(DrawableGroup::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
|
||||
* order)
|
||||
*/
|
||||
NONE(Bundle.GroupSortBy_none(), false, "prohibition.png") { //NON-NLS
|
||||
@Override
|
||||
public Comparator<DrawableGroup> getGrpComparator(SortOrder sortOrder) {
|
||||
return new NoOpComparator<>();
|
||||
}
|
||||
public final static GroupSortBy NONE = new GroupSortBy(Bundle.GroupSortBy_none(), "prohibition.png", new AllEqualComparator<>());
|
||||
|
||||
@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
|
||||
*/
|
||||
PRIORITY(Bundle.GroupSortBy_priority(), false, "hashset_hits.png") { //NON-NLS
|
||||
@Override
|
||||
public Comparator<DrawableGroup> getGrpComparator(SortOrder sortOrder) {
|
||||
return Comparator.nullsLast(Comparator.comparingDouble(DrawableGroup::getHashHitDensity).thenComparingInt(DrawableGroup::getSize).reversed());
|
||||
}
|
||||
public final static GroupSortBy PRIORITY = new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png", Comparator.comparing(DrawableGroup::getHashHitDensity).thenComparing(Comparator.comparing(DrawableGroup::getUncategorizedCount)));
|
||||
|
||||
@Override
|
||||
public <A extends Comparable<A>> Comparator<A> getValueComparator(DrawableAttribute<A> attr, SortOrder sortOrder) {
|
||||
return getDefaultValueComparator(attr, sortOrder);
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public int compare(DrawableGroup o1, DrawableGroup o2) {
|
||||
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
|
||||
@ -108,8 +69,7 @@ public enum GroupSortBy implements ComparatorProvider {
|
||||
* @return
|
||||
*/
|
||||
public static ObservableList<GroupSortBy> getValues() {
|
||||
return FXCollections.observableArrayList(Arrays.asList(values()));
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
final private String displayName;
|
||||
@ -118,12 +78,12 @@ public enum GroupSortBy implements ComparatorProvider {
|
||||
|
||||
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.sortOrderEnabled = sortOrderEnabled;
|
||||
this.imageName = imagePath;
|
||||
this.delegate = internalComparator;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
@ -139,49 +99,11 @@ public enum GroupSortBy implements ComparatorProvider {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public Boolean isSortOrderEnabled() {
|
||||
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> {
|
||||
static class AllEqualComparator<A> implements Comparator<A> {
|
||||
|
||||
@Override
|
||||
public int compare(A o1, A o2) {
|
||||
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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.*?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.image.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ProgressBar?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?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>
|
||||
<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>
|
||||
<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>
|
||||
<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" HBox.hgrow="NEVER">
|
||||
<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">
|
||||
<StackPane.margin>
|
||||
<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>
|
||||
<HBox.margin>
|
||||
<Insets />
|
||||
@ -27,10 +34,13 @@
|
||||
<StackPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" HBox.hgrow="NEVER">
|
||||
<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" />
|
||||
<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>
|
||||
<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>
|
||||
<HBox.margin>
|
||||
<Insets right="5.0" />
|
||||
@ -42,19 +52,6 @@
|
||||
</BorderPane.margin>
|
||||
</HBox>
|
||||
</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">
|
||||
<graphic><ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-14 Basis Technology Corp.
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -19,8 +19,6 @@
|
||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
@ -38,21 +36,12 @@ public class StatusBar extends AnchorPane {
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
|
||||
@FXML
|
||||
private ResourceBundle resources;
|
||||
|
||||
@FXML
|
||||
private URL location;
|
||||
|
||||
@FXML
|
||||
private ProgressBar fileTaskProgresBar;
|
||||
|
||||
@FXML
|
||||
private Label fileUpdateTaskLabel;
|
||||
|
||||
@FXML
|
||||
private Label statusLabel;
|
||||
|
||||
@FXML
|
||||
private Label bgTaskLabel;
|
||||
|
||||
@ -64,31 +53,24 @@ public class StatusBar extends AnchorPane {
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages({"StatusBar.fileUpdateTaskLabel.text= File Update Tasks",
|
||||
"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."})
|
||||
"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."})
|
||||
void initialize() {
|
||||
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 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 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");
|
||||
fileTaskProgresBar.progressProperty().bind(controller.getFileUpdateQueueSizeProperty().negate());
|
||||
// controller.getFileUpdateQueueSizeProperty().addListener((ov, oldSize, newSize) -> {
|
||||
// Platform.runLater(() -> {
|
||||
//
|
||||
//
|
||||
// });
|
||||
// });
|
||||
fileUpdateTaskLabel.textProperty().bind(controller.getDBTasksQueueSizeProperty().asString().concat(Bundle.StatusBar_fileUpdateTaskLabel_text()));
|
||||
fileTaskProgresBar.progressProperty().bind(controller.getDBTasksQueueSizeProperty().negate());
|
||||
|
||||
controller.regroupProgress().addListener((ov, oldSize, newSize) -> {
|
||||
Platform.runLater(() -> {
|
||||
if(controller.regroupProgress().lessThan(1.0).get()){
|
||||
if (controller.regroupProgress().lessThan(1.0).get()) {
|
||||
// Regrouping in progress
|
||||
bgTaskProgressBar.progressProperty().setValue(-1.0);
|
||||
bgTaskLabel.setText(Bundle.StatusBar_bgTaskLabel_text());
|
||||
} else{
|
||||
} else {
|
||||
// Clear the progress bar
|
||||
bgTaskProgressBar.progressProperty().setValue(0.0);
|
||||
bgTaskLabel.setText("");
|
||||
@ -96,10 +78,7 @@ public class StatusBar extends AnchorPane {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Platform.runLater(() -> {
|
||||
staleLabel.setTooltip(new Tooltip(Bundle.StatuBar_toolTip()));
|
||||
});
|
||||
Platform.runLater(() -> staleLabel.setTooltip(new Tooltip(Bundle.StatuBar_toolTip())));
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -1,113 +1,86 @@
|
||||
<?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.MenuItem?>
|
||||
<?import javafx.scene.control.RadioButton?>
|
||||
<?import javafx.scene.control.Separator?>
|
||||
<?import javafx.scene.control.Slider?>
|
||||
<?import javafx.scene.control.SplitMenuButton?>
|
||||
<?import javafx.scene.control.ToggleGroup?>
|
||||
<?import javafx.scene.control.ToolBar?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?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">
|
||||
<items>
|
||||
<Label fx:id="groupByLabel" text="Group By:">
|
||||
<labelFor>
|
||||
<ComboBox fx:id="groupByBox" editable="false" />
|
||||
</labelFor>
|
||||
</Label>
|
||||
<fx:reference source="groupByBox" />
|
||||
<Region prefHeight="-1.0" prefWidth="10.0" />
|
||||
<Label fx:id="sortByLabel" text="Sort By:">
|
||||
<labelFor>
|
||||
<ComboBox fx:id="sortByBox" />
|
||||
</labelFor>
|
||||
</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">
|
||||
<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>
|
||||
</children>
|
||||
</HBox>
|
||||
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="20.0" />
|
||||
<HBox alignment="CENTER" spacing="5.0">
|
||||
<children>
|
||||
<Label fx:id="tagImageViewLabel" text="Tag Group's Files:">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/tag_red.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Label>
|
||||
<SplitMenuButton id="tagSplitMenu" fx:id="tagGroupMenuButton" disable="true" mnemonicParsing="false" text="Follow Up" textOverrun="ELLIPSIS">
|
||||
<items>
|
||||
<MenuItem mnemonicParsing="false" text="Action 1" />
|
||||
<MenuItem mnemonicParsing="false" text="Action 2" />
|
||||
</items>
|
||||
</SplitMenuButton>
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER" spacing="5.0">
|
||||
<children>
|
||||
<Label fx:id="categoryImageViewLabel" text="Categorize Group's Files:">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/category-icon.png" />
|
||||
</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>
|
||||
<items>
|
||||
<HBox alignment="CENTER" spacing="5.0">
|
||||
<children>
|
||||
<Label fx:id="groupByLabel" text="Group By:">
|
||||
<labelFor>
|
||||
<ComboBox fx:id="groupByBox" editable="false" />
|
||||
</labelFor>
|
||||
</Label>
|
||||
<fx:reference source="groupByBox" />
|
||||
|
||||
</children>
|
||||
</HBox>
|
||||
|
||||
|
||||
|
||||
|
||||
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="10.0" />
|
||||
<HBox alignment="CENTER" spacing="5.0">
|
||||
<children>
|
||||
<Label fx:id="tagImageViewLabel" text="Tag Group's Files:">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/tag_red.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Label>
|
||||
<SplitMenuButton id="tagSplitMenu" fx:id="tagGroupMenuButton" disable="true" mnemonicParsing="false" text="Follow Up" textOverrun="ELLIPSIS">
|
||||
<items>
|
||||
<MenuItem mnemonicParsing="false" text="Action 1" />
|
||||
<MenuItem mnemonicParsing="false" text="Action 2" />
|
||||
</items>
|
||||
</SplitMenuButton>
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER" spacing="5.0">
|
||||
<children>
|
||||
<Label fx:id="categoryImageViewLabel" text="Categorize Group's Files:">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/category-icon.png" />
|
||||
</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>
|
||||
<padding>
|
||||
<Insets left="5.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="10.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>
|
||||
|
@ -26,20 +26,14 @@ import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.SplitMenuButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.control.ToolBar;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javax.swing.SortOrder;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
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.datamodel.Category;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
@ -67,21 +62,6 @@ public class Toolbar extends ToolBar {
|
||||
@FXML
|
||||
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
|
||||
private SplitMenuButton catGroupMenuButton;
|
||||
|
||||
@ -91,9 +71,6 @@ public class Toolbar extends ToolBar {
|
||||
@FXML
|
||||
private Label groupByLabel;
|
||||
|
||||
@FXML
|
||||
private Label sortByLabel;
|
||||
|
||||
@FXML
|
||||
private Label tagImageViewLabel;
|
||||
|
||||
@ -103,54 +80,35 @@ public class Toolbar extends ToolBar {
|
||||
@FXML
|
||||
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 SortChooser<DrawableGroup, GroupSortBy> sortChooser;
|
||||
|
||||
synchronized public SortOrder getSortOrder() {
|
||||
return orderProperty.get();
|
||||
}
|
||||
|
||||
public DoubleProperty sizeSliderValue() {
|
||||
return sizeSlider.valueProperty();
|
||||
}
|
||||
|
||||
static synchronized public Toolbar getDefault(ImageGalleryController controller) {
|
||||
if (instance == null) {
|
||||
instance = new Toolbar(controller);
|
||||
private final InvalidationListener queryInvalidationListener = new InvalidationListener() {
|
||||
public void invalidated(Observable o) {
|
||||
controller.getGroupManager().regroup(
|
||||
groupByBox.getSelectionModel().getSelectedItem(),
|
||||
sortChooser.getComparator(),
|
||||
sortChooser.getSortOrder(),
|
||||
false);
|
||||
}
|
||||
return instance;
|
||||
};
|
||||
|
||||
public DoubleProperty thumbnailSizeProperty() {
|
||||
return sizeSlider.valueProperty();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages({"Toolbar.groupByLabel=Group By:",
|
||||
"Toolbar.sortByLabel=Sort By:",
|
||||
"Toolbar.ascRadio=Ascending",
|
||||
"Toolbar.descRadio=Descending",
|
||||
"Toolbar.tagImageViewLabel=Tag Group's Files:",
|
||||
"Toolbar.categoryImageViewLabel=Categorize Group's Files:",
|
||||
"Toolbar.thumbnailSizeLabel=Thumbnail Size (px):"})
|
||||
"Toolbar.sortByLabel=Sort By:",
|
||||
"Toolbar.ascRadio=Ascending",
|
||||
"Toolbar.descRadio=Descending",
|
||||
"Toolbar.tagImageViewLabel=Tag Group's Files:",
|
||||
"Toolbar.categoryImageViewLabel=Categorize Group's Files:",
|
||||
"Toolbar.thumbnailSizeLabel=Thumbnail Size (px):"})
|
||||
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 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 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 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'.";
|
||||
|
||||
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);
|
||||
catGroupMenuButton.setOnAction(cat5GroupAction);
|
||||
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.getSelectionModel().select(DrawableAttribute.PATH);
|
||||
groupByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener);
|
||||
@ -201,21 +156,20 @@ public class Toolbar extends ToolBar {
|
||||
groupByBox.setCellFactory(listView -> new AttributeListCell());
|
||||
groupByBox.setButtonCell(new AttributeListCell());
|
||||
|
||||
sortByBox.setCellFactory(listView -> new SortByListCell());
|
||||
sortByBox.setButtonCell(new SortByListCell());
|
||||
sortByBox.setItems(GroupSortBy.getValues());
|
||||
|
||||
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);
|
||||
sortChooser = new SortChooser<>(GroupSortBy.getValues());
|
||||
sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> {
|
||||
final boolean orderEnabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY;
|
||||
sortChooser.setSortOrderDisabled(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) {
|
||||
@ -230,13 +184,11 @@ public class Toolbar extends ToolBar {
|
||||
public void reset() {
|
||||
Platform.runLater(() -> {
|
||||
groupByBox.getSelectionModel().select(DrawableAttribute.PATH);
|
||||
sortByBox.getSelectionModel().select(GroupSortBy.NONE);
|
||||
orderGroup.selectToggle(ascRadio);
|
||||
sizeSlider.setValue(SIZE_SLIDER_DEFAULT);
|
||||
});
|
||||
}
|
||||
|
||||
private Toolbar(ImageGalleryController controller) {
|
||||
public Toolbar(ImageGalleryController controller) {
|
||||
this.controller = controller;
|
||||
FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ public class VideoPlayer extends BorderPane {
|
||||
mp.seek(Duration.millis(timeSlider.getValue()));
|
||||
}
|
||||
};
|
||||
private final VideoFile<?> file;
|
||||
private final VideoFile file;
|
||||
|
||||
@FXML
|
||||
@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.mp = mp;
|
||||
FXMLConstructor.construct(this, "MediaControl.fxml"); //NON-NLS
|
||||
|
@ -31,13 +31,10 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
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,
|
||||
@ -66,8 +63,8 @@ public class DrawableTile extends DrawableTileBase {
|
||||
setCache(true);
|
||||
setCacheHint(CacheHint.SPEED);
|
||||
nameLabel.prefWidthProperty().bind(imageView.fitWidthProperty());
|
||||
imageView.fitHeightProperty().bind(Toolbar.getDefault(getController()).sizeSliderValue());
|
||||
imageView.fitWidthProperty().bind(Toolbar.getDefault(getController()).sizeSliderValue());
|
||||
imageView.fitHeightProperty().bind(getController().thumbnailSizeProperty());
|
||||
imageView.fitWidthProperty().bind(getController().thumbnailSizeProperty());
|
||||
|
||||
selectionModel.lastSelectedProperty().addListener(new WeakChangeListener<>(lastSelectionListener));
|
||||
|
||||
@ -108,12 +105,12 @@ public class DrawableTile extends DrawableTileBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
Task<Image> newReadImageTask(DrawableFile<?> file) {
|
||||
Task<Image> newReadImageTask(DrawableFile file) {
|
||||
return file.getThumbnailTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTextForLabel() {
|
||||
return getFile().map(AbstractContent::getName).orElse("");
|
||||
return getFile().map(DrawableFile::getName).orElse("");
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
|
||||
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
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.DeleteFollowUpTagAction;
|
||||
import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction;
|
||||
@ -142,7 +142,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
||||
* @param controller the value of controller
|
||||
*/
|
||||
@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) {
|
||||
super(controller);
|
||||
this.groupPane = groupPane;
|
||||
@ -179,13 +179,11 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
||||
t.consume();
|
||||
}
|
||||
|
||||
private ContextMenu buildContextMenu(DrawableFile<?> file) {
|
||||
private ContextMenu buildContextMenu(DrawableFile file) {
|
||||
final ArrayList<MenuItem> menuItems = new ArrayList<>();
|
||||
|
||||
menuItems.add(new CategorizeAction(getController()).getPopupMenu());
|
||||
|
||||
menuItems.add(new AddDrawableTagAction(getController()).getPopupMenu());
|
||||
|
||||
menuItems.add(CategorizeAction.getCategoriesMenu(getController()));
|
||||
menuItems.add(AddTagAction.getTagMenu(getController()));
|
||||
|
||||
final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles());
|
||||
extractMenuItem.setOnAction(actionEvent -> {
|
||||
@ -196,7 +194,6 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
||||
});
|
||||
menuItems.add(extractMenuItem);
|
||||
|
||||
|
||||
MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer());
|
||||
contentViewer.setOnAction(actionEvent -> {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
@ -242,7 +239,7 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
||||
if (followUpToggle.isSelected() == true) {
|
||||
try {
|
||||
selectionModel.clearAndSelect(file.getId());
|
||||
new AddDrawableTagAction(getController()).addTag(getController().getTagsManager().getFollowUpTagName(), "");
|
||||
new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent);
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex); //NON-NLS
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
|
||||
private Optional<DrawableFile<?>> fileOpt = Optional.empty();
|
||||
private Optional<DrawableFile> fileOpt = Optional.empty();
|
||||
|
||||
private Optional<Long> fileIDOpt = Optional.empty();
|
||||
private volatile Task<Image> imageTask;
|
||||
@ -89,12 +89,12 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
this.fileIDOpt = fileIDOpt;
|
||||
}
|
||||
|
||||
synchronized void setFileOpt(Optional<DrawableFile<?>> fileOpt) {
|
||||
synchronized void setFileOpt(Optional<DrawableFile> fileOpt) {
|
||||
this.fileOpt = fileOpt;
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public Optional<DrawableFile<?>> getFile() {
|
||||
synchronized public Optional<DrawableFile> getFile() {
|
||||
if (fileIDOpt.isPresent()) {
|
||||
if (fileOpt.isPresent() && fileOpt.get().getId() == fileIDOpt.get()) {
|
||||
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);
|
||||
imageTask = myTask;
|
||||
Node progressNode = newProgressIndicator(myTask);
|
||||
@ -191,7 +191,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
}
|
||||
|
||||
@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()
|
||||
try {
|
||||
Image fxImage = imageTask.get();
|
||||
@ -210,7 +210,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
void showErrorNode(String errorMessage, DrawableFile<?> file) {
|
||||
void showErrorNode(String errorMessage, DrawableFile file) {
|
||||
Button createButton = ActionUtils.createButton(new OpenExternalViewerAction(file));
|
||||
|
||||
VBox vBox = new VBox(10, new Label(errorMessage), createButton);
|
||||
@ -218,5 +218,5 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
imageBorder.setCenter(vBox);
|
||||
}
|
||||
|
||||
abstract Task<Image> newReadImageTask(DrawableFile<?> file);
|
||||
abstract Task<Image> newReadImageTask(DrawableFile file);
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ public interface DrawableView {
|
||||
|
||||
Region getCategoryBorderRegion();
|
||||
|
||||
Optional<DrawableFile<?>> getFile();
|
||||
Optional<DrawableFile> getFile();
|
||||
|
||||
void setFile(final Long fileID);
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -47,6 +48,7 @@ import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableSet;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
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.ImageGalleryController;
|
||||
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.CategorizeAction;
|
||||
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.GroupViewState;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
@ -306,7 +307,7 @@ public class GroupPane extends BorderPane {
|
||||
|
||||
}
|
||||
|
||||
void syncCatToggle(DrawableFile<?> file) {
|
||||
void syncCatToggle(DrawableFile file) {
|
||||
getToggleForCategory(file.getCategory()).setSelected(true);
|
||||
}
|
||||
|
||||
@ -377,10 +378,10 @@ public class GroupPane extends BorderPane {
|
||||
*/
|
||||
@FXML
|
||||
@NbBundle.Messages({"GroupPane.gridViewContextMenuItem.extractFiles=Extract File(s)",
|
||||
"GroupPane.bottomLabel.displayText=Group Viewing History: ",
|
||||
"GroupPane.hederLabel.displayText=Tag Selected Files:",
|
||||
"GroupPane.catContainerLabel.displayText=Categorize Selected File:",
|
||||
"GroupPane.catHeadingLabel.displayText=Category:"})
|
||||
"GroupPane.bottomLabel.displayText=Group Viewing History: ",
|
||||
"GroupPane.hederLabel.displayText=Tag Selected Files:",
|
||||
"GroupPane.catContainerLabel.displayText=Categorize Selected File:",
|
||||
"GroupPane.catHeadingLabel.displayText=Category:"})
|
||||
void initialize() {
|
||||
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
||||
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
||||
@ -403,9 +404,9 @@ public class GroupPane extends BorderPane {
|
||||
toggleForCategory.getStyleClass().add("toggle-button");
|
||||
toggleForCategory.selectedProperty().addListener((ov, wasSelected, toggleSelected) -> {
|
||||
if (toggleSelected && slideShowPane != null) {
|
||||
slideShowPane.getFileID().ifPresent((fileID) -> {
|
||||
slideShowPane.getFileID().ifPresent(fileID -> {
|
||||
selectionModel.clearAndSelect(fileID);
|
||||
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), "");
|
||||
new CategorizeAction(controller, cat, ImmutableSet.of(fileID)).handle(null);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -416,7 +417,7 @@ public class GroupPane extends BorderPane {
|
||||
flashAnimation.setAutoReverse(true);
|
||||
|
||||
//configure gridView cell properties
|
||||
DoubleBinding cellSize = Toolbar.getDefault(controller).sizeSliderValue().add(75);
|
||||
DoubleBinding cellSize = controller.thumbnailSizeProperty().add(75);
|
||||
gridView.cellHeightProperty().bind(cellSize);
|
||||
gridView.cellWidthProperty().bind(cellSize);
|
||||
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell());
|
||||
@ -762,37 +763,42 @@ public class GroupPane extends BorderPane {
|
||||
selectAllFiles();
|
||||
t.consume();
|
||||
}
|
||||
if (selectionModel.getSelected().isEmpty() == false) {
|
||||
switch (t.getCode()) {
|
||||
case NUMPAD0:
|
||||
case DIGIT0:
|
||||
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(Category.ZERO), "");
|
||||
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;
|
||||
ObservableSet<Long> selected = selectionModel.getSelected();
|
||||
if (selected.isEmpty() == false) {
|
||||
Category cat = keyCodeToCat(t.getCode());
|
||||
if (cat != null) {
|
||||
new CategorizeAction(controller, cat, selected).handle(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
Long lastSelectFileId = selectionModel.lastSelectedProperty().get();
|
||||
|
||||
@ -826,8 +832,10 @@ public class GroupPane extends BorderPane {
|
||||
private ContextMenu buildContextMenu() {
|
||||
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);
|
||||
|
||||
|
@ -201,7 +201,7 @@ public class MetaDataPane extends DrawableUIBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
Task<Image> newReadImageTask(DrawableFile<?> file) {
|
||||
Task<Image> newReadImageTask(DrawableFile file) {
|
||||
return file.getThumbnailTask();
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,6 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.VideoFile;
|
||||
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.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
|
||||
@ -186,16 +185,16 @@ public class SlideShowView extends DrawableTileBase {
|
||||
synchronized protected void updateContent() {
|
||||
disposeContent();
|
||||
if (getFile().isPresent()) {
|
||||
DrawableFile<?> file = getFile().get();
|
||||
DrawableFile file = getFile().get();
|
||||
if (file.isVideo()) {
|
||||
doMediaLoadTask((VideoFile<?>) file);
|
||||
doMediaLoadTask((VideoFile) file);
|
||||
} else {
|
||||
doReadImageTask(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized private Node doMediaLoadTask(VideoFile<?> file) {
|
||||
synchronized private Node doMediaLoadTask(VideoFile file) {
|
||||
|
||||
//specially handling for videos
|
||||
MediaLoadTask myTask = new MediaLoadTask(file);
|
||||
@ -225,7 +224,7 @@ public class SlideShowView extends DrawableTileBase {
|
||||
}
|
||||
|
||||
@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()
|
||||
try {
|
||||
Node mediaNode = mediaTask.get();
|
||||
@ -265,7 +264,7 @@ public class SlideShowView extends DrawableTileBase {
|
||||
*/
|
||||
@Override
|
||||
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
|
||||
@ThreadConfined(type = ThreadType.ANY)
|
||||
public Category updateCategory() {
|
||||
Optional<DrawableFile<?>> file = getFile();
|
||||
Optional<DrawableFile> file = getFile();
|
||||
if (file.isPresent()) {
|
||||
Category updateCategory = super.updateCategory();
|
||||
Platform.runLater(() -> getGroupPane().syncCatToggle(file.get()));
|
||||
@ -319,7 +318,7 @@ public class SlideShowView extends DrawableTileBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
Task<Image> newReadImageTask(DrawableFile<?> file) {
|
||||
Task<Image> newReadImageTask(DrawableFile file) {
|
||||
return file.getReadFullSizeImageTask();
|
||||
|
||||
}
|
||||
@ -328,9 +327,9 @@ public class SlideShowView extends DrawableTileBase {
|
||||
"MediaLoadTask.messageText=Reading video: {0}"})
|
||||
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()));
|
||||
this.file = file;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
.groupTreeCell{
|
||||
.groupCell{
|
||||
|
||||
-fx-indent:5; /* default indent is 10 */
|
||||
}
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -18,21 +18,18 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.Comparator;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@NbBundle.Messages({"GroupComparators.uncategorizedCount=Uncategorized Count",
|
||||
"GroupComparators.groupName=Group Name",
|
||||
"GroupComparators.hitCount=Hit Count",
|
||||
"GroupComparators.groupSize=Group Size",
|
||||
"GroupComparators.hitDensity=Hit Density"})
|
||||
"GroupComparators.groupName=Group Name",
|
||||
"GroupComparators.hitCount=Hit Count",
|
||||
"GroupComparators.groupSize=Group Size",
|
||||
"GroupComparators.hitDensity=Hit Density"})
|
||||
final class GroupComparators<T extends Comparable<T>> implements Comparator<DrawableGroup> {
|
||||
|
||||
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 =
|
||||
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() {
|
||||
return values;
|
||||
public static ObservableList<GroupComparators<?>> getValues() {
|
||||
return FXCollections.unmodifiableObservableList(values);
|
||||
}
|
||||
|
||||
private final Function<DrawableGroup, T> extractor;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -72,7 +72,8 @@ final public class GroupTree extends NavPanel<TreeItem<GroupTreeNode>> {
|
||||
getToolBar().visibleProperty().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);
|
||||
|
||||
getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
|
||||
@ -151,4 +152,5 @@ final public class GroupTree extends NavPanel<TreeItem<GroupTreeNode>> {
|
||||
return Arrays.asList(stripStart);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -79,8 +79,8 @@ final public class HashHitGroupList extends NavPanel<DrawableGroup> {
|
||||
getBorderPane().setCenter(groupList);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -1,63 +1,14 @@
|
||||
<?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.RadioButton?>
|
||||
<?import javafx.scene.control.ToggleGroup?>
|
||||
<?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.VBox?>
|
||||
|
||||
|
||||
<fx:root type="Tab" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" closable="false" >
|
||||
<fx:root closable="false" type="Tab" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<content>
|
||||
<BorderPane prefHeight="200.0" prefWidth="200.0" fx:id="borderPane">
|
||||
<BorderPane fx:id="borderPane">
|
||||
<top>
|
||||
<ToolBar fx:id="toolBar" 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>
|
||||
<ToolBar fx:id="toolBar" minWidth="-Infinity" BorderPane.alignment="CENTER" />
|
||||
</top>
|
||||
</BorderPane>
|
||||
</content>
|
||||
|
@ -22,15 +22,13 @@ import com.google.common.eventbus.Subscribe;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
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.Tab;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.control.ToolBar;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javax.swing.SortOrder;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
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.GroupManager;
|
||||
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.
|
||||
@ -50,24 +49,10 @@ abstract class NavPanel<X> extends Tab {
|
||||
@FXML
|
||||
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 GroupManager groupManager;
|
||||
private final CategoryManager categoryManager;
|
||||
private SortChooser<DrawableGroup, GroupComparators<?>> sortChooser;
|
||||
|
||||
NavPanel(ImageGalleryController controller) {
|
||||
this.controller = controller;
|
||||
@ -75,34 +60,35 @@ abstract class NavPanel<X> extends Tab {
|
||||
this.categoryManager = controller.getCategoryManager();
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<GroupComparators<?>> comparatorProperty() {
|
||||
return sortChooser.comparatorProperty();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages({"NavPanel.ascRadio.text=Ascending",
|
||||
"NavPanel.descRadio.text=Descending",
|
||||
"NavPanel.sortByBoxLabel.text=Sort By:"})
|
||||
"NavPanel.descRadio.text=Descending",
|
||||
"NavPanel.sortByBoxLabel.text=Sort By:"})
|
||||
void initialize() {
|
||||
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 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());
|
||||
sortByBox.getSelectionModel().select(getDefaultComparator());
|
||||
orderGroup.selectedToggleProperty().addListener(order -> sortGroups());
|
||||
sortByBox.getSelectionModel().selectedItemProperty().addListener(observable -> {
|
||||
sortChooser = new SortChooser<>(GroupComparators.getValues());
|
||||
sortChooser.setComparator(getDefaultComparator());
|
||||
sortChooser.sortOrderProperty().addListener(order -> sortGroups());
|
||||
sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> {
|
||||
sortGroups();
|
||||
//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);
|
||||
} else {
|
||||
categoryManager.unregisterListener(NavPanel.this);
|
||||
}
|
||||
});
|
||||
|
||||
ascRadio.setText(Bundle.NavPanel_ascRadio_text());
|
||||
descRadio.setText(Bundle.NavPanel_descRadio_text());
|
||||
sortByBoxLabel.setText(Bundle.NavPanel_sortByBoxLabel_text());
|
||||
final SortChooser.ValueType valueType = newComparator == GroupComparators.ALPHABETICAL ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC;
|
||||
sortChooser.setValueType(valueType);
|
||||
});
|
||||
toolBar.getItems().add(sortChooser);
|
||||
|
||||
//keep selection in sync with controller
|
||||
controller.viewState().addListener(observable -> {
|
||||
Optional.ofNullable(controller.viewState().get())
|
||||
@ -129,8 +115,8 @@ abstract class NavPanel<X> extends Tab {
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
Comparator<DrawableGroup> getComparator() {
|
||||
Comparator<DrawableGroup> comparator = sortByBox.getSelectionModel().getSelectedItem();
|
||||
return (orderGroup.getSelectedToggle() == ascRadio)
|
||||
Comparator<DrawableGroup> comparator = sortChooser.getComparator();
|
||||
return (sortChooser.getSortOrder() == SortOrder.ASCENDING)
|
||||
? comparator
|
||||
: comparator.reversed();
|
||||
}
|
||||
@ -194,22 +180,6 @@ abstract class NavPanel<X> extends Tab {
|
||||
return toolBar;
|
||||
}
|
||||
|
||||
ComboBox<GroupComparators<?>> getSortByBox() {
|
||||
return sortByBox;
|
||||
}
|
||||
|
||||
RadioButton getAscRadio() {
|
||||
return ascRadio;
|
||||
}
|
||||
|
||||
ToggleGroup getOrderGroup() {
|
||||
return orderGroup;
|
||||
}
|
||||
|
||||
RadioButton getDescRadio() {
|
||||
return descRadio;
|
||||
}
|
||||
|
||||
ImageGalleryController getController() {
|
||||
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 |
@ -274,9 +274,10 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
} else {
|
||||
try {
|
||||
// Get the associated artifact attribute and return its value as the ID
|
||||
List<BlackboardAttribute> blackboardAttributes = artifact.getAttributes(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT);
|
||||
if (!blackboardAttributes.isEmpty()) {
|
||||
return blackboardAttributes.get(0).getValueLong();
|
||||
BlackboardAttribute blackboardAttribute = artifact.getAttribute(
|
||||
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
|
||||
if (blackboardAttribute != null) {
|
||||
return blackboardAttribute.getValueLong();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting associated artifact attributes", ex); //NON-NLS
|
||||
|
@ -142,7 +142,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
|
||||
try {
|
||||
fileTypeDetector = new FileTypeDetector();
|
||||
} 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();
|
||||
this.context = context;
|
||||
@ -162,7 +162,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
|
||||
String details = NbBundle.getMessage(this.getClass(), "SolrConnectionCheck.Port");
|
||||
logger.log(Level.SEVERE, "{0}: {1} {2}", new Object[]{msg, details, ex.toString()});
|
||||
services.postMessage(IngestMessage.createErrorMessage(KeywordSearchModuleFactory.getModuleName(), msg, details));
|
||||
throw new IngestModuleException(msg);
|
||||
throw new IngestModuleException(msg, ex);
|
||||
}
|
||||
try {
|
||||
kwsService.tryConnect(UserPreferences.getIndexingServerHost(), port);
|
||||
@ -171,7 +171,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
|
||||
String details = ex.getMessage();
|
||||
logger.log(Level.SEVERE, "{0}: {1} {2}", new Object[]{msg, details, ex.toString()});
|
||||
services.postMessage(IngestMessage.createErrorMessage(KeywordSearchModuleFactory.getModuleName(), msg, details));
|
||||
throw new IngestModuleException(msg);
|
||||
throw new IngestModuleException(msg, ex);
|
||||
}
|
||||
} else {
|
||||
// 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 details = NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.init.tryStopSolrMsg", msg);
|
||||
services.postMessage(IngestMessage.createErrorMessage(KeywordSearchModuleFactory.getModuleName(), msg, details));
|
||||
throw new IngestModuleException(msg);
|
||||
throw new IngestModuleException(msg, ex);
|
||||
}
|
||||
try {
|
||||
// make an actual query to verify that server is responding
|
||||
@ -198,7 +198,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule {
|
||||
} catch (KeywordSearchModuleException | NoOpenCoreException ex) {
|
||||
throw new IngestModuleException(
|
||||
NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.init.exception.errConnToSolr.msg",
|
||||
ex.getMessage()));
|
||||
ex.getMessage()), ex);
|
||||
}
|
||||
|
||||
// check if this job has any searchable keywords
|
||||
|
@ -161,11 +161,11 @@ class SearchEngineURLQueryAnalyzer extends Extract {
|
||||
}
|
||||
|
||||
} 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) {
|
||||
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) {
|
||||
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
|
||||
@ -394,7 +394,7 @@ class SearchEngineURLQueryAnalyzer extends Extract {
|
||||
String message = NbBundle
|
||||
.getMessage(this.getClass(), "SearchEngineURLQueryAnalyzer.init.exception.msg", XMLFILE);
|
||||
logger.log(Level.SEVERE, message, e);
|
||||
throw new IngestModuleException(message);
|
||||
throw new IngestModuleException(message, e);
|
||||
}
|
||||
|
||||
loadConfigFile();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user