This commit is contained in:
Karl Mortensen 2015-10-23 09:39:59 -04:00
commit add51c6ecd
78 changed files with 1596 additions and 1272 deletions

View File

@ -42,10 +42,11 @@ to the root 64-bit JRE directory.
use a released version or download the latest from github:
- git://github.com/sleuthkit/sleuthkit.git
2b) Build the TSK JAR file by typing 'ant PostgreSQL' in bindings/java in the
TSK source code folder from a command line. Note it is case sensitive. You
can also add the code to a NetBeans project and build it from there,
selecting the PostgreSQL target.
2b) Build the TSK JAR file by typing 'ant dist-PostgreSQL' in
bindings/java in the
TSK source code folder from a command line. Note it is case
sensitive. You can also add the code to a NetBeans project and build
it from there, selecting the dist-PostgreSQL target.
2c) Set TSK_HOME environment variable to the root directory of TSK

View File

@ -245,7 +245,6 @@ NewCaseVisualPanel1.singleUserCaseRadioButton.text=Single-user
NewCaseVisualPanel1.multiUserSettingsWarningLabel.text=Multi-user settings warning label
Case.deleteReports.deleteFromDiskException.log.msg=Unable to delete the report from the disk.
Case.deleteReports.deleteFromDiskException.msg=Unable to delete the report {0} from the disk.\nYou may manually delete it from {1}
CaseOpenException.DatabaseSettingsIssue=Database settings:
CaseExceptionWarning.CheckMultiUserOptions=Check Multi-user options.
SingleUserCaseConverter.BadDatabaseFileName=Database file does not exist!
SingleUserCaseConverter.AlreadyMultiUser=Case is already multi-user!

View File

@ -349,7 +349,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
}
// start listening for TSK errors for the new case
currentCase.tskErrorReporter = new IntervalErrorReportData(currentCase, MIN_SECONDS_BETWEEN_ERROR_REPORTS,
NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText"));
NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText"));
doCaseChange(currentCase);
SwingUtilities.invokeLater(() -> {
RecentCases.getInstance().addRecentCase(currentCase.name, currentCase.configFilePath); // update the recent cases
@ -378,7 +378,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
});
}
@Override
public void receiveError(String context, String errorMessage) {
/* NOTE: We are accessing tskErrorReporter from two different threads.
@ -464,7 +464,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
SwingUtilities.invokeLater(() -> {
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
});
throw new CaseActionException(NbBundle.getMessage(Case.class, "CaseOpenException.DatabaseSettingsIssue") + " " + ex.getMessage()); //NON-NLS
throw new CaseActionException(ex.getMessage(), ex); //NON-NLS
} catch (UserPreferencesException ex) {
logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
throw new CaseActionException(
@ -581,7 +581,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled"));
}
try {
db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseDir);
db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseDir);
} catch (UserPreferencesException ex) {
logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
throw new CaseActionException(
@ -635,7 +635,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
} catch (IllegalStateException unused) {
// Already logged.
}
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.gen.msg") + ". " + ex.getMessage(), ex); //NON-NLS
throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.gen.msg") + ": " + ex.getMessage(), ex); //NON-NLS
} catch (TskCoreException ex) {
try {
Case badCase = Case.getCurrentCase();
@ -646,7 +646,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
SwingUtilities.invokeLater(() -> {
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
});
throw new CaseActionException(NbBundle.getMessage(Case.class, "CaseOpenException.DatabaseSettingsIssue") + " " + ex.getMessage(), ex); //NON-NLS
throw new CaseActionException(ex.getMessage(), ex); //NON-NLS
}
}
@ -704,7 +704,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
*
* @deprecated As of release 4.0, replaced by {@link #notifyAddingDataSource(java.util.UUID) and
* {@link #notifyDataSourceAdded(org.sleuthkit.datamodel.Content, java.util.UUID) and
* {@link #notifyFailedAddingDataSource(java.util.UUID)}
* {@link #notifyFailedAddingDataSource(java.util.UUID)}
*/
@Deprecated
public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
@ -719,13 +719,13 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* Finishes adding new local data source to the case. Sends out event and
* reopens windows if needed.
* reopens windows if needed.
*
* @param newDataSource new data source added
*
* @deprecated As of release 4.0, replaced by {@link #notifyAddingDataSource(java.util.UUID) and
* {@link #notifyDataSourceAdded(org.sleuthkit.datamodel.Content, java.util.UUID) and
* {@link #notifyFailedAddingDataSource(java.util.UUID)}
* {@link #notifyFailedAddingDataSource(java.util.UUID)}
*/
@Deprecated
void addLocalDataSource(Content newDataSource) {

View File

@ -101,7 +101,7 @@ public final class CaseOpenAction implements ActionListener {
StartupWindowProvider.getInstance().open();
}
});
}
}
}).start();
}
}

View File

@ -111,7 +111,6 @@ final class NewCaseWizardAction extends CallableSystemAction {
final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS
String createdDirectory = (String) wizardDescriptor.getProperty("createdDirectory"); //NON-NLS
CaseType caseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS
Case.create(createdDirectory, caseName, caseNumber, examiner, caseType);
return null;
}
@ -121,26 +120,10 @@ final class NewCaseWizardAction extends CallableSystemAction {
final String caseName = (String) wizardDescriptor.getProperty("caseName"); //NON-NLS
try {
get();
CaseType currentCaseType = CaseType.values()[(int) wizardDescriptor.getProperty("caseType")]; //NON-NLS
CaseDbConnectionInfo info = UserPreferences.getDatabaseConnectionInfo();
if (currentCaseType == CaseType.SINGLE_USER_CASE) {
AddImageAction addImageAction = SystemAction.get(AddImageAction.class);
addImageAction.actionPerformed(null);
} else {
if (info.getDbType() != DbType.SQLITE) {
SleuthkitCase.tryConnect(info);
AddImageAction addImageAction = SystemAction.get(AddImageAction.class);
addImageAction.actionPerformed(null);
} else {
JOptionPane.showMessageDialog(null,
NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.databaseProblem1.text"),
NbBundle.getMessage(this.getClass(), "NewCaseWizardAction.databaseProblem2.text"),
JOptionPane.ERROR_MESSAGE);
doFailedCaseCleanup(wizardDescriptor);
}
}
} catch (InterruptedException | ExecutionException | MissingResourceException | TskCoreException | HeadlessException | UserPreferencesException ex) {
AddImageAction addImageAction = SystemAction.get(AddImageAction.class);
addImageAction.actionPerformed(null);
} catch (Exception ex) {
logger.log(Level.SEVERE, "Error creating case", ex); //NON-NLS
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), ex.getCause().getMessage() + " "
+ NbBundle.getMessage(this.getClass(), "CaseExceptionWarning.CheckMultiUserOptions"),

View File

@ -66,7 +66,7 @@ public class Installer extends ModuleInstall {
//We should update this if we officially switch to a new version of CRT/compiler
System.loadLibrary("msvcr100"); //NON-NLS
System.loadLibrary("msvcp100"); //NON-NLS
System.loadLibrary("msvcr120"); //NON-NLS
logger.log(Level.INFO, "MSVCR100 and MSVCP100 libraries loaded"); //NON-NLS
} catch (UnsatisfiedLinkError e) {
logger.log(Level.SEVERE, "Error loading MSVCR100 and MSVCP100 libraries, ", e); //NON-NLS
@ -86,6 +86,14 @@ public class Installer extends ModuleInstall {
logger.log(Level.SEVERE, "Error loading EWF library, ", e); //NON-NLS
}
/* PostgreSQL */
try {
System.loadLibrary("msvcr120"); //NON-NLS
logger.log(Level.INFO, "MSVCR 120 library loaded"); //NON-NLS
} catch (UnsatisfiedLinkError e) {
logger.log(Level.SEVERE, "Error loading MSVCR120 library, ", e); //NON-NLS
}
try {
System.loadLibrary("libeay32"); //NON-NLS
logger.log(Level.INFO, "LIBEAY32 library loaded"); //NON-NLS

View File

@ -197,6 +197,7 @@
<file name="org-netbeans-modules-autoupdate-ui-actions-PluginManagerAction.shadow"/>
<file name="org-netbeans-modules-favorites-templates-TemplatesAction.shadow_hidden"/>
<file name="org-netbeans-modules-options-OptionsWindowAction.shadow"/>
<file name="org-netbeans-modules-templates-actions-TemplatesAction.shadow_hidden"/>
<file name="org-openide-actions-ToolsAction.shadow_hidden"/>
<file name="org-sleuthkit-autopsy-filesearch-FileSearchAction.shadow">
<attr name="originalFile" stringvalue="Actions/Tools/org-sleuthkit-autopsy-filesearch-FileSearchAction.instance"/>

View File

@ -22,6 +22,7 @@ import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
@ -174,6 +175,9 @@ public class MediaViewImagePanel extends JPanel implements DataContentViewerMedi
borderpane.setCenter(fxImageView);
}
}
} catch (EOFException ex) {
LOGGER.log(Level.WARNING, "Could not load image file into media view (EOF): {0}", file.getName()); //NON-NLS
borderpane.setCenter(errorLabel);
} catch (IllegalArgumentException | IOException ex) {
LOGGER.log(Level.WARNING, "Could not load image file into media view: " + file.getName(), ex); //NON-NLS
borderpane.setCenter(errorLabel);

View File

@ -26,6 +26,7 @@ import com.google.common.io.Files;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@ -135,8 +136,8 @@ public class ImageUtils {
/**
* thread that saves generated thumbnails to disk in the background
*/
private static final Executor imageSaver
= Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
private static final Executor imageSaver =
Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
.namingPattern("icon saver-%d").build());
public static List<String> getSupportedImageExtensions() {
@ -524,14 +525,13 @@ public class ImageUtils {
return ScalrWrapper.cropImage(bi, Math.min(iconSize, bi.getWidth()), Math.min(iconSize, bi.getHeight()));
}
} catch (OutOfMemoryError e) {
LOGGER.log(Level.WARNING, "Could not scale image (too large): " + content.getName(), e); //NON-NLS
return null;
LOGGER.log(Level.WARNING, "Could not scale image (too large) " + content.getName(), e); //NON-NLS
} catch (EOFException e) {
LOGGER.log(Level.WARNING, "Could not load image (EOF) {0}", content.getName()); //NON-NLS
} catch (Exception e) {
LOGGER.log(Level.WARNING, "Could not load image: " + content.getName(), e); //NON-NLS
return null;
LOGGER.log(Level.WARNING, "Could not load image " + content.getName(), e); //NON-NLS
}
return null;
}
}

View File

@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.modules.filetypeid;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
@ -117,7 +118,6 @@ class FileType {
* The way the signature byte sequence should be interpreted.
*/
enum Type {
RAW, ASCII
};
@ -131,14 +131,42 @@ class FileType {
*
* @param signatureBytes The signature bytes.
* @param offset The offset of the signature bytes.
* @param type The interpretation of the signature bytes
* (e.g., raw bytes, an ASCII string).
* @param type The type of data in the byte array. Impacts
* how it is displayed to the user in the UI.
*/
Signature(final byte[] signatureBytes, long offset, Type type) {
this.signatureBytes = Arrays.copyOf(signatureBytes, signatureBytes.length);
this.offset = offset;
this.type = type;
}
/**
* Creates a file signature consisting of an ASCII string at a
* specific offset within a file.
*
* @param signatureString The ASCII string
* @param offset The offset of the signature bytes.
*/
Signature(String signatureString, long offset) {
this.signatureBytes = signatureString.getBytes(StandardCharsets.US_ASCII);
this.offset = offset;
this.type = Type.ASCII;
}
/**
* Creates a file signature consisting of a sequence of bytes at a
* specific offset within a file. If bytes correspond to an ASCII
* string, use one of the other constructors so that the string is
* displayed to the user instead of the raw bytes.
*
* @param signatureBytes The signature bytes.
* @param offset The offset of the signature bytes.
*/
Signature(final byte[] signatureBytes, long offset) {
this.signatureBytes = Arrays.copyOf(signatureBytes, signatureBytes.length);
this.offset = offset;
this.type = Type.RAW;
}
/**
* Gets the byte sequence of the signature.

View File

@ -180,20 +180,54 @@ final class UserDefinedFileTypesManager {
* org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException
*/
private void loadPredefinedFileTypes() throws UserDefinedFileTypesException {
byte[] byteArray;
FileType fileType;
try {
FileType fileTypeXml = new FileType("text/xml", new Signature("<?xml".getBytes(ASCII_ENCODING), 0L, FileType.Signature.Type.ASCII), "", false); //NON-NLS
fileTypes.put(fileTypeXml.getMimeType(), fileTypeXml);
byte[] gzip = DatatypeConverter.parseHexBinary("1F8B08");
FileType fileTypeGzip = new FileType("application/x-gzip", new Signature(gzip, 0L, FileType.Signature.Type.ASCII), "", false); //NON-NLS
fileTypes.put(fileTypeGzip.getMimeType(), fileTypeGzip);
} catch (UnsupportedEncodingException ex) {
/**
* Using an all-or-none policy.
*/
fileTypes.clear();
throwUserDefinedFileTypesException(ex, "UserDefinedFileTypesManager.loadFileTypes.errorMessage");
// Add rule for xml
fileType = new FileType("text/xml", new Signature("<?xml", 0L), "", false); //NON-NLS
fileTypes.put(fileType.getMimeType(), fileType);
// Add rule for gzip
byteArray = DatatypeConverter.parseHexBinary("1F8B"); //NON-NLS
fileType = new FileType("application/x-gzip", new Signature(byteArray, 0L), "", false); //NON-NLS
fileTypes.put(fileType.getMimeType(), fileType);
// Add rule for .wk1
byteArray = DatatypeConverter.parseHexBinary("0000020006040600080000000000"); //NON-NLS
fileType = new FileType("application/x-123", new Signature(byteArray, 0L), "", false); //NON-NLS
fileTypes.put(fileType.getMimeType(), fileType);
// Add rule for Radiance image
byteArray = DatatypeConverter.parseHexBinary("233F52414449414E43450A");//NON-NLS
fileType = new FileType("image/vnd.radiance", new Signature(byteArray, 0L), "", false); //NON-NLS
fileTypes.put(fileType.getMimeType(), fileType);
// Add rule for .dcx image
byteArray = DatatypeConverter.parseHexBinary("B168DE3A"); //NON-NLS
fileType = new FileType("image/x-dcx", new Signature(byteArray, 0L), "", false); //NON-NLS
fileTypes.put(fileType.getMimeType(), fileType);
// Add rule for .ics image
fileType = new FileType("image/x-icns", new Signature("icns", 0L), "", false); //NON-NLS
fileTypes.put(fileType.getMimeType(), fileType);
// Add rule for .pict image
byteArray = DatatypeConverter.parseHexBinary("001102FF"); //NON-NLS
fileType = new FileType("image/x-pict", new Signature(byteArray, 522L), "", false); //NON-NLS
fileTypes.put(fileType.getMimeType(), fileType);
// Add rule for .pam
fileType = new FileType("image/x-portable-arbitrarymap", new Signature("P7", 0L), "", false); //NON-NLS
fileTypes.put(fileType.getMimeType(), fileType);
// Add rule for .pfm
fileType = new FileType("image/x-portable-floatmap", new Signature("PF", 0L), "", false); //NON-NLS
fileTypes.put(fileType.getMimeType(), fileType);
}
// parseHexBinary() throws this if the argument passed in is not Hex
catch (IllegalArgumentException e) {
throw new UserDefinedFileTypesException("Error creating predefined file types", e); //
}
}

View File

@ -249,6 +249,9 @@ public class ReportProgressPanel extends javax.swing.JPanel {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
// make sure we disable an indeterminate
reportProgressBar.setIndeterminate(false);
if (STATUS != ReportStatus.CANCELED) {
switch (reportStatus) {
case COMPLETE: {
@ -261,6 +264,7 @@ public class ReportProgressPanel extends javax.swing.JPanel {
// set reportProgressBar color as green.
reportProgressBar.setForeground(new Color(50, 205, 50));
reportProgressBar.setString("Complete"); //NON-NLS
break;
}
case ERROR: {

View File

@ -24,10 +24,7 @@ TimelinePanel.jButton7.text=3d
TimelinePanel.jButton2.text=1m
TimelinePanel.jButton3.text=3m
TimelinePanel.jButton4.text=2w
TimeLineTopComponent.eventsTab.name=Events
TimeLineTopComponent.filterTab.name=Filters
OpenTimelineAction.title=Timeline
OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources.
TimeLineTopComponent.timeZonePanel.text=Display Times In\:
ProgressWindow.progressHeader.text=\

View File

@ -42,8 +42,7 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
* intended only to remove the boilerplate initialization code when defining a
* relatively static layout
*
* TODO: find a way to move this to CoreUtils and remove duplicate verison in
* image analyzer
* TODO: move this to CoreUtils and remove duplicate verison in image analyzer
*/
public class FXMLConstructor {
@ -55,12 +54,28 @@ public class FXMLConstructor {
*
*
* @param node a node to initialize from a loaded FXML
* @param fxmlFileName the the file name of the FXML to load, relative to
* the package that the class of node is defined in.
* @param fxmlFileName the file name of the FXML to load, relative to the
* package that the class of node is defined in.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
static public void construct(Node node, String fxmlFileName) {
final String name = "nbres:/" + StringUtils.replace(node.getClass().getPackage().getName(), ".", "/") + "/" + fxmlFileName; // NON-NLS
construct(node, node.getClass(), fxmlFileName);
}
/**
* Load an fxml file and initialize a node with it. Since this manipulates
* the node, it must be called on the JFX thread.
*
*
* @param node a node to initialize from a loaded FXML
* @param clazz a class to use for relative location of the fxml
* @param fxmlFileName the file name of the FXML to load, relative to the
* package of clazz.
*
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
static public void construct(Node node, Class<? extends Node> clazz, String fxmlFileName) {
final String name = "nbres:/" + StringUtils.replace(clazz.getPackage().getName(), ".", "/") + "/" + fxmlFileName; // NON-NLS
try {
FXMLLoader fxmlLoader = new FXMLLoader(new URL(name));

View File

@ -64,13 +64,13 @@ import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
import org.sleuthkit.autopsy.coreutils.History;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.coreutils.History;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
@ -135,6 +135,21 @@ public class TimeLineController {
private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper();
private final ReadOnlyStringWrapper status = new ReadOnlyStringWrapper();
/**
* status is a string that will be displayed in the status bar as a kind of
* user hint/information when it is not empty
*
* @return the status property
*/
public ReadOnlyStringProperty getStatusProperty() {
return status.getReadOnlyProperty();
}
public void setStatus(String string) {
status.set(string);
}
private final Case autoCase;
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@ -301,8 +316,10 @@ public class TimeLineController {
LOGGER.log(Level.INFO, "Beginning generation of timeline"); // NON-NLS
try {
SwingUtilities.invokeLater(() -> {
if (isWindowOpen()) {
mainFrame.close();
synchronized (TimeLineController.this) {
if (isWindowOpen()) {
mainFrame.close();
}
}
});
final SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
@ -347,8 +364,10 @@ public class TimeLineController {
void rebuildTagsTable() {
LOGGER.log(Level.INFO, "starting to rebuild tags table"); // NON-NLS
SwingUtilities.invokeLater(() -> {
if (isWindowOpen()) {
mainFrame.close();
synchronized (TimeLineController.this) {
if (isWindowOpen()) {
mainFrame.close();
}
}
});
synchronized (eventsRepository) {
@ -373,16 +392,19 @@ public class TimeLineController {
IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener);
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
Case.removePropertyChangeListener(caseListener);
mainFrame.close();
mainFrame.setVisible(false);
mainFrame = null;
SwingUtilities.invokeLater(() -> {
synchronized (TimeLineController.this) {
mainFrame.close();
mainFrame = null;
}
});
}
}
/**
* show the timeline window and prompt for rebuilding database if necessary.
*/
synchronized void openTimeLine() {
void openTimeLine() {
// listen for case changes (specifically images being added, and case changes).
if (Case.isCaseOpen() && !listeningToAutopsy) {
IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener);
@ -524,20 +546,20 @@ public class TimeLineController {
/**
* private method to build gui if necessary and make it visible.
*/
synchronized private void showWindow() {
if (mainFrame == null) {
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
if (mainFrame == null) {
mainFrame = new TimeLineTopComponent();
}
mainFrame.setController(this);
}
private void showWindow() {
SwingUtilities.invokeLater(() -> {
mainFrame.open();
mainFrame.setVisible(true);
mainFrame.toFront();
synchronized (TimeLineController.this) {
if (mainFrame == null) {
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
if (mainFrame == null) {
mainFrame = new TimeLineTopComponent(this);
}
}
mainFrame.open();
mainFrame.toFront();
}
});
}

View File

@ -23,7 +23,7 @@ import java.util.Collections;
import java.util.List;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.event.ActionEvent;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
@ -34,7 +34,6 @@ import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.netbeans.api.settings.ConvertAsProperties;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.util.NbBundle;
@ -51,135 +50,103 @@ import org.sleuthkit.autopsy.timeline.ui.StatusBar;
import org.sleuthkit.autopsy.timeline.ui.TimeLineResultView;
import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel;
import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavPanel;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel;
import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
/**
* TopComponent for the timeline feature.
*/
@ConvertAsProperties(
dtd = "-//org.sleuthkit.autopsy.timeline//TimeLine//EN",
autostore = false)
@TopComponent.Description(
preferredID = "TimeLineTopComponent",
//iconBase="SET/PATH/TO/ICON/HERE",
persistenceType = TopComponent.PERSISTENCE_NEVER)
@TopComponent.Registration(mode = "timeline", openAtStartup = false)
public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider, TimeLineUI {
public final class TimeLineTopComponent extends TopComponent implements ExplorerManager.Provider {
private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName());
private DataContentPanel dataContentPanel;
private final DataContentPanel dataContentPanel;
private TimeLineResultView tlrv;
private final TimeLineResultView tlrv;
private final ExplorerManager em = new ExplorerManager();
private TimeLineController controller;
private final TimeLineController controller;
////jfx componenets that make up the interface
private final FilterSetPanel filtersPanel = new FilterSetPanel();
private final Tab eventsTab = new Tab(
NbBundle.getMessage(TimeLineTopComponent.class, "TimeLineTopComponent.eventsTab.name"));
private final Tab filterTab = new Tab(
NbBundle.getMessage(TimeLineTopComponent.class, "TimeLineTopComponent.filterTab.name"));
private final VBox leftVBox = new VBox(5);
private final NavPanel navPanel = new NavPanel();
private final StatusBar statusBar = new StatusBar();
private final TabPane tabPane = new TabPane();
private final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane();
private final VisualizationPanel visualizationPanel = new VisualizationPanel(navPanel);
private final SplitPane splitPane = new SplitPane();
private final TimeZonePanel timeZonePanel = new TimeZonePanel();
public TimeLineTopComponent() {
public TimeLineTopComponent(TimeLineController controller) {
initComponents();
this.controller = controller;
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
timeZonePanel.setText(NbBundle.getMessage(this.getClass(), "TimeLineTopComponent.timeZonePanel.text"));
customizeComponents();
}
synchronized private void customizeComponents() {
dataContentPanel = DataContentPanel.createInstance();
this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER);
tlrv = new TimeLineResultView(dataContentPanel);
tlrv = new TimeLineResultView(controller, dataContentPanel);
DataResultPanel dataResultPanel = tlrv.getDataResultPanel();
this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER);
dataResultPanel.open();
Platform.runLater(() -> {
//assemble ui componenets together
jFXstatusPanel.setScene(new Scene(statusBar));
jFXVizPanel.setScene(new Scene(splitPane));
splitPane.setDividerPositions(0);
filterTab.setClosable(false);
filterTab.setContent(filtersPanel);
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
eventsTab.setClosable(false);
eventsTab.setContent(navPanel);
eventsTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
tabPane.getTabs().addAll(filterTab, eventsTab);
VBox.setVgrow(tabPane, Priority.ALWAYS);
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
leftVBox.getChildren().addAll(timeZonePanel, zoomSettingsPane, tabPane);
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
splitPane.getItems().addAll(leftVBox, visualizationPanel);
});
customizeFXComponents();
}
@Override
public synchronized void setController(TimeLineController controller) {
this.controller = controller;
tlrv.setController(controller);
@NbBundle.Messages({"TimeLineTopComponent.eventsTab.name=Events",
"TimeLineTopComponent.filterTab.name=Filters"})
void customizeFXComponents() {
Platform.runLater(() -> {
jFXVizPanel.getScene().addEventFilter(KeyEvent.KEY_PRESSED,
(KeyEvent event) -> {
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
new Back(controller).handle(new ActionEvent());
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
new Back(controller).handle(new ActionEvent());
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
new Forward(controller).handle(new ActionEvent());
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
new Forward(controller).handle(new ActionEvent());
}
});
//create and wire up jfx componenets that make up the interface
final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller));
filterTab.setClosable(false);
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
final EventsTree eventsTree = new EventsTree(controller);
final VisualizationPanel visualizationPanel = new VisualizationPanel(controller, eventsTree);
final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree);
eventsTreeTab.setClosable(false);
eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
eventsTreeTab.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab);
VBox.setVgrow(leftTabPane, Priority.ALWAYS);
controller.viewModeProperty().addListener((Observable observable) -> {
if (controller.viewModeProperty().get().equals(VisualizationMode.COUNTS)) {
tabPane.getSelectionModel().select(filterTab);
//if view mode is counts, make sure events tabd is not active
leftTabPane.getSelectionModel().select(filterTab);
}
});
eventsTab.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
visualizationPanel.setController(controller);
navPanel.setController(controller);
filtersPanel.setController(controller);
zoomSettingsPane.setController(controller);
statusBar.setController(controller);
final TimeZonePanel timeZonePanel = new TimeZonePanel();
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller);
final VBox leftVBox = new VBox(5, timeZonePanel, zoomSettingsPane, leftTabPane);
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
final SplitPane mainSplitPane = new SplitPane(leftVBox, visualizationPanel);
mainSplitPane.setDividerPositions(0);
final Scene scene = new Scene(mainSplitPane);
scene.addEventFilter(KeyEvent.KEY_PRESSED,
(KeyEvent event) -> {
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
new Back(controller).handle(null);
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
new Back(controller).handle(null);
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
new Forward(controller).handle(null);
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
new Forward(controller).handle(null);
}
});
//add ui componenets to JFXPanels
jFXVizPanel.setScene(scene);
jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
});
}
@ -196,9 +163,9 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jFXstatusPanel = new javafx.embed.swing.JFXPanel();
jFXstatusPanel = new JFXPanel();
splitYPane = new javax.swing.JSplitPane();
jFXVizPanel = new javafx.embed.swing.JFXPanel();
jFXVizPanel = new JFXPanel();
lowerSplitXPane = new javax.swing.JSplitPane();
resultContainerPanel = new javax.swing.JPanel();
contentViewerContainerPanel = new javax.swing.JPanel();

View File

@ -1,27 +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.timeline;
/**
*
*/
public interface TimeLineUI {
void setController(TimeLineController controller);
}

View File

@ -1,36 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.timeline;
/**
* Interface to be implemented by views of the data.
*
* Most implementations should install the relevant listeners in their
* {@link #setController} and {@link #setModel} methods
*/
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
public interface TimeLineView extends TimeLineUI {
@Override
void setController(TimeLineController controller);
void setModel(final FilteredEventsModel filteredEvents);
}

View File

@ -1,6 +1,4 @@
Back.actions.name.text=Back
DefaultFilters.action.name.text=apply default filters
Forward.action.name.text=Forward
SaveSnapshot.action.name.text=save snapshot
SaveSnapshot.fileChoose.title.text=Save snapshot to
ZoomOut.action.name.text=apply default filters

View File

@ -46,51 +46,48 @@ import org.sleuthkit.datamodel.TskCoreException;
/**
*/
public class SaveSnapshot extends Action {
public class SaveSnapshotAsReport extends Action {
private static final String HTML_EXT = ".html";
private static final String REPORT_IMAGE_EXTENSION = ".png";
private static final Logger LOGGER = Logger.getLogger(SaveSnapshot.class.getName());
private static final Logger LOGGER = Logger.getLogger(SaveSnapshotAsReport.class.getName());
private final TimeLineController controller;
private final WritableImage snapshot;
public SaveSnapshot(TimeLineController controller, WritableImage snapshot) {
super(NbBundle.getMessage(SaveSnapshot.class, "SaveSnapshot.action.name.text"));
this.controller = controller;
this.snapshot = snapshot;
@NbBundle.Messages({"SaveSnapshot.action.name.text=save snapshot",
"SaveSnapshot.fileChoose.title.text=Save snapshot to"})
public SaveSnapshotAsReport(TimeLineController controller, WritableImage snapshot) {
super(Bundle.SaveSnapshot_action_name_text());
setEventHandler(new Consumer<ActionEvent>() {
@Override
public void accept(ActionEvent t) {
//choose location/name
DirectoryChooser fileChooser = new DirectoryChooser();
fileChooser.setTitle(NbBundle.getMessage(this.getClass(), "SaveSnapshot.fileChoose.title.text"));
fileChooser.setTitle(Bundle.SaveSnapshot_fileChoose_title_text());
fileChooser.setInitialDirectory(new File(Case.getCurrentCase().getReportDirectory()));
File outFolder = fileChooser.showDialog(null);
if (outFolder == null) {
File reportDirectory = fileChooser.showDialog(null);
if (reportDirectory == null) {
return;
}
outFolder.mkdir();
String name = outFolder.getName();
reportDirectory.mkdir();
String reportName = reportDirectory.getName();
String reportPath = reportDirectory.getPath();
//gather metadata
List<Pair<String, String>> reportMetaData = new ArrayList<>();
reportMetaData.add(new Pair<>("Case", Case.getCurrentCase().getName())); // NON-NLS
ZoomParams get = controller.getEventsModel().zoomParametersProperty().get();
reportMetaData.add(new Pair<>("Time Range", get.getTimeRange().toString())); // NON-NLS
reportMetaData.add(new Pair<>("Description Level of Detail", get.getDescriptionLOD().getDisplayName())); // NON-NLS
reportMetaData.add(new Pair<>("Event Type Zoom Level", get.getTypeZoomLevel().getDisplayName())); // NON-NLS
reportMetaData.add(new Pair<>("Filters", get.getFilter().getHTMLReportString())); // NON-NLS
ZoomParams zoomParams = controller.getEventsModel().zoomParametersProperty().get();
reportMetaData.add(new Pair<>("Time Range", zoomParams.getTimeRange().toString())); // NON-NLS
reportMetaData.add(new Pair<>("Description Level of Detail", zoomParams.getDescriptionLOD().getDisplayName())); // NON-NLS
reportMetaData.add(new Pair<>("Event Type Zoom Level", zoomParams.getTypeZoomLevel().getDisplayName())); // NON-NLS
reportMetaData.add(new Pair<>("Filters", zoomParams.getFilter().getHTMLReportString())); // NON-NLS
//save snapshot as png
try {
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png", new File(outFolder.getPath() + File.separator + outFolder.getName() + REPORT_IMAGE_EXTENSION)); // NON-NLS
ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png",
new File(reportPath, reportName + REPORT_IMAGE_EXTENSION)); // NON-NLS
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "failed to write snapshot to disk", ex); // NON-NLS
return;
@ -99,17 +96,18 @@ public class SaveSnapshot extends Action {
//build html string
StringBuilder wrapper = new StringBuilder();
wrapper.append("<html>\n<head>\n\t<title>").append("timeline snapshot").append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n</head>\n<body>\n"); // NON-NLS
wrapper.append("<div id=\"content\">\n<h1>").append(outFolder.getName()).append("</h1>\n"); // NON-NLS
wrapper.append("<img src = \"").append(outFolder.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
wrapper.append("<div id=\"content\">\n<h1>").append(reportDirectory.getName()).append("</h1>\n"); // NON-NLS
wrapper.append("<img src = \"").append(reportDirectory.getName()).append(REPORT_IMAGE_EXTENSION + "\" alt = \"snaphot\">"); // NON-NLS
wrapper.append("<table>\n"); // NON-NLS
for (Pair<String, String> pair : reportMetaData) {
wrapper.append("<tr><td>").append(pair.getKey()).append(": </td><td>").append(pair.getValue()).append("</td></tr>\n"); // NON-NLS
}
wrapper.append("</table>\n"); // NON-NLS
wrapper.append("</div>\n</body>\n</html>"); // NON-NLS
File reportHTMLFIle = new File(reportDirectory, reportName + HTML_EXT);
//write html wrapper
try (Writer htmlWriter = new FileWriter(new File(outFolder, name + HTML_EXT))) {
try (Writer htmlWriter = new FileWriter(reportHTMLFIle)) {
htmlWriter.write(wrapper.toString());
} catch (FileNotFoundException ex) {
LOGGER.log(Level.WARNING, "failed to open html wrapper file for writing ", ex); // NON-NLS
@ -121,14 +119,14 @@ public class SaveSnapshot extends Action {
//copy css
try (InputStream resource = this.getClass().getResourceAsStream("/org/sleuthkit/autopsy/timeline/index.css")) { // NON-NLS
Files.copy(resource, Paths.get(outFolder.getPath(), "index.css")); // NON-NLS
Files.copy(resource, Paths.get(reportPath, "index.css")); // NON-NLS
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "failed to copy css file", ex); // NON-NLS
}
//add html file as report to case
try {
Case.getCurrentCase().addReport(outFolder.getPath() + File.separator + outFolder.getName() + HTML_EXT, "Timeline", outFolder.getName() + HTML_EXT); // NON-NLS
Case.getCurrentCase().addReport(reportHTMLFIle.getPath(), "Timeline", reportName + HTML_EXT); // NON-NLS
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "failed add html wrapper as a report", ex); // NON-NLS
}

View File

@ -0,0 +1,44 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015 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.timeline.actions;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import org.controlsfx.control.action.Action;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
/**
*
*/
public class ZoomIn extends Action {
private static final Image MAGNIFIER_IN = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-in-green.png"); //NOI18N
@NbBundle.Messages({"ZoomIn.longText=Zoom in to view half as much time.",
"ZoomIn.action.text=Zoom in"})
public ZoomIn(TimeLineController controller) {
super(Bundle.ZoomIn_action_text());
setLongText(Bundle.ZoomIn_longText());
setGraphic(new ImageView(MAGNIFIER_IN));
setEventHandler(actionEvent -> {
controller.pushZoomInTime();
});
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -19,7 +19,8 @@
package org.sleuthkit.autopsy.timeline.actions;
import javafx.beans.binding.BooleanBinding;
import javafx.event.ActionEvent;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import org.controlsfx.control.action.Action;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
@ -30,15 +31,22 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
*/
public class ZoomOut extends Action {
private final TimeLineController controller;
private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png"); //NOI18N
private final FilteredEventsModel eventsModel;
@NbBundle.Messages({"ZoomOut.longText=Zoom out to view 50% more time.",
"ZoomOut.action.text=Zoom out"})
public ZoomOut(TimeLineController controller) {
super(Bundle.ZoomOut_action_text());
setLongText(Bundle.ZoomOut_longText());
setGraphic(new ImageView(MAGNIFIER_OUT));
setEventHandler(actionEvent -> {
controller.pushZoomOutTime();
});
public ZoomOut(final TimeLineController controller) {
super(NbBundle.getMessage(ZoomOut.class, "ZoomOut.action.name.text"));
this.controller = controller;
eventsModel = controller.getEventsModel();
//disable action when the current time range already encompases the entire case.
disabledProperty().bind(new BooleanBinding() {
private final FilteredEventsModel eventsModel = controller.getEventsModel();
{
bind(eventsModel.zoomParametersProperty());
}
@ -48,8 +56,5 @@ public class ZoomOut extends Action {
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
}
});
setEventHandler((ActionEvent t) -> {
controller.zoomOutToActivity();
});
}
}

View File

@ -0,0 +1,61 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014-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.timeline.actions;
import javafx.beans.binding.BooleanBinding;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import org.controlsfx.control.action.Action;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
/**
*
*/
public class ZoomToEvents extends Action {
private static final Image MAGNIFIER_OUT = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-out-red.png", 16, 16, true, true); //NOI18N
@NbBundle.Messages({"ZoomToEvents.action.text=Zoom to events",
"ZoomToEvents.longText=Zoom out to show the nearest events."})
public ZoomToEvents(final TimeLineController controller) {
super(Bundle.ZoomToEvents_action_text());
setLongText(Bundle.ZoomToEvents_longText());
setGraphic(new ImageView(MAGNIFIER_OUT));
setEventHandler(actionEvent -> {
controller.zoomOutToActivity();
});
//disable action when the current time range already encompases the entire case.
disabledProperty().bind(new BooleanBinding() {
private final FilteredEventsModel eventsModel = controller.getEventsModel();
{
bind(eventsModel.zoomParametersProperty());
}
@Override
protected boolean computeValue() {
//TODO: do a db query to see if using this action will actually result in viewable events
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
}
});
}
}

View File

@ -41,7 +41,6 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
import org.sleuthkit.autopsy.timeline.db.EventsRepository;

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

View File

@ -40,6 +40,7 @@ import javafx.scene.chart.Chart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.control.OverrunStyle;
import javafx.scene.control.Tooltip;
import javafx.scene.effect.Effect;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
@ -50,12 +51,11 @@ import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
@ -73,8 +73,15 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
* {@link XYChart} doing the rendering. Is this a good idea? -jm TODO: pull up
* common history context menu items out of derived classes? -jm
*/
public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & TimeLineChart<X>> extends BorderPane implements TimeLineView {
public abstract class AbstractVisualizationPane<X, Y, N, C extends XYChart<X, Y> & TimeLineChart<X>> extends BorderPane {
@NbBundle.Messages("AbstractVisualization.Drag_Tooltip.text=Drag the mouse to select a time interval to zoom into.")
private static final Tooltip DRAG_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Drag_Tooltip_text());
private static final Logger LOGGER = Logger.getLogger(AbstractVisualizationPane.class.getName());
public static Tooltip getDragTooltip() {
return DRAG_TOOLTIP;
}
protected final SimpleBooleanProperty hasEvents = new SimpleBooleanProperty(true);
protected final ObservableList<BarChart.Series<X, Y>> dataSets = FXCollections.<BarChart.Series<X, Y>>observableArrayList();
@ -93,11 +100,15 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
*/
private Task<Boolean> updateTask;
protected TimeLineController controller;
final protected TimeLineController controller;
protected FilteredEventsModel filteredEvents;
final protected FilteredEventsModel filteredEvents;
protected ReadOnlyListWrapper<N> selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
final protected ReadOnlyListWrapper<N> selectedNodes = new ReadOnlyListWrapper<>(FXCollections.observableArrayList());
private InvalidationListener invalidationListener = (Observable observable) -> {
update();
};
public ReadOnlyListProperty<N> getSelectedNodes() {
return selectedNodes.getReadOnlyProperty();
@ -177,7 +188,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
* Primarily this invokes the background {@link Task} returned by
* {@link #getUpdateTask()} which derived classes must implement.
*/
synchronized public void update() {
final synchronized public void update() {
if (updateTask != null) {
updateTask.cancel(true);
updateTask = null;
@ -195,7 +206,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
try {
this.hasEvents.set(updateTask.get());
} catch (InterruptedException | ExecutionException ex) {
Logger.getLogger(AbstractVisualization.class.getName()).log(Level.SEVERE, "Unexpected exception updating visualization", ex);
LOGGER.log(Level.SEVERE, "Unexpected exception updating visualization", ex); //NOI18N
}
break;
}
@ -203,7 +214,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
controller.monitorTask(updateTask);
}
synchronized public void dispose() {
final synchronized public void dispose() {
if (updateTask != null) {
updateTask.cancel(true);
}
@ -211,7 +222,12 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
invalidationListener = null;
}
protected AbstractVisualization(Pane partPane, Pane contextPane, Region spacer) {
protected AbstractVisualizationPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
this.controller = controller;
this.filteredEvents = controller.getEventsModel();
this.filteredEvents.registerForEvents(this);
this.filteredEvents.zoomParametersProperty().addListener(invalidationListener);
this.leafPane = partPane;
this.branchPane = contextPane;
this.spacer = spacer;
@ -226,32 +242,18 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
});
}
});
}
@Override
synchronized public void setController(TimeLineController controller) {
this.controller = controller;
chart.setController(controller);
setModel(controller.getEventsModel());
TimeLineController.getTimeZone().addListener((Observable observable) -> {
update();
TimeLineController.getTimeZone().addListener(invalidationListener);
//show tooltip text in status bar
hoverProperty().addListener((observable, oldActivated, newActivated) -> {
if (newActivated) {
controller.setStatus(DRAG_TOOLTIP.getText());
} else {
controller.setStatus("");
}
});
}
@Override
synchronized public void setModel(@Nonnull FilteredEventsModel filteredEvents) {
if (this.filteredEvents != null && this.filteredEvents != filteredEvents) {
this.filteredEvents.unRegisterForEvents(this);
this.filteredEvents.zoomParametersProperty().removeListener(invalidationListener);
}
if (this.filteredEvents != filteredEvents) {
filteredEvents.registerForEvents(this);
filteredEvents.zoomParametersProperty().addListener(invalidationListener);
}
this.filteredEvents = filteredEvents;
update();
}
@ -260,10 +262,6 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
update();
}
protected InvalidationListener invalidationListener = (Observable observable) -> {
update();
};
/**
* iterate through the list of tick-marks building a two level structure of
* replacement tick marl labels. (Visually) upper level has most
@ -370,7 +368,7 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
*/
private synchronized void assignLeafLabel(String labelText, double labelWidth, double labelX, boolean bold) {
Text label = new Text(" " + labelText + " ");
Text label = new Text(" " + labelText + " "); //NOI18N
label.setTextAlignment(TextAlignment.CENTER);
label.setFont(Font.font(null, bold ? FontWeight.BOLD : FontWeight.NORMAL, 10));
//position label accounting for width
@ -414,9 +412,9 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
label.relocate(labelX, 0);
if (labelX == 0) { // first label has no border
label.setStyle("-fx-border-width: 0 0 0 0 ; -fx-border-color:black;"); // NON-NLS
label.setStyle("-fx-border-width: 0 0 0 0 ; -fx-border-color:black;"); // NON-NLS //NOI18N
} else { // subsequent labels have border on left to create dividers
label.setStyle("-fx-border-width: 0 0 0 1; -fx-border-color:black;"); // NON-NLS
label.setStyle("-fx-border-width: 0 0 0 1; -fx-border-color:black;"); // NON-NLS //NOI18N
}
branchPane.getChildren().add(label);
@ -446,10 +444,10 @@ public abstract class AbstractVisualization<X, Y, N, C extends XYChart<X, Y> & T
TwoPartDateTime(String dateString) {
//find index of separator to spit on
int splitIndex = StringUtils.lastIndexOfAny(dateString, " ", "-", ":");
int splitIndex = StringUtils.lastIndexOfAny(dateString, " ", "-", ":"); //NOI18N
if (splitIndex < 0) { // there is only one part
leaf = dateString;
branch = "";
branch = ""; //NOI18N
} else { //split at index
leaf = StringUtils.substring(dateString, splitIndex + 1);
branch = StringUtils.substring(dateString, 0, splitIndex);

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -35,7 +35,6 @@ Timeline.ui.ZoomRanges.threeyears.text=Three Years
Timeline.ui.ZoomRanges.fiveyears.text=Five Years
Timeline.ui.ZoomRanges.tenyears.text=Ten Years
Timeline.ui.ZoomRanges.all.text=All
Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range\:\n{0} to {1}\nRight-click to clear.
TimeLineResultView.startDateToEndDate.text={0} to {1}
VisualizationPanel.histogramTask.title=Rebuild Histogram
VisualizationPanel.histogramTask.preparing=preparing
@ -56,4 +55,4 @@ VisualizationPanel.zoomMenuButton.text=Zoom in/out to
VisualizationPanel.snapShotButton.text=Screenshot
VisualizationPanel.detailsToggle.text=Details
VisualizationPanel.countsToggle.text=Counts
VisualizationPanel.resetFiltersButton.text=Reset all filters
VisualizationPanel.resetFiltersButton.text=Reset all filters

View File

@ -0,0 +1,16 @@
.intervalSelector{
-fx-background-color: rgba(0,0,255,.25);
-fx-border-color: rgba(0,0,255,.25);
-fx-border-width: 0 3 0 3;
}
.closeButton{
}
.closeButton:hover{
-fx-opacity: 1;
}
.zoomButton:hover{
-fx-opacity: 1;
}

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" styleClass="intervalSelector" stylesheets="@IntervalSelector.css" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
<top>
<Button fx:id="closeButton" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" opacity="0.5" prefHeight="16.0" prefWidth="16.0" styleClass="closeButton" BorderPane.alignment="CENTER_RIGHT">
<graphic>
<ImageView opacity="0.5" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/cross-script.png" />
</image>
</ImageView>
</graphic>
</Button>
</top>
<padding>
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
</padding>
<bottom>
<BorderPane fx:id="bottomBorder" minHeight="32.0" BorderPane.alignment="BOTTOM_LEFT">
<left>
<HBox alignment="BOTTOM_LEFT" BorderPane.alignment="BOTTOM_LEFT">
<children>
<Label fx:id="startLabel" alignment="BOTTOM_LEFT" minWidth="0.0" text="Label" />
<Region HBox.hgrow="ALWAYS" />
</children>
</HBox>
</left>
<right>
<HBox alignment="BOTTOM_RIGHT" BorderPane.alignment="CENTER">
<children>
<Region HBox.hgrow="ALWAYS" />
<Label fx:id="endLabel" minWidth="0.0" text="Label" />
</children>
</HBox>
</right>
<center>
<Button fx:id="zoomButton" alignment="CENTER" mnemonicParsing="false" opacity="0.66" styleClass="zoomButton" text="Zoom" BorderPane.alignment="CENTER">
<graphic>
<ImageView pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/magnifier-zoom-fit.png" />
</image>
</ImageView>
</graphic>
</Button>
</center>
</BorderPane>
</bottom>
</fx:root>

View File

@ -0,0 +1,316 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014-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.timeline.ui;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionUtils;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
/**
* Visually represents a 'selected' time range, and allows mouse interactions
* with it.
*
* @param <X> the type of values along the x axis this is a selector for
*
* This abstract class requires concrete implementations to implement template
* methods to handle formating and date 'lookup' of the generic x-axis type
*/
public abstract class IntervalSelector<X> extends BorderPane {
private static final Image ClEAR_INTERVAL_ICON = new Image("/org/sleuthkit/autopsy/timeline/images/cross-script.png", 16, 16, true, true, true);
private static final Image ZOOM_TO_INTERVAL_ICON = new Image("/org/sleuthkit/autopsy/timeline/images/magnifier-zoom-fit.png", 16, 16, true, true, true);
private static final double STROKE_WIDTH = 3;
private static final double HALF_STROKE = STROKE_WIDTH / 2;
/**
* the Axis this is a selector over
*/
public final TimeLineChart<X> chart;
private Tooltip tooltip;
/////////drag state
private DragPosition dragPosition;
private double startLeft;
private double startDragX;
private double startWidth;
private final BooleanProperty isDragging = new SimpleBooleanProperty(false);
/////////end drag state
private final TimeLineController controller;
@FXML
private Label startLabel;
@FXML
private Label endLabel;
@FXML
private Button closeButton;
@FXML
private Button zoomButton;
@FXML
private BorderPane bottomBorder;
public IntervalSelector(TimeLineChart<X> chart) {
this.chart = chart;
this.controller = chart.getController();
FXMLConstructor.construct(this, IntervalSelector.class, "IntervalSelector.fxml"); // NON-NLS
}
@FXML
void initialize() {
assert startLabel != null : "fx:id=\"startLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
assert endLabel != null : "fx:id=\"endLabel\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
assert closeButton != null : "fx:id=\"closeButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'IntervalSelector.fxml'.";
setMaxHeight(USE_PREF_SIZE);
setMinHeight(USE_PREF_SIZE);
setMaxWidth(USE_PREF_SIZE);
setMinWidth(USE_PREF_SIZE);
BooleanBinding showingControls = hoverProperty().and(isDragging.not());
closeButton.visibleProperty().bind(showingControls);
closeButton.managedProperty().bind(showingControls);
zoomButton.visibleProperty().bind(showingControls);
zoomButton.managedProperty().bind(showingControls);
widthProperty().addListener(o -> {
IntervalSelector.this.updateStartAndEnd();
if (startLabel.getWidth() + zoomButton.getWidth() + endLabel.getWidth() > getWidth()) {
this.setCenter(zoomButton);
} else {
bottomBorder.setCenter(zoomButton);
}
BorderPane.setAlignment(zoomButton, Pos.BOTTOM_CENTER);
});
layoutXProperty().addListener(o -> this.updateStartAndEnd());
updateStartAndEnd();
setOnMouseMoved(mouseMove -> {
Point2D parentMouse = getLocalMouseCoords(mouseMove);
final double diffX = getLayoutX() - parentMouse.getX();
if (Math.abs(diffX) <= HALF_STROKE) {
setCursor(Cursor.W_RESIZE);
} else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
setCursor(Cursor.E_RESIZE);
} else {
setCursor(Cursor.HAND);
}
mouseMove.consume();
});
setOnMousePressed(mousePress -> {
Point2D parentMouse = getLocalMouseCoords(mousePress);
final double diffX = getLayoutX() - parentMouse.getX();
startDragX = mousePress.getScreenX();
startWidth = getWidth();
startLeft = getLayoutX();
if (Math.abs(diffX) <= HALF_STROKE) {
dragPosition = IntervalSelector.DragPosition.LEFT;
} else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
dragPosition = IntervalSelector.DragPosition.RIGHT;
} else {
dragPosition = IntervalSelector.DragPosition.CENTER;
}
mousePress.consume();
});
setOnMouseReleased(mouseRelease -> isDragging.set(false));
setOnMouseDragged(mouseDrag -> {
isDragging.set(true);
double dX = mouseDrag.getScreenX() - startDragX;
switch (dragPosition) {
case CENTER:
setLayoutX(startLeft + dX);
break;
case LEFT:
if (dX > startWidth) {
startDragX = mouseDrag.getScreenX();
startWidth = 0;
dragPosition = DragPosition.RIGHT;
} else {
setLayoutX(startLeft + dX);
setPrefWidth(startWidth - dX);
autosize();
}
break;
case RIGHT:
Point2D parentMouse = getLocalMouseCoords(mouseDrag);
if (parentMouse.getX() < startLeft) {
dragPosition = DragPosition.LEFT;
startDragX = mouseDrag.getScreenX();
startWidth = 0;
} else {
setPrefWidth(startWidth + dX);
autosize();
}
break;
}
mouseDrag.consume();
});
ActionUtils.configureButton(new ZoomToSelectedIntervalAction(), zoomButton);
ActionUtils.configureButton(new ClearSelectedIntervalAction(), closeButton);
//have to add handler rather than use convenience methods so that charts can listen for dismisal click
setOnMouseClicked(mosueClick -> {
if (mosueClick.getButton() == MouseButton.SECONDARY) {
chart.clearIntervalSelector();
mosueClick.consume();
}
if (mosueClick.getClickCount() >= 2) {
zoomToSelectedInterval();
mosueClick.consume();
}
});
}
private Point2D getLocalMouseCoords(MouseEvent mouseEvent) {
return getParent().sceneToLocal(new Point2D(mouseEvent.getSceneX(), mouseEvent.getSceneY()));
}
private void zoomToSelectedInterval() {
//convert to DateTimes, using max/min if null(off axis)
DateTime start = parseDateTime(getSpanStart());
DateTime end = parseDateTime(getSpanEnd());
Interval i = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start));
controller.pushTimeRange(i);
}
/**
*
* @param i the interval represented by this selector
*
* @return a modified version of {@code i} adjusted to suite the needs of
* the concrete implementation
*/
protected abstract Interval adjustInterval(Interval i);
/**
* format a string representation of the given x-axis value to use in the
* tooltip
*
* @param date a x-axis value of type X
*
* @return a string representation of the given x-axis value
*/
protected abstract String formatSpan(final X date);
/**
* parse an x-axis value to a {@link DateTime}
*
* @param date a x-axis value of type X
*
* @return a {@link DateTime} corresponding to the given x-axis value
*/
protected abstract DateTime parseDateTime(X date);
@NbBundle.Messages(value = {"# {0} - start timestamp",
"# {1} - end timestamp",
"Timeline.ui.TimeLineChart.tooltip.text=Double-click to zoom into range:\n{0} to {1}\nRight-click to clear."})
private void updateStartAndEnd() {
String startString = formatSpan(getSpanStart());
String endString = formatSpan(getSpanEnd());
startLabel.setText(startString);
endLabel.setText(endString);
Tooltip.uninstall(this, tooltip);
tooltip = new Tooltip(Bundle.Timeline_ui_TimeLineChart_tooltip_text(startString, endString));
Tooltip.install(this, tooltip);
}
/**
* @return the value along the x-axis corresponding to the left edge of the
* selector
*/
public X getSpanEnd() {
return getValueForDisplay(getBoundsInParent().getMaxX());
}
/**
* @return the value along the x-axis corresponding to the right edge of the
* selector
*/
public X getSpanStart() {
return getValueForDisplay(getBoundsInParent().getMinX());
}
private X getValueForDisplay(final double display) {
return chart.getXAxis().getValueForDisplay(chart.getXAxis().parentToLocal(display, 0).getX());
}
/**
* enum to represent whether the drag is a left/right-edge modification or a
* horizontal slide triggered by dragging the center
*/
private enum DragPosition {
LEFT,
CENTER,
RIGHT
}
private class ZoomToSelectedIntervalAction extends Action {
@NbBundle.Messages("IntervalSelector.ZoomAction.name=Zoom")
ZoomToSelectedIntervalAction() {
super(Bundle.IntervalSelector_ZoomAction_name());
setGraphic(new ImageView(ZOOM_TO_INTERVAL_ICON));
setEventHandler((ActionEvent t) -> {
zoomToSelectedInterval();
});
}
}
private class ClearSelectedIntervalAction extends Action {
@NbBundle.Messages("IntervalSelector.ClearSelectedIntervalAction.tooltTipText=Clear Selected Interval")
ClearSelectedIntervalAction() {
super("");
setLongText(Bundle.IntervalSelector_ClearSelectedIntervalAction_tooltTipText());
setGraphic(new ImageView(ClEAR_INTERVAL_ICON));
setEventHandler((ActionEvent t) -> {
chart.clearIntervalSelector();
});
}
}
}

View File

@ -5,7 +5,7 @@
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
<items>
<Label fx:id="refreshLabel">
<graphic>
@ -17,6 +17,15 @@
</graphic>
</Label>
<Separator orientation="VERTICAL" />
<Label fx:id="statusLabel" layoutX="10.0" layoutY="11.0">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/information-gray.png" />
</image>
</ImageView>
</graphic>
</Label>
<Region fx:id="spacer" maxWidth="1.7976931348623157E308" />
<Separator orientation="VERTICAL" />
<Label fx:id="taskLabel" contentDisplay="RIGHT">

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2014-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -28,19 +28,21 @@ import javafx.scene.layout.Region;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineUI;
/**
* simple status bar that only shows one possible message determined by
* {@link TimeLineController#newEventsFlag}
*/
public class StatusBar extends ToolBar implements TimeLineUI {
public class StatusBar extends ToolBar {
private TimeLineController controller;
@FXML
private Label refreshLabel;
@FXML
private Label statusLabel;
@FXML
private ProgressBar progressBar;
@ -53,7 +55,8 @@ public class StatusBar extends ToolBar implements TimeLineUI {
@FXML
private Label messageLabel;
public StatusBar() {
public StatusBar(TimeLineController controller) {
this.controller = controller;
FXMLConstructor.construct(this, "StatusBar.fxml"); // NON-NLS
}
@ -70,15 +73,16 @@ public class StatusBar extends ToolBar implements TimeLineUI {
taskLabel.setText(NbBundle.getMessage(this.getClass(), "StatusBar.taskLabel.text"));
taskLabel.setVisible(false);
HBox.setHgrow(spacer, Priority.ALWAYS);
}
@Override
public void setController(TimeLineController controller) {
this.controller = controller;
refreshLabel.visibleProperty().bind(this.controller.getNewEventsFlag());
refreshLabel.managedProperty().bind(this.controller.getNewEventsFlag());
taskLabel.textProperty().bind(this.controller.getTaskTitle());
messageLabel.textProperty().bind(this.controller.getMessage());
progressBar.progressProperty().bind(this.controller.getProgress());
taskLabel.visibleProperty().bind(this.controller.getTasks().emptyProperty().not());
statusLabel.textProperty().bind(this.controller.getStatusProperty());
statusLabel.visibleProperty().bind(statusLabel.textProperty().isNotEmpty());
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2014-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -19,28 +19,27 @@
package org.sleuthkit.autopsy.timeline.ui;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.event.EventType;
import javafx.scene.Cursor;
import javafx.scene.chart.Axis;
import javafx.scene.chart.Chart;
import javafx.scene.control.Tooltip;
import javafx.scene.control.ContextMenu;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.controlsfx.control.action.ActionGroup;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
/**
* Interface for TimeLineViews that are 'charts'.
*
* @param <X> the type of values along the horizontal axis
*/
public interface TimeLineChart<X> extends TimeLineView {
public interface TimeLineChart<X> {
// void setController(TimeLineController controller);
IntervalSelector<? extends X> getIntervalSelector();
void setIntervalSelector(IntervalSelector<? extends X> newIntervalSelector);
@ -49,12 +48,9 @@ public interface TimeLineChart<X> extends TimeLineView {
* derived classes should implement this so as to supply an appropriate
* subclass of {@link IntervalSelector}
*
* @param x the initial x position of the new interval selector
* @param axis the axis the new interval selector will be over
*
* @return a new interval selector
*/
IntervalSelector<X> newIntervalSelector(double x, Axis<X> axis);
IntervalSelector<X> newIntervalSelector();
/**
* clear any references to previous interval selectors , including removing
@ -62,6 +58,14 @@ public interface TimeLineChart<X> extends TimeLineView {
*/
void clearIntervalSelector();
public Axis<X> getXAxis();
public TimeLineController getController();
ContextMenu getChartContextMenu();
ContextMenu getChartContextMenu(MouseEvent m);
/**
* drag handler class used by {@link TimeLineChart}s to create
* {@link IntervalSelector}s
@ -69,228 +73,87 @@ public interface TimeLineChart<X> extends TimeLineView {
* @param <X> the type of values along the horizontal axis
* @param <Y> the type of chart this is a drag handler for
*/
class ChartDragHandler<X, Y extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
static class ChartDragHandler<X, Y extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
private final Y chart;
private final Axis<X> dateAxis;
private double startX; //hanlder mainstains position of drag start
public ChartDragHandler(Y chart, Axis<X> dateAxis) {
public ChartDragHandler(Y chart) {
this.chart = chart;
this.dateAxis = dateAxis;
}
@Override
public void handle(MouseEvent t) {
if (t.getButton() == MouseButton.SECONDARY) {
if (t.getEventType() == MouseEvent.MOUSE_PRESSED) {
//caputure x-position, incase we are repositioning existing selector
startX = t.getX();
chart.setCursor(Cursor.E_RESIZE);
} else if (t.getEventType() == MouseEvent.MOUSE_DRAGGED) {
if (chart.getIntervalSelector() == null) {
//make new interval selector
chart.setIntervalSelector(chart.newIntervalSelector(t.getX(), dateAxis));
chart.getIntervalSelector().heightProperty().bind(chart.heightProperty().subtract(dateAxis.heightProperty().subtract(dateAxis.tickLengthProperty())));
chart.getIntervalSelector().addEventHandler(MouseEvent.MOUSE_CLICKED, (MouseEvent event) -> {
if (event.getButton() == MouseButton.SECONDARY) {
chart.clearIntervalSelector();
event.consume();
}
});
startX = t.getX();
public void handle(MouseEvent mouseEvent) {
EventType<? extends MouseEvent> mouseEventType = mouseEvent.getEventType();
if (mouseEventType == MouseEvent.MOUSE_PRESSED) {
//caputure x-position, incase we are repositioning existing selector
startX = mouseEvent.getX();
chart.setCursor(Cursor.H_RESIZE);
} else if (mouseEventType == MouseEvent.MOUSE_DRAGGED) {
if (chart.getIntervalSelector() == null) {
//make new interval selector
chart.setIntervalSelector(chart.newIntervalSelector());
chart.getIntervalSelector().prefHeightProperty().bind(chart.heightProperty());
startX = mouseEvent.getX();
chart.getIntervalSelector().relocate(startX, 0);
} else {
//resize/position existing selector
if (mouseEvent.getX() > startX) {
chart.getIntervalSelector().relocate(startX, 0);
chart.getIntervalSelector().setPrefWidth(mouseEvent.getX() - startX);
} else {
//resize/position existing selector
if (t.getX() > startX) {
chart.getIntervalSelector().setX(startX);
chart.getIntervalSelector().setWidth(t.getX() - startX);
} else {
chart.getIntervalSelector().setX(t.getX());
chart.getIntervalSelector().setWidth(startX - t.getX());
}
chart.getIntervalSelector().relocate(mouseEvent.getX(), 0);
chart.getIntervalSelector().setPrefWidth(startX - mouseEvent.getX());
}
} else if (t.getEventType() == MouseEvent.MOUSE_RELEASED) {
chart.setCursor(Cursor.DEFAULT);
}
t.consume();
chart.getIntervalSelector().autosize();
} else if (mouseEventType == MouseEvent.MOUSE_RELEASED) {
chart.setCursor(Cursor.DEFAULT);
} else if (mouseEventType == MouseEvent.MOUSE_CLICKED) {
chart.setCursor(Cursor.DEFAULT);
}
}
}
static class MouseClickedHandler<X, C extends Chart & TimeLineChart<X>> implements EventHandler<MouseEvent> {
private final C chart;
public MouseClickedHandler(C chart) {
this.chart = chart;
}
@Override
public void handle(MouseEvent clickEvent) {
if (chart.getChartContextMenu() != null) {
chart.getChartContextMenu().hide();
}
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
chart.getChartContextMenu(clickEvent);
chart.setOnMouseMoved(this);
chart.getChartContextMenu().show(chart, clickEvent.getScreenX(), clickEvent.getScreenY());
clickEvent.consume();
}
}
}
/**
* Visually represents a 'selected' time range, and allows mouse
* interactions with it.
*
* @param <X> the type of values along the x axis this is a selector for
*
* This abstract class requires concrete implementations to implement hook
* methods to handle formating and date 'lookup' of the generic x-axis type
* enum to represent whether the drag is a left/right-edge modification or a
* horizontal slide triggered by dragging the center
*/
static abstract class IntervalSelector<X> extends Rectangle {
enum DragPosition {
private static final double STROKE_WIDTH = 3;
LEFT,
CENTER,
RIGHT
}
private static final double HALF_STROKE = STROKE_WIDTH / 2;
/**
* the Axis this is a selector over
*/
private final Axis<X> dateAxis;
protected Tooltip tooltip;
/////////drag state
private DragPosition dragPosition;
private double startLeft;
private double startX;
private double startWidth;
/////////end drag state
/**
*
* @param x the initial x position of this selector
* @param height the initial height of this selector
* @param axis the {@link Axis<X>} this is a selector over
* @param controller the controller to invoke when this selector is
* double clicked
*/
public IntervalSelector(double x, double height, Axis<X> axis, TimeLineController controller) {
super(x, 0, x, height);
dateAxis = axis;
setStroke(Color.BLUE);
setStrokeWidth(STROKE_WIDTH);
setFill(Color.BLUE.deriveColor(0, 1, 1, 0.5));
setOpacity(0.5);
widthProperty().addListener(o -> {
setTooltip();
});
xProperty().addListener(o -> {
setTooltip();
});
setTooltip();
setOnMouseMoved((MouseEvent event) -> {
Point2D localMouse = sceneToLocal(new Point2D(event.getSceneX(), event.getSceneY()));
final double diffX = getX() - localMouse.getX();
if (Math.abs(diffX) <= HALF_STROKE || Math.abs(diffX + getWidth()) <= HALF_STROKE) {
setCursor(Cursor.E_RESIZE);
} else {
setCursor(Cursor.HAND);
}
});
setOnMousePressed((MouseEvent event) -> {
Point2D localMouse = sceneToLocal(new Point2D(event.getSceneX(), event.getSceneY()));
final double diffX = getX() - localMouse.getX();
startX = event.getX();
startWidth = getWidth();
startLeft = getX();
if (Math.abs(diffX) <= HALF_STROKE) {
dragPosition = IntervalSelector.DragPosition.LEFT;
} else if (Math.abs(diffX + getWidth()) <= HALF_STROKE) {
dragPosition = IntervalSelector.DragPosition.RIGHT;
} else {
dragPosition = IntervalSelector.DragPosition.CENTER;
}
});
setOnMouseDragged((MouseEvent event) -> {
double dX = event.getX() - startX;
switch (dragPosition) {
case CENTER:
setX(startLeft + dX);
break;
case LEFT:
setX(startLeft + dX);
setWidth(startWidth - dX);
break;
case RIGHT:
setWidth(startWidth + dX);
break;
}
});
//have to add handler rather than use convenience methods so that charts can listen for dismisal click
addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
if (event.getClickCount() >= 2) {
//convert to DateTimes, using max/min if null(off axis)
DateTime start = parseDateTime(getSpanStart());
DateTime end = parseDateTime(getSpanEnd());
Interval i = adjustInterval(start.isBefore(end) ? new Interval(start, end) : new Interval(end, start));
controller.pushTimeRange(i);
}
}
});
}
/**
*
* @param i the interval represented by this selector
*
* @return a modified version of {@code i} adjusted to suite the needs
* of the concrete implementation
*/
protected abstract Interval adjustInterval(Interval i);
/**
* format a string representation of the given x-axis value to use in
* the tooltip
*
* @param date a x-axis value of type X
*
* @return a string representation of the given x-axis value
*/
protected abstract String formatSpan(final X date);
/**
* parse an x-axis value to a {@link DateTime}
*
* @param date a x-axis value of type X
*
* @return a {@link DateTime} corresponding to the given x-axis value
*/
protected abstract DateTime parseDateTime(X date);
private void setTooltip() {
final X start = getSpanStart();
final X end = getSpanEnd();
Tooltip.uninstall(this, tooltip);
tooltip = new Tooltip(
NbBundle.getMessage(TimeLineChart.class, "Timeline.ui.TimeLineChart.tooltip.text", formatSpan(start),
formatSpan(end)));
Tooltip.install(this, tooltip);
}
/**
* @return the value along the x-axis corresponding to the left edge of
* the selector
*/
public X getSpanEnd() {
return dateAxis.getValueForDisplay(dateAxis.parentToLocal(getBoundsInParent().getMaxX(), 0).getX());
}
/**
* @return the value along the x-axis corresponding to the right edge of
* the selector
*/
public X getSpanStart() {
return dateAxis.getValueForDisplay(dateAxis.parentToLocal(getBoundsInParent().getMinX(), 0).getX());
}
/**
* enum to represent whether the drag is a left/right-edge modification
* or a horizontal slide triggered by dragging the center
*/
private enum DragPosition {
LEFT, CENTER, RIGHT
}
@NbBundle.Messages({"TimeLineChart.zoomHistoryActionGroup.name=Zoom History"})
static ActionGroup newZoomHistoyActionGroup(TimeLineController controller) {
return new ActionGroup(Bundle.TimeLineChart_zoomHistoryActionGroup_name(),
new Back(controller),
new Forward(controller));
}
}

View File

@ -25,12 +25,11 @@ import javax.swing.SwingUtilities;
import org.joda.time.format.DateTimeFormatter;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
/**
* Since it was too hard to derive from {@link DataResultPanel}, this class
@ -39,16 +38,16 @@ import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
* {@link DataResultPanel}. That is, this class acts as a sort of bridge/adapter
* between a FilteredEventsModel instance and a DataResultPanel instance.
*/
public class TimeLineResultView implements TimeLineView {
public class TimeLineResultView {
/**
* the {@link DataResultPanel} that is the real view proxied by this class
*/
private final DataResultPanel dataResultPanel;
private TimeLineController controller;
private final TimeLineController controller;
private FilteredEventsModel filteredEvents;
private final FilteredEventsModel filteredEvents;
private Set<Long> selectedEventIDs = new HashSet<>();
@ -56,19 +55,11 @@ public class TimeLineResultView implements TimeLineView {
return dataResultPanel;
}
public TimeLineResultView(DataContent dataContent) {
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
}
public TimeLineResultView(TimeLineController controller, DataContent dataContent) {
/**
* Set the Controller for this class. Also sets the model provided by the
* controller as the model for this view.
*
* @param controller
*/
@Override
public void setController(TimeLineController controller) {
this.controller = controller;
this.filteredEvents = controller.getEventsModel();
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
//set up listeners on relevant properties
TimeLineController.getTimeZone().addListener((Observable observable) -> {
@ -78,18 +69,7 @@ public class TimeLineResultView implements TimeLineView {
controller.getSelectedEventIDs().addListener((Observable o) -> {
refresh();
});
setModel(controller.getEventsModel());
}
/**
* Set the Model for this View
*
* @param filteredEvents
*/
@Override
synchronized public void setModel(final FilteredEventsModel filteredEvents) {
this.filteredEvents = filteredEvents;
refresh();
}
/**

View File

@ -23,7 +23,10 @@ import java.util.Date;
import java.util.TimeZone;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
@ -50,8 +53,9 @@ public class TimeZonePanel extends TitledPane {
}
@FXML
@NbBundle.Messages({"TimeZonePanel.title=Display Times In:"})
public void initialize() {
setText(Bundle.TimeZonePanel_title());
// localRadio.setText("Local Time Zone: " + getTimeZoneString(TimeZone.getDefault()));
localRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.localRadio.text"));
otherRadio.setText(NbBundle.getMessage(this.getClass(), "TimeZonePanel.otherRadio.text"));

View File

@ -9,7 +9,7 @@
<?import jfxtras.scene.control.*?>
<?import org.controlsfx.control.*?>
<fx:root prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.BorderPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<fx:root prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.BorderPane" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
<top>
<ToolBar fx:id="toolBar" prefWidth="200.0" BorderPane.alignment="CENTER">
<items>
@ -115,7 +115,7 @@
</Separator>
<HBox>
<children>
<Button fx:id="zoomOutButton" mnemonicParsing="false">
<Button fx:id="zoomOutButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
<graphic>
<ImageView pickOnBounds="true" preserveRatio="true">
<image>
@ -127,7 +127,7 @@
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
</HBox.margin>
</Button>
<Button fx:id="zoomInButton" mnemonicParsing="false">
<Button fx:id="zoomInButton" contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false">
<graphic>
<ImageView pickOnBounds="true" preserveRatio="true">
<image>
@ -143,7 +143,7 @@
</HBox>
<MenuButton fx:id="zoomMenuButton" mnemonicParsing="false">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<ImageView pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/magnifier-left.png" />
</image>

View File

@ -33,8 +33,6 @@ import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
@ -48,7 +46,6 @@ import javafx.scene.control.Tooltip;
import javafx.scene.effect.Lighting;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
@ -66,6 +63,7 @@ import jfxtras.scene.control.LocalDateTimeTextField;
import org.controlsfx.control.NotificationPane;
import org.controlsfx.control.RangeSlider;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Interval;
@ -74,11 +72,12 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshot;
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport;
import org.sleuthkit.autopsy.timeline.actions.ZoomIn;
import org.sleuthkit.autopsy.timeline.actions.ZoomOut;
import org.sleuthkit.autopsy.timeline.actions.ZoomToEvents;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
@ -86,18 +85,18 @@ import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_refres
import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_tagsAddedOrDeleted;
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavPanel;
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
/**
* A Container for an {@link AbstractVisualization}, has a toolbar on top to
* hold settings widgets supplied by contained {@link AbstractVisualization},
* A container for an {@link AbstractVisualizationPane}, has a toolbar on top to
* hold settings widgets supplied by contained {@link AbstAbstractVisualization}
* and the histogram / timeselection on bottom. Also supplies containers for
* replacement axis to contained {@link AbstractVisualization}
* replacement axis to contained {@link AbstractAbstractVisualization}
*
* TODO: refactor common code out of histogram and CountsView? -jm
*/
public class VisualizationPanel extends BorderPane implements TimeLineView {
final public class VisualizationPanel extends BorderPane {
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
@ -107,9 +106,9 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
@GuardedBy("this")
private LoggedTask<Void> histogramTask;
private final NavPanel navPanel;
private final EventsTree eventsTree;
private AbstractVisualization<?, ?, ?, ?> visualization;
private AbstractVisualizationPane<?, ?, ?, ?> visualization;
//// range slider and histogram componenets
@FXML
@ -178,8 +177,8 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
private FilteredEventsModel filteredEvents;
private final ChangeListener<Object> rangeSliderListener
= (observable1, oldValue, newValue) -> {
private final ChangeListener<Object> rangeSliderListener =
(observable1, oldValue, newValue) -> {
if (rangeSlider.isHighValueChanging() == false && rangeSlider.isLowValueChanging() == false) {
Long minTime = filteredEvents.getMinTime() * 1000;
controller.pushTimeRange(new Interval(
@ -207,14 +206,15 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
static private final Lighting lighting = new Lighting();
public VisualizationPanel(NavPanel navPanel) {
this.navPanel = navPanel;
public VisualizationPanel(TimeLineController controller, EventsTree eventsTree) {
this.controller = controller;
this.eventsTree = eventsTree;
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
}
@FXML // This method is called by the FXMLLoader when initialization is complete
@NbBundle.Messages("VisualizationPanel.refresh=refresh")
protected void initialize() {
void initialize() {
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
assert startPicker != null : "fx:id=\"startPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
@ -289,32 +289,30 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
}
zoomMenuButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomMenuButton.text")); // NON-NLS
zoomOutButton.setOnAction(e -> {
controller.pushZoomOutTime();
});
zoomInButton.setOnAction(e -> {
controller.pushZoomInTime();
});
snapShotButton.setOnAction((ActionEvent event) -> {
//take snapshot
final SnapshotParameters snapshotParameters = new SnapshotParameters();
snapshotParameters.setViewport(new Rectangle2D(visualization.getBoundsInParent().getMinX(), visualization.getBoundsInParent().getMinY(),
visualization.getBoundsInParent().getWidth(),
contextPane.getLayoutBounds().getHeight() + visualization.getLayoutBounds().getHeight() + partPane.getLayoutBounds().getHeight()
));
WritableImage snapshot = this.snapshot(snapshotParameters, null);
//pass snapshot to save action
new SaveSnapshot(controller, snapshot).handle(event);
});
snapShotButton.setOnAction(event ->
this.snapshot(snapShotResult -> {
new SaveSnapshotAsReport(controller, snapShotResult.getImage()).handle(event);
return null;
}, null, null)
);
snapShotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapShotButton.text")); // NON-NLS
}
if (this.filteredEvents != null && this.filteredEvents != controller.getEventsModel()) {
this.filteredEvents.unRegisterForEvents(this);
this.filteredEvents.timeRangeProperty().removeListener(timeRangeInvalidationListener);
this.filteredEvents.zoomParametersProperty().removeListener(zoomListener);
}
if (this.filteredEvents != controller.getEventsModel()) {
controller.getEventsModel().registerForEvents(this);
controller.getEventsModel().timeRangeProperty().addListener(timeRangeInvalidationListener);
controller.getEventsModel().zoomParametersProperty().addListener(zoomListener);
}
this.filteredEvents = controller.getEventsModel();
refreshTimeUI(controller.getEventsModel().timeRangeProperty().get());
ActionUtils.configureButton(new ZoomOut(controller), zoomOutButton);
ActionUtils.configureButton(new ZoomIn(controller), zoomInButton);
@Override
public synchronized void setController(TimeLineController controller) {
this.controller = controller;
setModel(controller.getEventsModel());
setViewMode(controller.viewModeProperty().get());
controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
if (newValue) {
@ -329,39 +327,20 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
refreshHistorgram();
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
if (this.filteredEvents != null && this.filteredEvents != filteredEvents) {
this.filteredEvents.unRegisterForEvents(this);
this.filteredEvents.timeRangeProperty().removeListener(timeRangeInvalidationListener);
this.filteredEvents.zoomParametersProperty().removeListener(zoomListener);
}
if (this.filteredEvents != filteredEvents) {
filteredEvents.registerForEvents(this);
filteredEvents.timeRangeProperty().addListener(timeRangeInvalidationListener);
filteredEvents.zoomParametersProperty().addListener(zoomListener);
}
this.filteredEvents = filteredEvents;
refreshTimeUI(filteredEvents.timeRangeProperty().get());
}
private void setViewMode(VisualizationMode visualizationMode) {
switch (visualizationMode) {
case COUNTS:
setVisualization(new CountsViewPane(partPane, contextPane, spacer));
setVisualization(new CountsViewPane(controller, partPane, contextPane, spacer));
countsToggle.setSelected(true);
break;
case DETAIL:
setVisualization(new DetailViewPane(partPane, contextPane, spacer));
setVisualization(new DetailViewPane(controller, partPane, contextPane, spacer));
detailsToggle.setSelected(true);
break;
}
}
private synchronized void setVisualization(final AbstractVisualization<?, ?, ?, ?> newViz) {
private synchronized void setVisualization(final AbstractVisualizationPane<?, ?, ?, ?> newViz) {
Platform.runLater(() -> {
synchronized (VisualizationPanel.this) {
if (visualization != null) {
@ -370,24 +349,25 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
}
visualization = newViz;
visualization.update();
toolBar.getItems().addAll(newViz.getSettingsNodes());
visualization.setController(controller);
notificationPane.setContent(visualization);
if (visualization instanceof DetailViewPane) {
navPanel.setDetailViewPane((DetailViewPane) visualization);
eventsTree.setDetailViewPane((DetailViewPane) visualization);
}
visualization.hasEvents.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
visualization.hasEvents.addListener((observable, oldValue, newValue) -> {
if (newValue == false) {
notificationPane.setContent(new StackPane(visualization, new Region() {
{
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
setOpacity(.3);
}
}, new NoEventsDialog(() -> {
notificationPane.setContent(visualization);
})));
notificationPane.setContent(
new StackPane(visualization,
new Region() {
{
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
setOpacity(.3);
}
},
new NoEventsDialog(() -> notificationPane.setContent(visualization))));
} else {
notificationPane.setContent(visualization);
}
@ -552,7 +532,6 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
private NoEventsDialog(Runnable closeCallback) {
this.closeCallback = closeCallback;
FXMLConstructor.construct(this, "NoEventsDialog.fxml"); // NON-NLS
}
@FXML
@ -562,15 +541,9 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS
zoomButton.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.zoomButton.text")); // NON-NLS
ActionUtils.configureButton(new ZoomToEvents(controller), zoomButton);
Action zoomOutAction = new ZoomOut(controller);
zoomButton.setOnAction(zoomOutAction);
zoomButton.disableProperty().bind(zoomOutAction.disabledProperty());
dismissButton.setOnAction(e -> {
closeCallback.run();
});
dismissButton.setOnAction(actionEvent -> closeCallback.run());
Action defaultFiltersAction = new ResetFilters(controller);
resetFiltersButton.setOnAction(defaultFiltersAction);
resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -26,14 +26,12 @@ Timeline.ui.countsview.menuItem.selectTimeRange=Select Time Range
Timeline.ui.countsview.menuItem.selectEventType=Select Event Type
Timeline.ui.countsview.menuItem.selectTimeandType=Select Time and Type
Timeline.ui.countsview.menuItem.zoomIntoTimeRange=Zoom into Time Range
Timeline.ui.countsview.contextMenu.ActionGroup.zoomHistory.title=Zoom History
CountsViewPane.loggedTask.name=Updating Counts Graph
CountsViewPane.loggedTask.prepUpdate=preparing update
CountsViewPane.loggedTask.resetUI=resetting ui
CountsViewPane.tooltip.text={0} {1} events\nbetween {2}\nand {3}
CountsViewPane.loggedTask.updatingCounts=updating counts
CountsViewPane.loggedTask.wrappingUp=wrapping up
EventCountsChart.contextMenu.zoomHistory.name=Zoom History
CountsViewPane.scaleLabel.text=Scale\:
CountsViewPane.logRadio.text=Logarithmic
CountsViewPane.linearRadio.text=Linear

View File

@ -66,14 +66,13 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
/**
@ -97,7 +96,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
* TODO: refactor common code out of this class and ClusterChartPane into
* AbstractChartView
*/
public class CountsViewPane extends AbstractVisualization<String, Number, Node, EventCountsChart> {
public class CountsViewPane extends AbstractVisualizationPane<String, Number, Node, EventCountsChart> {
private static final Effect SELECTED_NODE_EFFECT = new Lighting();
@ -213,17 +212,17 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
node.setStyle("-fx-border-width: 2; -fx-border-color: " + ColorUtilities.getRGBCode(et.getSuperType().getColor()) + "; -fx-bar-fill: " + ColorUtilities.getRGBCode(et.getColor())); // NON-NLS
node.setCursor(Cursor.HAND);
final Tooltip tooltip = new Tooltip(
NbBundle.getMessage(this.getClass(), "CountsViewPane.tooltip.text",
count,
et.getDisplayName(),
dateString,
interval.getEnd().toString(
rangeInfo.getTickFormatter())));
tooltip.setGraphic(new ImageView(et.getFXImage()));
Tooltip.install(node, tooltip);
node.setOnMouseEntered((MouseEvent event) -> {
//defer tooltip creation till needed, this had a surprisingly large impact on speed of loading the chart
final Tooltip tooltip = new Tooltip(
NbBundle.getMessage(this.getClass(), "CountsViewPane.tooltip.text",
count,
et.getDisplayName(),
dateString,
interval.getEnd().toString(
rangeInfo.getTickFormatter())));
tooltip.setGraphic(new ImageView(et.getFXImage()));
Tooltip.install(node, tooltip);
node.setEffect(new DropShadow(10, et.getColor()));
});
node.setOnMouseExited((MouseEvent event) -> {
@ -279,13 +278,15 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
};
}
public CountsViewPane(Pane partPane, Pane contextPane, Region spacer) {
super(partPane, contextPane, spacer);
chart = new EventCountsChart(dateAxis, countAxis);
public CountsViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
super(controller, partPane, contextPane, spacer);
chart = new EventCountsChart(controller, dateAxis, countAxis);
setChartClickHandler();
chart.setData(dataSets);
setCenter(chart);
Tooltip.install(chart, getDragTooltip());
settingsNodes = new ArrayList<>(new CountsViewSettingsPane().getChildrenUnmodifiable());
dateAxis.getTickMarks().addListener((Observable observable) -> {
@ -310,9 +311,6 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
});
}
@Override
protected NumberAxis getYAxis() {
return countAxis;
@ -534,7 +532,7 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
scaleLabel.setText(NbBundle.getMessage(this.getClass(), "CountsViewPane.scaleLabel.text"));
}
public CountsViewSettingsPane() {
CountsViewSettingsPane() {
FXMLConstructor.construct(this, "CountsViewSettingsPane.fxml"); // NON-NLS
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2014-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -19,24 +19,19 @@
package org.sleuthkit.autopsy.timeline.ui.countsview;
import java.util.Arrays;
import java.util.Collections;
import javafx.scene.chart.Axis;
import java.util.MissingResourceException;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedBarChart;
import javafx.scene.control.ContextMenu;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.util.StringConverter;
import org.controlsfx.control.action.ActionGroup;
import org.controlsfx.control.action.ActionUtils;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
@ -44,11 +39,16 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
* Customized {@link StackedBarChart<String, Number>} used to display the event
* counts in {@link CountsViewPane}
*/
class EventCountsChart extends StackedBarChart<String, Number> implements TimeLineChart<String> {
final class EventCountsChart extends StackedBarChart<String, Number> implements TimeLineChart<String> {
private ContextMenu contextMenu;
private ContextMenu chartContextMenu;
private TimeLineController controller;
@Override
public ContextMenu getChartContextMenu() {
return chartContextMenu;
}
private final TimeLineController controller;
private IntervalSelector<? extends String> intervalSelector;
@ -59,8 +59,9 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
*/
private RangeDivisionInfo rangeInfo;
EventCountsChart(CategoryAxis dateAxis, NumberAxis countAxis) {
EventCountsChart(TimeLineController controller, CategoryAxis dateAxis, NumberAxis countAxis) {
super(dateAxis, countAxis);
this.controller = controller;
//configure constant properties on axes and chart
dateAxis.setAnimated(true);
dateAxis.setLabel(null);
@ -80,19 +81,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
setAnimated(true);
setTitle(null);
//use one handler with an if chain because it maintains state
ChartDragHandler<String, EventCountsChart> dragHandler = new ChartDragHandler<>(this, getXAxis());
setOnMousePressed(dragHandler);
setOnMouseReleased(dragHandler);
setOnMouseDragged(dragHandler);
ChartDragHandler<String, EventCountsChart> chartDragHandler = new ChartDragHandler<>(this);
setOnMousePressed(chartDragHandler);
setOnMouseReleased(chartDragHandler);
setOnMouseDragged(chartDragHandler);
setOnMouseClicked(new MouseClickedHandler<>(this));
setOnMouseClicked((MouseEvent clickEvent) -> {
contextMenu.hide();
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
contextMenu.show(EventCountsChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
clickEvent.consume();
}
});
}
@Override
@ -102,16 +97,21 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
}
@Override
public final synchronized void setController(TimeLineController controller) {
this.controller = controller;
setModel(this.controller.getEventsModel());
//we have defered creating context menu until control is available
contextMenu = ActionUtils.createContextMenu(
Arrays.asList(new ActionGroup(
NbBundle.getMessage(this.getClass(), "EventCountsChart.contextMenu.zoomHistory.name"),
new Back(controller),
new Forward(controller))));
contextMenu.setAutoHide(true);
public ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
if (chartContextMenu != null) {
chartContextMenu.hide();
}
chartContextMenu = ActionUtils.createContextMenu(
Arrays.asList(
TimeLineChart.newZoomHistoyActionGroup(controller)));
chartContextMenu.setAutoHide(true);
return chartContextMenu;
}
@Override
public TimeLineController getController() {
return controller;
}
@Override
@ -126,16 +126,8 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
filteredEvents.zoomParametersProperty().addListener(o -> {
clearIntervalSelector();
controller.selectEventIDs(Collections.emptyList());
});
}
@Override
public CountsIntervalSelector newIntervalSelector(double x, Axis<String> dateAxis) {
return new CountsIntervalSelector(x, getHeight() - dateAxis.getHeight() - dateAxis.getTickLength(), dateAxis, controller);
public CountsIntervalSelector newIntervalSelector() {
return new CountsIntervalSelector(this);
}
/**
@ -145,7 +137,7 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
* @return the context menu for this chart
*/
ContextMenu getContextMenu() {
return contextMenu;
return chartContextMenu;
}
void setRangeInfo(RangeDivisionInfo rangeInfo) {
@ -175,10 +167,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
* Interval Selector for the counts chart, adjusts interval based on
* rangeInfo to include final period
*/
private class CountsIntervalSelector extends IntervalSelector<String> {
final static private class CountsIntervalSelector extends IntervalSelector<String> {
public CountsIntervalSelector(double x, double height, Axis<String> axis, TimeLineController controller) {
super(x, height, axis, controller);
private final EventCountsChart countsChart;
CountsIntervalSelector(EventCountsChart chart) {
super(chart);
this.countsChart = chart;
}
@Override
@ -195,12 +190,13 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
final DateTime lowerDate = new DateTime(lowerBound, TimeLineController.getJodaTimeZone());
final DateTime upperDate = new DateTime(upperBound, TimeLineController.getJodaTimeZone());
//add extra block to end that gets cut of by conversion from string/category.
return new Interval(lowerDate, upperDate.plus(rangeInfo.getPeriodSize().getPeriod()));
return new Interval(lowerDate, upperDate.plus(countsChart.rangeInfo.getPeriodSize().getPeriod()));
}
@Override
protected DateTime parseDateTime(String date) {
return date == null ? new DateTime(rangeInfo.getLowerBound()) : rangeInfo.getTickFormatter().parseDateTime(date);
return date == null ? new DateTime(countsChart.rangeInfo.getLowerBound()) : countsChart.rangeInfo.getTickFormatter().parseDateTime(date);
}
}
}

View File

@ -73,25 +73,24 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
/**
* Controller class for a {@link EventDetailChart} based implementation of a
* Controller class for a {@link EventDetailsChart} based implementation of a
* TimeLineView.
*
* This class listens to changes in the assigned {@link FilteredEventsModel} and
* updates the internal {@link EventDetailChart} to reflect the currently
* updates the internal {@link EventDetailsChart} to reflect the currently
* requested events.
*
* Concurrency Policy: Access to the private members clusterChart, dateAxis,
* EventTypeMap, and dataSets is all linked directly to the ClusterChart which
* must only be manipulated on the JavaFx thread.
*/
public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster, EventBundleNodeBase<?, ?, ?>, EventDetailChart> {
public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventCluster, EventBundleNodeBase<?, ?, ?>, EventDetailsChart> {
private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
private MultipleSelectionModel<TreeItem<EventBundle<?>>> treeSelectionModel;
@ -114,13 +113,15 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
return chart.getEventBundles();
}
public DetailViewPane(Pane partPane, Pane contextPane, Region spacer) {
super(partPane, contextPane, spacer);
chart = new EventDetailChart(dateAxis, verticalAxis, selectedNodes);
public DetailViewPane(TimeLineController controller, Pane partPane, Pane contextPane, Region spacer) {
super(controller, partPane, contextPane, spacer);
chart = new EventDetailsChart(controller, dateAxis, verticalAxis, selectedNodes);
setChartClickHandler();
chart.setData(dataSets);
setCenter(chart);
chart.setPrefHeight(USE_COMPUTED_SIZE);
settingsNodes = new ArrayList<>(new DetailViewSettingsPane().getChildrenUnmodifiable());
@ -211,11 +212,6 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
}
@Override
public synchronized void setModel(FilteredEventsModel filteredEvents) {
super.setModel(filteredEvents);
}
private void incrementScrollValue(int factor) {
vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() + factor * (chart.getHeight() / chart.maxVScrollProperty().get()))));
}
@ -477,5 +473,4 @@ public class DetailViewPane extends AbstractVisualization<DateTime, EventCluster
return chart.new HideDescriptionAction(description, descriptionLoD);
}
}

View File

@ -64,6 +64,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.show;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
import org.sleuthkit.datamodel.SleuthkitCase;
@ -96,7 +97,7 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
b.setManaged(show);
}
protected final EventDetailChart chart;
protected final EventDetailsChart chart;
final SimpleObjectProperty<DescriptionLoD> descLOD = new SimpleObjectProperty<>();
final SimpleObjectProperty<DescriptionVisibility> descVisibility = new SimpleObjectProperty<>(DescriptionVisibility.SHOWN);
protected final BundleType eventBundle;
@ -119,9 +120,9 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
final ImageView tagIV = new ImageView(TAG);
final HBox infoHBox = new HBox(5, descrLabel, countLabel, hashIV, tagIV);
private Tooltip tooltip;
private final Tooltip tooltip = new Tooltip("loading...");
public EventBundleNodeBase(EventDetailChart chart, BundleType eventBundle, ParentNodeType parentNode) {
public EventBundleNodeBase(EventDetailsChart chart, BundleType eventBundle, ParentNodeType parentNode) {
this.eventBundle = eventBundle;
this.parentNode = parentNode;
this.chart = chart;
@ -156,7 +157,6 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
infoHBox.setMaxWidth(USE_PREF_SIZE);
infoHBox.setPadding(new Insets(2, 5, 2, 5));
infoHBox.setAlignment(Pos.TOP_LEFT);
infoHBox.setPickOnBounds(true);
//set up subnode pane sizing contraints
subNodePane.setPrefHeight(USE_COMPUTED_SIZE);
@ -165,27 +165,34 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
subNodePane.setMinWidth(USE_PREF_SIZE);
subNodePane.setMaxWidth(USE_PREF_SIZE);
Tooltip.install(this, this.tooltip);
//set up mouse hover effect and tooltip
setOnMouseEntered((MouseEvent e) -> {
/*
* defer tooltip creation till needed, this had a surprisingly large
* impact on speed of loading the chart
* defer tooltip content creation till needed, this had a
* surprisingly large impact on speed of loading the chart
*/
installTooltip();
Tooltip.uninstall(chart, AbstractVisualizationPane.getDragTooltip());
showHoverControls(true);
toFront();
});
setOnMouseExited((MouseEvent event) -> {
showHoverControls(false);
if (parentNode != null) {
parentNode.showHoverControls(true);
} else {
Tooltip.install(chart, AbstractVisualizationPane.getDragTooltip());
}
});
setDescriptionVisibility(DescriptionVisibility.SHOWN);
descVisibility.addListener((ObservableValue<? extends DescriptionVisibility> observable, DescriptionVisibility oldValue, DescriptionVisibility newValue) -> {
setDescriptionVisibility(newValue);
setDescriptionVisibiltiyImpl(newValue);
});
setDescriptionVisibiltiyImpl(DescriptionVisibility.SHOWN);
}
final DescriptionLoD getDescriptionLoD() {
@ -210,8 +217,11 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
"EventBundleNodeBase.tooltip.text={0} {1} events\n{2}\nbetween\t{3}\nand \t{4}"})
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void installTooltip() {
if (tooltip == null) {
if (tooltip.getText().equalsIgnoreCase("loading...")) {
final Task<String> tooltTipTask = new Task<String>() {
{
updateTitle("loading tooltip");
}
@Override
protected String call() throws Exception {
@ -252,13 +262,10 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
protected void succeeded() {
super.succeeded();
try {
tooltip = new Tooltip(get());
tooltip.setAutoHide(true);
Tooltip.install(EventBundleNodeBase.this, tooltip);
tooltip.setText(get());
tooltip.setGraphic(null);
} catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.SEVERE, "Tooltip generation failed.", ex);
Tooltip.uninstall(EventBundleNodeBase.this, tooltip);
tooltip = null;
}
}
};
@ -288,15 +295,18 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
return subNodes;
}
abstract void setDescriptionVisibility(DescriptionVisibility get);
abstract void setDescriptionVisibiltiyImpl(DescriptionVisibility get);
void showHoverControls(final boolean showControls) {
Effect dropShadow = dropShadowMap.computeIfAbsent(getEventType(),
eventType -> new DropShadow(-10, eventType.getColor()));
setEffect(showControls ? dropShadow : null);
enableTooltip(showControls);
if (parentNode != null) {
parentNode.enableTooltip(false);
parentNode.showHoverControls(false);
}
}
final EventType getEventType() {
@ -330,8 +340,15 @@ public abstract class EventBundleNodeBase<BundleType extends EventBundle<ParentT
*/
abstract void setDescriptionWidth(double w);
void setDescriptionVisibilityLevel(DescriptionVisibility get) {
void setDescriptionVisibility(DescriptionVisibility get) {
descVisibility.set(get);
}
void enableTooltip(boolean toolTipEnabled) {
if (toolTipEnabled) {
Tooltip.install(this, tooltip);
} else {
Tooltip.uninstall(this, tooltip);
}
}
}

View File

@ -74,7 +74,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
final Button plusButton = ActionUtils.createButton(new ExpandClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
final Button minusButton = ActionUtils.createButton(new CollapseClusterAction(), ActionUtils.ActionTextBehavior.HIDE);
public EventClusterNode(EventDetailChart chart, EventCluster eventCluster, EventStripeNode parentNode) {
public EventClusterNode(EventDetailsChart chart, EventCluster eventCluster, EventStripeNode parentNode) {
super(chart, eventCluster, parentNode);
setMinHeight(24);
@ -114,7 +114,7 @@ final public class EventClusterNode extends EventBundleNodeBase<EventCluster, Ev
}
@Override
public void setDescriptionVisibility(DescriptionVisibility descrVis) {
void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
final int size = getEventBundle().getEventIDs().size();
switch (descrVis) {
case HIDDEN:

View File

@ -53,6 +53,7 @@ import javafx.scene.chart.Axis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
@ -62,14 +63,11 @@ import javafx.scene.shape.StrokeLineCap;
import javafx.util.Duration;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionGroup;
import org.controlsfx.control.action.ActionUtils;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
@ -77,6 +75,8 @@ import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
@ -94,7 +94,7 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
*
* //TODO: refactor the projected lines to a separate class. -jm
*/
public final class EventDetailChart extends XYChart<DateTime, EventCluster> implements TimeLineChart<DateTime> {
public final class EventDetailsChart extends XYChart<DateTime, EventCluster> implements TimeLineChart<DateTime> {
private static final Image HIDE = new Image("/org/sleuthkit/autopsy/timeline/images/eye--minus.png"); // NON-NLS
private static final Image SHOW = new Image("/org/sleuthkit/autopsy/timeline/images/eye--plus.png"); // NON-NLS
@ -102,11 +102,18 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
private static final int PROJECTED_LINE_Y_OFFSET = 5;
private static final int PROJECTED_LINE_STROKE_WIDTH = 5;
private static final int MINIMUM_EVENT_NODE_GAP = 4;
private final TimeLineController controller;
private final FilteredEventsModel filteredEvents;
private ContextMenu chartContextMenu;
private TimeLineController controller;
private FilteredEventsModel filteredEvents;
public ContextMenu getChartContextMenu() {
return chartContextMenu;
}
/**
* a user positionable vertical line to help compare events
@ -183,8 +190,21 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
*/
final SimpleDoubleProperty truncateWidth = new SimpleDoubleProperty(200.0);
EventDetailChart(DateAxis dateAxis, final Axis<EventCluster> verticalAxis, ObservableList<EventBundleNodeBase<?, ?, ?>> selectedNodes) {
EventDetailsChart(TimeLineController controller, DateAxis dateAxis, final Axis<EventCluster> verticalAxis, ObservableList<EventBundleNodeBase<?, ?, ?>> selectedNodes) {
super(dateAxis, verticalAxis);
this.controller = controller;
this.filteredEvents = this.controller.getEventsModel();
filteredEvents.zoomParametersProperty().addListener(o -> {
clearGuideLine();
clearIntervalSelector();
selectedNodes.clear();
projectionMap.clear();
controller.selectEventIDs(Collections.emptyList());
});
Tooltip.install(this, AbstractVisualizationPane.getDragTooltip());
dateAxis.setAutoRanging(false);
verticalAxis.setVisible(false);//TODO: why doesn't this hide the vertical axis, instead we have to turn off all parts individually? -jm
@ -204,6 +224,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
truncateAll.addListener(layoutInvalidationListener);
truncateWidth.addListener(layoutInvalidationListener);
descrVisibility.addListener(layoutInvalidationListener);
getController().getQuickHideFilters().addListener(layoutInvalidationListener);
//this is needed to allow non circular binding of the guideline and timerangeRect heights to the height of the chart
//TODO: seems like a hack, can we remove? -jm
@ -211,22 +232,12 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
setPrefHeight(boundsInLocalProperty().get().getHeight());
});
///////set up mouse listeners
setOnMouseClicked((MouseEvent clickEvent) -> {
if (chartContextMenu != null) {
chartContextMenu.hide();
}
if (clickEvent.getButton() == MouseButton.SECONDARY && clickEvent.isStillSincePress()) {
getChartContextMenu(clickEvent);
chartContextMenu.show(EventDetailChart.this, clickEvent.getScreenX(), clickEvent.getScreenY());
clickEvent.consume();
}
});
//use one handler with an if chain because it maintains state
final ChartDragHandler<DateTime, EventDetailChart> dragHandler = new ChartDragHandler<>(this, getXAxis());
setOnMousePressed(dragHandler);
setOnMouseReleased(dragHandler);
setOnMouseDragged(dragHandler);
ChartDragHandler<DateTime, EventDetailsChart> chartDragHandler = new ChartDragHandler<>(this);
setOnMousePressed(chartDragHandler);
setOnMouseReleased(chartDragHandler);
setOnMouseDragged(chartDragHandler);
setOnMouseClicked(new MouseClickedHandler<>(this));
this.selectedNodes = selectedNodes;
this.selectedNodes.addListener(new SelectionChangeHandler());
@ -236,21 +247,20 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
return bundles;
}
TimeLineController getController() {
@Override
public TimeLineController getController() {
return controller;
}
@NbBundle.Messages({"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker",
"EventDetailChart.contextMenu.zoomHistory.name=Zoom History"})
ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
@Override
public ContextMenu getChartContextMenu(MouseEvent clickEvent) throws MissingResourceException {
if (chartContextMenu != null) {
chartContextMenu.hide();
}
chartContextMenu = ActionUtils.createContextMenu(Arrays.asList(new PlaceMarkerAction(clickEvent),
new ActionGroup(Bundle.EventDetailChart_contextMenu_zoomHistory_name(),
new Back(controller),
new Forward(controller))));
// new StartIntervalSelectionAction(clickEvent, dragHandler),
TimeLineChart.newZoomHistoyActionGroup(controller)));
chartContextMenu.setAutoHide(true);
return chartContextMenu;
}
@ -266,32 +276,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
}
@Override
public synchronized void setController(TimeLineController controller) {
this.controller = controller;
setModel(this.controller.getEventsModel());
getController().getQuickHideFilters().addListener(layoutInvalidationListener);
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
if (this.filteredEvents != filteredEvents) {
filteredEvents.zoomParametersProperty().addListener(o -> {
clearGuideLine();
clearIntervalSelector();
selectedNodes.clear();
projectionMap.clear();
controller.selectEventIDs(Collections.emptyList());
});
}
this.filteredEvents = filteredEvents;
}
@Override
public IntervalSelector<DateTime> newIntervalSelector(double x, Axis<DateTime> axis) {
return new DetailIntervalSelector(x, getHeight() - axis.getHeight() - axis.getTickLength(), axis, controller);
public IntervalSelector<DateTime> newIntervalSelector() {
return new DetailIntervalSelector(this);
}
synchronized void setBandByType(Boolean t1) {
@ -300,12 +286,12 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
/**
* get the DateTime along the x-axis that corresponds to the given
* x-coordinate in the coordinate system of this {@link EventDetailChart}
* x-coordinate in the coordinate system of this {@link EventDetailsChart}
*
* @param x a x-coordinate in the space of this {@link EventDetailChart}
* @param x a x-coordinate in the space of this {@link EventDetailsChart}
*
* @return the DateTime along the x-axis corresponding to the given x value
* (in the space of this {@link EventDetailChart}
* (in the space of this {@link EventDetailsChart}
*/
public DateTime getDateTimeForPosition(double x) {
return getXAxis().getValueForDisplay(getXAxis().parentToLocal(x, 0).getX());
@ -352,7 +338,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
return EventStripe.merge(u, v);
}
);
EventStripeNode stripeNode = new EventStripeNode(EventDetailChart.this, eventStripe, null);
EventStripeNode stripeNode = new EventStripeNode(EventDetailsChart.this, eventStripe, null);
stripeNodeMap.put(eventStripe, stripeNode);
nodeGroup.getChildren().add(stripeNode);
data.setNode(stripeNode);
@ -498,7 +484,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
bundleNode.setVisible(true);
bundleNode.setManaged(true);
//apply advanced layout description visibility options
bundleNode.setDescriptionVisibilityLevel(descrVisibility.get());
bundleNode.setDescriptionVisibility(descrVisibility.get());
bundleNode.setDescriptionWidth(truncateAll.get() ? truncateWidth.get() : USE_PREF_SIZE);
//do recursive layout
@ -586,8 +572,8 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
static private class DetailIntervalSelector extends IntervalSelector<DateTime> {
DetailIntervalSelector(double x, double height, Axis<DateTime> axis, TimeLineController controller) {
super(x, height, axis, controller);
DetailIntervalSelector(EventDetailsChart chart) {
super(chart);
}
@Override
@ -608,6 +594,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
private class PlaceMarkerAction extends Action {
@NbBundle.Messages({"EventDetailChart.chartContextMenu.placeMarker.name=Place Marker"})
PlaceMarkerAction(MouseEvent clickEvent) {
super(Bundle.EventDetailChart_chartContextMenu_placeMarker_name());
@ -664,7 +651,7 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
}
});
}
EventDetailChart.this.controller.selectEventIDs(selectedNodes.stream()
EventDetailsChart.this.controller.selectEventIDs(selectedNodes.stream()
.flatMap(detailNode -> detailNode.getEventIDs().stream())
.collect(Collectors.toList()));
}
@ -708,4 +695,5 @@ public final class EventDetailChart extends XYChart<DateTime, EventCluster> impl
);
}
}
}

View File

@ -37,7 +37,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventBundleNodeBase.configureLoDButton;
/**
* Node used in {@link EventDetailChart} to represent an EventStripe.
* Node used in {@link EventDetailsChart} to represent an EventStripe.
*/
final public class EventStripeNode extends EventBundleNodeBase<EventStripe, EventCluster, EventClusterNode> {
@ -53,12 +53,12 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
// private final HBox clustersHBox = new HBox();
private final ImageView eventTypeImageView = new ImageView();
public EventStripeNode(EventDetailChart chart, EventStripe eventStripe, EventClusterNode parentNode) {
public EventStripeNode(EventDetailsChart chart, EventStripe eventStripe, EventClusterNode parentNode) {
super(chart, eventStripe, parentNode);
setMinHeight(48);
EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
EventDetailsChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
hideButton = ActionUtils.createButton(hideClusterAction, ActionUtils.ActionTextBehavior.HIDE);
configureLoDButton(hideButton);
@ -115,7 +115,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
}
@Override
public void setDescriptionVisibility(DescriptionVisibility descrVis) {
void setDescriptionVisibiltiyImpl(DescriptionVisibility descrVis) {
final int size = getEventStripe().getEventIDs().size();
switch (descrVis) {
@ -150,7 +150,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
public void handle(MouseEvent t) {
if (t.getButton() == MouseButton.PRIMARY) {
if (t.isShiftDown()) {
if (chart.selectedNodes.contains(EventStripeNode.this) == false) {
chart.selectedNodes.add(EventStripeNode.this);
@ -167,7 +167,7 @@ final public class EventStripeNode extends EventBundleNodeBase<EventStripe, Even
contextMenu = new ContextMenu();
contextMenu.setAutoHide(true);
EventDetailChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
EventDetailsChart.HideDescriptionAction hideClusterAction = chart.new HideDescriptionAction(getDescription(), eventBundle.getDescriptionLoD());
MenuItem hideDescriptionMenuItem = ActionUtils.createMenuItem(hideClusterAction);
contextMenu.getItems().addAll(hideDescriptionMenuItem);
contextMenu.getItems().addAll(chartContextMenu.getItems());

View File

@ -1 +0,0 @@
NavPanel.eventsTreeLabel.text=Sort By\:

View File

@ -1 +1 @@
NavPanel.eventsTreeLabel.text=\u4E0B\u8A18\u306B\u5F93\u3044\u4E26\u3079\u66FF\u3048\uFF1A
EventsTree.Label.text=\u4e0b\u8a18\u306b\u5f93\u3044\u4e26\u3079\u66ff\u3048\uff1a

View File

@ -46,9 +46,7 @@ import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.datamodel.EventBundle;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
@ -59,11 +57,9 @@ import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
* out. Right clicking on a item in the tree shows a context menu to show/hide
* it.
*/
public class NavPanel extends BorderPane implements TimeLineView {
final public class EventsTree extends BorderPane {
private TimeLineController controller;
private FilteredEventsModel filteredEvents;
private final TimeLineController controller;
private DetailViewPane detailViewPane;
@ -76,8 +72,10 @@ public class NavPanel extends BorderPane implements TimeLineView {
@FXML
private ComboBox<Comparator<TreeItem<EventBundle<?>>>> sortByBox;
public NavPanel() {
FXMLConstructor.construct(this, "NavPanel.fxml"); // NON-NLS
public EventsTree(TimeLineController controller) {
this.controller = controller;
FXMLConstructor.construct(this, "EventsTree.fxml"); // NON-NLS
}
public void setDetailViewPane(DetailViewPane detailViewPane) {
@ -112,19 +110,8 @@ public class NavPanel extends BorderPane implements TimeLineView {
}
@Override
public void setController(TimeLineController controller) {
this.controller = controller;
setModel(controller.getEventsModel());
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
this.filteredEvents = filteredEvents;
}
@FXML
@NbBundle.Messages("EventsTree.Label.text=Sort By:")
void initialize() {
assert sortByBox != null : "fx:id=\"sortByBox\" was not injected: check your FXML file 'NavPanel.fxml'."; // NON-NLS
@ -137,7 +124,7 @@ public class NavPanel extends BorderPane implements TimeLineView {
eventsTree.setCellFactory((TreeView<EventBundle<?>> p) -> new EventBundleTreeCell());
eventsTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
eventsTreeLabel.setText(NbBundle.getMessage(this.getClass(), "NavPanel.eventsTreeLabel.text"));
eventsTreeLabel.setText(Bundle.EventsTree_Label_text());
}
/**
@ -232,6 +219,5 @@ public class NavPanel extends BorderPane implements TimeLineView {
setContextMenu(null);
}
}
}
}

View File

@ -44,7 +44,6 @@ import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
@ -64,7 +63,7 @@ import static org.sleuthkit.autopsy.timeline.ui.filtering.Bundle.Timeline_ui_fil
* This also implements {@link TimeLineView} since it dynamically updates its
* filters based on the contents of a {@link FilteredEventsModel}
*/
final public class FilterSetPanel extends BorderPane implements TimeLineView {
final public class FilterSetPanel extends BorderPane {
private static final Image TICK = new Image("org/sleuthkit/autopsy/timeline/images/tick.png");
@ -171,20 +170,25 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView {
legendColumn.setCellValueFactory(param -> param.getValue().valueProperty());
legendColumn.setCellFactory(col -> new LegendCell(this.controller));
}
public FilterSetPanel() {
FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS
expansionMap.put(new TypeFilter(RootEventType.getInstance()).getDisplayName(), true);
}
@Override
public void setController(TimeLineController timeLineController) {
this.controller = timeLineController;
Action defaultFiltersAction = new ResetFilters(controller);
defaultButton.setOnAction(defaultFiltersAction);
defaultButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
this.setModel(timeLineController.getEventsModel());
this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> {
applyFilters();
});
this.filteredEvents.descriptionLODProperty().addListener((Observable observable1) -> {
applyFilters();
});
this.filteredEvents.timeRangeProperty().addListener((Observable observable2) -> {
applyFilters();
});
this.filteredEvents.filterProperty().addListener((Observable o) -> {
refresh();
});
refresh();
hiddenDescriptionsListView.setItems(controller.getQuickHideFilters());
hiddenDescriptionsListView.setCellFactory((ListView<DescriptionFilter> param) -> {
@ -237,25 +241,13 @@ final public class FilterSetPanel extends BorderPane implements TimeLineView {
}
});
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
this.filteredEvents = filteredEvents;
this.filteredEvents.eventTypeZoomProperty().addListener((Observable observable) -> {
applyFilters();
});
this.filteredEvents.descriptionLODProperty().addListener((Observable observable) -> {
applyFilters();
});
this.filteredEvents.timeRangeProperty().addListener((Observable observable) -> {
applyFilters();
});
this.filteredEvents.filterProperty().addListener((Observable o) -> {
refresh();
});
refresh();
public FilterSetPanel(TimeLineController controller) {
this.controller = controller;
this.filteredEvents = controller.getEventsModel();
FXMLConstructor.construct(this, "FilterSetPanel.fxml"); // NON-NLS
}

View File

@ -29,7 +29,6 @@ import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
@ -40,18 +39,19 @@ import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
* A TreeTableCell that shows an icon and color corresponding to the represented
* filter
*/
class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> implements TimeLineView {
final class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> {
private static final Color CLEAR = Color.rgb(0, 0, 0, 0);
private TimeLineController controller;
private final TimeLineController controller;
private FilteredEventsModel filteredEvents;
private final FilteredEventsModel filteredEvents;
//We need a controller so we can listen to changes in EventTypeZoom to show/hide legends
public LegendCell(TimeLineController controller) {
LegendCell(TimeLineController controller) {
setEditable(false);
setController(controller);
this.controller = controller;
this.filteredEvents = this.controller.getEventsModel();
}
@Override
@ -119,15 +119,4 @@ class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> implement
});
}
}
@Override
synchronized public final void setController(TimeLineController controller) {
this.controller = controller;
setModel(this.controller.getEventsModel());
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
this.filteredEvents = filteredEvents;
}
}

View File

@ -18,19 +18,20 @@
*/
package org.sleuthkit.autopsy.timeline.zooming;
import java.net.URL;
import java.time.temporal.ChronoUnit;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Tooltip;
import javafx.util.StringConverter;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
@ -44,13 +45,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
* has sliders to provide context/control over three axes of zooming (timescale,
* event hierarchy, and description detail).
*/
public class ZoomSettingsPane extends TitledPane implements TimeLineView {
@FXML
private ResourceBundle resources;
@FXML
private URL location;
public class ZoomSettingsPane extends TitledPane {
@FXML
private Button backButton;
@ -105,35 +100,6 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
timeUnitLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.timeUnitLabel.text"));
zoomLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.zoomLabel.text"));
historyLabel.setText(NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.historyLabel.text"));
}
public ZoomSettingsPane() {
FXMLConstructor.construct(this, "ZoomSettingsPane.fxml"); // NON-NLS
}
@Override
synchronized public void setController(TimeLineController controller) {
this.controller = controller;
setModel(controller.getEventsModel());
descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
Back back = new Back(controller);
backButton.disableProperty().bind(back.disabledProperty());
backButton.setOnAction(back);
backButton.setTooltip(new Tooltip(
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.backButton.toolTip.text",
back.getAccelerator().getName())));
Forward forward = new Forward(controller);
forwardButton.disableProperty().bind(forward.disabledProperty());
forwardButton.setOnAction(forward);
forwardButton.setTooltip(new Tooltip(
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.forwardButton.toolTip.text",
forward.getAccelerator().getName())));
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
this.filteredEvents = filteredEvents;
initializeSlider(timeUnitSlider,
() -> {
@ -151,18 +117,14 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
});
initializeSlider(descrLODSlider,
() -> {
DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
if (controller.pushDescrLOD(newLOD) == false) {
descrLODSlider.setValue(new DescrLODConverter().fromString(filteredEvents.getDescriptionLOD().toString()));
}
}, this.filteredEvents.descriptionLODProperty(),
() -> {
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
});
initializeSlider(descrLODSlider, () -> {
DescriptionLoD newLOD = DescriptionLoD.values()[Math.round(descrLODSlider.valueProperty().floatValue())];
if (controller.pushDescrLOD(newLOD) == false) {
descrLODSlider.setValue(new DescrLODConverter().fromString(controller.getEventsModel().getDescriptionLOD().toString()));
}
}, this.filteredEvents.descriptionLODProperty(), () -> {
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
});
initializeSlider(typeZoomSlider,
() -> {
EventTypeZoomLevel newZoomLevel = EventTypeZoomLevel.values()[Math.round(typeZoomSlider.valueProperty().floatValue())];
@ -172,6 +134,26 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
() -> {
typeZoomSlider.setValue(this.filteredEvents.eventTypeZoomProperty().get().ordinal());
});
descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(VisualizationMode.COUNTS));
Back back = new Back(controller);
backButton.disableProperty().bind(back.disabledProperty());
backButton.setOnAction(back);
backButton.setTooltip(new Tooltip(
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.backButton.toolTip.text",
back.getAccelerator().getName())));
Forward forward = new Forward(controller);
forwardButton.disableProperty().bind(forward.disabledProperty());
forwardButton.setOnAction(forward);
forwardButton.setTooltip(new Tooltip(
NbBundle.getMessage(this.getClass(), "ZoomSettingsPane.forwardButton.toolTip.text",
forward.getAccelerator().getName())));
}
public ZoomSettingsPane(TimeLineController controller) {
this.controller = controller;
this.filteredEvents = controller.getEventsModel();
FXMLConstructor.construct(this, "ZoomSettingsPane.fxml"); // NON-NLS
}
/**

View File

@ -0,0 +1,34 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015 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 com.sun.javafx;
/**
* This class takes the place of the one in Java8 versions prior to u60. As of
* u60 com.sun.javafx.Utils was moved to the com.sun.javafx.util package and
* code, specifically ControlsFX, that depended on it broke. ControlsFX has
* removed their dependency on this class, but their fix will not be released
* until version 8.60.10 of ControlsFX. Until then, this shim class allows
* version 8.40.9 to run on Java 8u60. This class (and package) should and will
* be removed once we upgrade to ControlsFX 8.60.x.
*/
@Deprecated // DO NOT USE
public class Utils extends com.sun.javafx.util.Utils {
//Does nothing but expose com.sun.javafx.utila.Utils in the old package (com.sun.javafx.Utils)
}

View File

@ -49,10 +49,12 @@ import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Region;
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;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
@ -395,6 +397,9 @@ public final class ImageGalleryController {
tagsManager.clearFollowUpTagName();
tagsManager.unregisterListener(groupManager);
tagsManager.unregisterListener(categoryManager);
dbWorkerThread.cancelAllTasks();
dbWorkerThread = null;
restartWorker();
Toolbar.getDefault(this).reset();
groupManager.clear();
@ -418,7 +423,12 @@ public final class ImageGalleryController {
dbWorkerThread.addTask(innerTask);
}
@Nullable
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.");
return null;
}
return db.getFileFromID(fileID);
}
@ -587,7 +597,7 @@ public final class ImageGalleryController {
try {
InnerTask it = workQueue.take();
if (it.cancelled == false) {
if (it.isCancelled() == false) {
it.run();
}
@ -609,7 +619,7 @@ public final class ImageGalleryController {
/**
* Abstract base class for task to be done on {@link DBWorkerThread}
*/
static public abstract class InnerTask implements Runnable {
static public abstract class InnerTask implements Runnable, Cancellable {
public double getProgress() {
return progress.get();
@ -653,13 +663,13 @@ public final class ImageGalleryController {
protected InnerTask() {
}
protected volatile boolean cancelled = false;
public void cancel() {
@Override
synchronized public boolean cancel() {
updateState(Worker.State.CANCELLED);
return true;
}
protected boolean isCancelled() {
synchronized protected boolean isCancelled() {
return getState() == Worker.State.CANCELLED;
}
}
@ -693,7 +703,7 @@ public final class ImageGalleryController {
*/
static private class UpdateFileTask extends FileTask {
public UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
super(f, taskDB);
}
@ -720,7 +730,7 @@ public final class ImageGalleryController {
*/
static private class RemoveFileTask extends FileTask {
public RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
super(f, taskDB);
}
@ -756,7 +766,7 @@ public final class ImageGalleryController {
private final DrawableDB taskDB;
private final SleuthkitCase tskCase;
public CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
this.controller = controller;
this.taskDB = taskDB;
this.tskCase = tskCase;
@ -766,8 +776,8 @@ public final class ImageGalleryController {
+ StringUtils.join(FileTypeUtils.getAllSupportedExtensions(),
"' or name LIKE '%.")
+ "')";
static private final String MIMETYPE_CLAUSE
= "blackboard_attributes.value_text LIKE '"
static private final String MIMETYPE_CLAUSE =
"blackboard_attributes.value_text LIKE '"
+ StringUtils.join(FileTypeUtils.getAllSupportedMimeTypes(),
"' OR blackboard_attributes.value_text LIKE '") + "' ";
@ -801,7 +811,7 @@ public final class ImageGalleryController {
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
int units = 0;
for (final AbstractFile f : files) {
if (cancelled) {
if (isCancelled()) {
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
progressHandle.finish();
break;
@ -848,12 +858,12 @@ public final class ImageGalleryController {
} catch (TskCoreException ex) {
Logger.getLogger(CopyAnalyzedFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex);
}
}
progressHandle.finish();
updateMessage("");
updateProgress(-1.0);
controller.setStale(false);
}
}
@ -877,13 +887,13 @@ public final class ImageGalleryController {
// (name like '.jpg' or name like '.png' ...)
private final String DRAWABLE_QUERY = "(name LIKE '%." + StringUtils.join(FileTypeUtils.getAllSupportedExtensions(), "' OR name LIKE '%.") + "') ";
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("prepopulating image/video database");
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("prepopulating image/video database", this);
/**
*
* @param dataSourceId Data source object ID
*/
public PrePopulateDataSourceFiles(Content dataSource) {
PrePopulateDataSourceFiles(Content dataSource) {
super();
this.dataSource = dataSource;
}
@ -933,7 +943,7 @@ public final class ImageGalleryController {
DrawableDB.DrawableTransaction tr = db.beginTransaction();
int units = 0;
for (final AbstractFile f : files) {
if (cancelled) {
if (isCancelled()) {
LOGGER.log(Level.WARNING, "task cancelled: not all contents may be transfered to database");
progressHandle.finish();
break;
@ -951,7 +961,7 @@ public final class ImageGalleryController {
} catch (TskCoreException ex) {
Logger.getLogger(PrePopulateDataSourceFiles.class.getName()).log(Level.WARNING, "failed to transfer all database contents", ex);
}
}
progressHandle.finish();
}

View File

@ -31,7 +31,7 @@ import org.openide.util.Lookup;
*/
@OptionsPanelController.TopLevelRegistration(
categoryName = "#OptionsCategory_Name_Options",
iconBase = "org/sleuthkit/autopsy/imagegallery/images/polaroid_48_silhouette.png",
iconBase = "org/sleuthkit/autopsy/imagegallery/images/polaroid_32_silhouette.png",
keywords = "#OptionsCategory_Keywords_Options",
keywordsCategory = "Options",
position = 10

View File

@ -89,7 +89,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
return fileOpt;
} else {
try {
fileOpt = Optional.of(getController().getFileFromId(fileIDOpt.get()));
fileOpt = Optional.ofNullable(getController().getFileFromId(fileIDOpt.get()));
} catch (TskCoreException ex) {
Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex);
fileOpt = Optional.empty();
@ -206,7 +206,6 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
super.failed();
LOGGER.log(Level.SEVERE, "Failed to cache content for" + file.getName(), getException());
}
abstract void saveToCache(X result);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -158,7 +158,6 @@ public class Server {
public static final Charset DEFAULT_INDEXED_TEXT_CHARSET = Charset.forName("UTF-8"); ///< default Charset to index text as
private static final int MAX_SOLR_MEM_MB = 512; //TODO set dynamically based on avail. system resources
private Process curSolrProcess = null;
private static Ingester ingester = null;
static final String PROPERTIES_FILE = KeywordSearchSettings.MODULE_NAME;
static final String PROPERTIES_CURRENT_SERVER_PORT = "IndexingServerPort"; //NON-NLS
static final String PROPERTIES_CURRENT_STOP_PORT = "IndexingServerStopPort"; //NON-NLS
@ -291,13 +290,12 @@ public class Server {
@Override
public void run() {
InputStreamReader isr = new InputStreamReader(stream);
BufferedReader br = new BufferedReader(isr);
OutputStreamWriter osw = null;
BufferedWriter bw = null;
try {
osw = new OutputStreamWriter(out, PlatformUtil.getDefaultPlatformCharset());
bw = new BufferedWriter(osw);
try (InputStreamReader isr = new InputStreamReader(stream);
BufferedReader br = new BufferedReader(isr);
OutputStreamWriter osw = new OutputStreamWriter(out, PlatformUtil.getDefaultPlatformCharset());
BufferedWriter bw = new BufferedWriter(osw);) {
String line = null;
while (doRun && (line = br.readLine()) != null) {
bw.write(line);
@ -309,22 +307,7 @@ public class Server {
}
bw.flush();
} catch (IOException ex) {
logger.log(Level.WARNING, "Error redirecting Solr output stream"); //NON-NLS
} finally {
if (bw != null) {
try {
bw.close();
} catch (IOException ex) {
logger.log(Level.WARNING, "Error closing Solr output stream writer"); //NON-NLS
}
}
if (br != null) {
try {
br.close();
} catch (IOException ex) {
logger.log(Level.WARNING, "Error closing Solr output stream reader"); //NON-NLS
}
}
logger.log(Level.SEVERE, "Error redirecting Solr output stream", ex); //NON-NLS
}
}
}
@ -357,7 +340,7 @@ public class Server {
void killSolr() {
List<Long> solrPids = getSolrPIDs();
for (long pid : solrPids) {
logger.log(Level.INFO, "Trying to kill old Solr process, PID: " + pid); //NON-NLS
logger.log(Level.INFO, "Trying to kill old Solr process, PID: {0}", pid); //NON-NLS
PlatformUtil.killProcess(pid);
}
}
@ -399,22 +382,12 @@ public class Server {
}
}
logger.log(Level.INFO, "Starting Solr server from: " + solrFolder.getAbsolutePath()); //NON-NLS
logger.log(Level.INFO, "Starting Solr server from: {0}", solrFolder.getAbsolutePath()); //NON-NLS
if (isPortAvailable(currentSolrServerPort)) {
logger.log(Level.INFO, "Port [" + currentSolrServerPort + "] available, starting Solr"); //NON-NLS
logger.log(Level.INFO, "Port [{0}] available, starting Solr", currentSolrServerPort); //NON-NLS
try {
final String MAX_SOLR_MEM_MB_PAR = "-Xmx" + Integer.toString(MAX_SOLR_MEM_MB) + "m"; //NON-NLS
// String loggingPropertiesOpt = "-Djava.util.logging.config.file="; //NON-NLS
// String loggingPropertiesFilePath = instanceDir + File.separator + "conf" + File.separator; //NON-NLS
//
// if (DEBUG) {
// loggingPropertiesFilePath += "logging-development.properties"; //NON-NLS
// } else {
// loggingPropertiesFilePath += "logging-release.properties"; //NON-NLS
// }
// final String loggingProperties = loggingPropertiesOpt + loggingPropertiesFilePath;
List<String> commandLine = new ArrayList<>();
commandLine.add(javaPath);
commandLine.add(MAX_SOLR_MEM_MB_PAR);
@ -434,7 +407,7 @@ public class Server {
Path solrStderrPath = Paths.get(Places.getUserDirectory().getAbsolutePath(), "var", "log", "solr.log.stderr");
solrProcessBuilder.redirectError(solrStderrPath.toFile());
logger.log(Level.INFO, "Starting Solr using: " + solrProcessBuilder.command()); //NON-NLS
logger.log(Level.INFO, "Starting Solr using: {0}", solrProcessBuilder.command()); //NON-NLS
curSolrProcess = solrProcessBuilder.start();
logger.log(Level.INFO, "Finished starting Solr"); //NON-NLS
@ -447,7 +420,7 @@ public class Server {
}
final List<Long> pids = this.getSolrPIDs();
logger.log(Level.INFO, "New Solr process PID: " + pids); //NON-NLS
logger.log(Level.INFO, "New Solr process PID: {0}", pids); //NON-NLS
} catch (SecurityException ex) {
logger.log(Level.SEVERE, "Could not start Solr process!", ex); //NON-NLS
throw new KeywordSearchModuleException(
@ -526,7 +499,7 @@ public class Server {
}
try {
logger.log(Level.INFO, "Stopping Solr server from: " + solrFolder.getAbsolutePath()); //NON-NLS
logger.log(Level.INFO, "Stopping Solr server from: {0}", solrFolder.getAbsolutePath()); //NON-NLS
//try graceful shutdown
final String[] SOLR_STOP_CMD = {
@ -1278,6 +1251,7 @@ public class Server {
}
class ServerAction extends AbstractAction {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
@ -1289,11 +1263,12 @@ public class Server {
* Exception thrown if solr port not available
*/
class SolrServerNoPortException extends SocketException {
private static final long serialVersionUID = 1L;
/**
* the port number that is not available
*/
private int port;
private final int port;
SolrServerNoPortException(int port) {
super(NbBundle.getMessage(Server.class, "Server.solrServerNoPortException.msg", port,

View File

@ -1,5 +1,5 @@
#Updated by build script
#Tue, 13 Oct 2015 17:00:33 -0400
#Fri, 23 Oct 2015 09:28:23 -0400
LBL_splash_window_title=Starting Autopsy
SPLASH_HEIGHT=314
SPLASH_WIDTH=538

View File

@ -1,4 +1,4 @@
#Updated by build script
#Tue, 13 Oct 2015 17:00:33 -0400
#Fri, 23 Oct 2015 09:28:23 -0400
CTL_MainWindow_Title=Autopsy 3.1.3
CTL_MainWindow_Title_No_Project=Autopsy 3.1.3

View File

@ -30,9 +30,11 @@
<property name="win64.TskLib.path" value="${env.TSK_HOME}/win32/x64/Release_PostgreSQL"/>
<property name="win32.TskLib.path" value="${env.TSK_HOME}/win32/Release_PostgreSQL" />
<property name="win64.TskLib.postgres_path" value="${env.TSK_HOME}/win32/x64/Release_PostgreSQL"/>
<property name="win32.TskLib.postgres_path" value="${env.TSK_HOME}/win32/Release_PostgreSQL"/>
<available property="win64.TskLib.exists" type="dir" file="${win64.TskLib.path}" />
<available property="win32.TskLib.exists" type="dir" file="${win32.TskLib.path}" />
<available property="win64.TskLib_postgres.exists" type="dir" file="{win64.TskLib.postgres_path}" />
<available property="win32.TskLib_postgres.exists" type="dir" file="{win32.TskLib.postgres_path}" />
</target>
<!-- The following copy the libtsk_jni dependencies to the Autopsy
@ -50,6 +52,7 @@
<include name="ssleay32.dll"/>
<include name="libintl-8.dll"/>
<include name="libpq.dll"/>
<include name="msvcr120.dll"/>
</fileset>
<copy todir="${amd64}" overwrite="true">
@ -67,23 +70,35 @@
<target name="copyWinTskLibs32ToBaseDir" if="win32.TskLib.exists">
<fileset dir="${win32.TskLib.path}" id="win32dlls">
<include name="zlib.dll" />
<include name="libewf.dll"/>
<include name="libewf.dll"/>
</fileset>
<fileset dir="${win32.TskLib.postgres_path}" id="postgres32dlls">
<include name="libeay32.dll"/>
<include name="ssleay32.dll"/>
<include name="intl.dll"/>
<include name="libpq.dll"/>
<include name="msvcr120.dll"/>
</fileset>
<copy todir="${i386}" overwrite="true">
<fileset refid="win32dlls" />
<fileset refid="postgres32dlls" />
</copy>
<copy todir="${x86}" overwrite="true">
<fileset refid="win32dlls" />
<fileset refid="postgres32dlls" />
</copy>
<copy todir="${i586}" overwrite="true">
<fileset refid="win32dlls" />
<fileset refid="postgres32dlls" />
</copy>
<copy todir="${i686}" overwrite="true">
<fileset refid="win32dlls" />
<fileset refid="postgres32dlls" />
</copy>
</target>
@ -147,6 +162,4 @@
<fileset refid="crt64dlls"/>
</copy>
</target>
</project>

View File

@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy User Documentation"
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 3.1
PROJECT_NUMBER = 4.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@ -1025,7 +1025,7 @@ GENERATE_HTML = YES
# The default directory is: html.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_OUTPUT = 3.1
HTML_OUTPUT = 4.0
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
# generated HTML page (for example: .htm, .php, .asp).

View File

@ -16,6 +16,8 @@ The New Case wizard dialog will open and you will need to enter the case name an
\image html case-newcase.png
NOTE: You will only have the option of making a multi-user case if you have configured Autopsy with multi-user settings. See \ref install_multiuser_page for installation instructions and \ref creating_multi_user_cases for details on creating multi-user cases.
You will also be prompted for optional information, such as investigator name and case number.
After you create the case, you will be prompted to add a data source, as described in \ref ds_add.

View File

@ -25,6 +25,8 @@ Regardless of the type of data source, there are some common steps in the proces
\image html select-data-source-type.PNG
NOTE: If you are adding a data source to a multi-user case, ensure that all Autopsy clients will have access to the data source at the same path. We recommend using UNC paths to ensure this consistent mapping.
2) Autopsy will perform a basic examination of the data source and populate an embedded database with an entry for each file in the data source. No content is analyzed in the process, only the files are enumerated.
3) While it is examining the data source, you will be prompted with a list of ingest modules to enable.

View File

@ -1,61 +1,81 @@
/*! \page install_activemq Install and Configure ActiveMQ
To install ActiveMQ, perform the following steps:
1. You need a 32-bit or 64-bit version of the Java Runtime Environment (JRE) installed, depending upon the version of Autopsy you have installed. You can test this by running _where java_ from the command line. If you see output like the yellow results below, you have a JRE.
\section install_activemq_prereq Prerequisites
You will need:
- 64-bit version of the Java Runtime Environment (JRE) from http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html.
- Download ActiveMQ-5.11.1 from: http://activemq.apache.org/activemq-5111-release.html
\section install_activemq_install Installation
\subsection install_activemq_install_java JRE Installation
Install the Java JRE if needed. You can test this by running _where java_ from the command line. If you see output like the yellow results below, you have a JRE.
<br><br>
\image html wherejava.PNG
<br><br>
You can test if you have a 32-bit or 64-bit JRE installed via the following:
- _java -d32 -version_ &nbsp;&nbsp;&nbsp; for a 32-bit JRE
- _java -d64 -version_ &nbsp;&nbsp;&nbsp; for a 64-bit JRE
<br><br>
The screenshot below shows that there is a no 32-bit JRE on this machine, and there is a 64-bit JRE.
<br><br>
\image html JRE_bitness.PNG
<br><br>
If you do not have a JRE installed, proceed to step 2. If you have a JRE installed, proceed to step 3.
<br><br>
2. Install the appropriate 32 or 64-bit version of the JRE, depending upon the version of Autopsy you have installed. Download one from: http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html. If you installed 32-bit Autopsy, be sure to select a package that has "x86" in the name. If you installed 64-bit Autopsy, be sure to select a package that has "x64" in the name. Follow the installation prompts to install the JRE.
3. Download ActiveMQ-5.11.1 from: http://activemq.apache.org/activemq-5111-release.html
4. Extract the files in the archive
5. Edit <i>apache-activemq-5.11.1\\conf\\activemq.xml</i> to add <i>"&amp;wireFormat.maxInactivityDuration=0"</i> to the URI for the _transportConnector_ named _openwire_. Add the text highlighted in yellow below:
If you need the JRE, install it with the default settings.
\subsection install_activemq_install_mq ActiveMQ Installation
1. Extract the contents of the ActiveMQ archive folder to a location of your choice, bearing in mind that the files should be in a location that the running process will have write permissions to the folder. A typical folder choice is <i>C:\\Program Files\\apache-activemq-5.11.1</i>. Typically, it will ask for administrator permission to move the folder. Allow it if required.
2. Edit the <i>conf\\activemq.xml</i> in the extracted folder to add <i>"&amp;wireFormat.maxInactivityDuration=0"</i> to the URI for the _transportConnector_ named _openwire_. Add the text highlighted in yellow below:
<br><br>
\image html maxinactivityduration.PNG
<br><br>
6. Move the <i>apache-activemq-5.11.1</i> folder to a location of your choice, bearing in mind that the files should be in a location that the running process will have write permissions to the folder. A typical folder choice is <i>C:\\Program Files\\apache-activemq-5.11.1</i>. Typically, it will ask for administrator permission to move the folder. Allow it if required.
7. Install ActiveMQ as a service by navigating to the folder <i>apache-activemq-5.11.1-bin\\apache-activemq-5.11.1\\bin\\win64</i>, right-clicking _InstallService.bat_, clicking _Run as administrator_, then click _Yes_.
8. If you desire authentication for your ActiveMQ server (a good idea), the following directions allow you to set up credentials:
+ Copy and paste the following text to the file <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\groups.properties"</i>, overwriting the text highlighted in yellow in the screenshot below:
admins=system,sslclient,client,broker1,broker2
tempDestinationAdmins=system,user,sslclient,client,broker1,broker2
users=system,user,sslclient,client,broker1,broker2
guests=guest
<br><br>
\image html groups.properties.before.PNG
<br><br>
When complete, the file should look like this:
<br><br>
\image html groups.properties.after.PNG
<br><br>
+ Copy and paste the following text to the file <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\users.properties"</i>, overwriting the text highlighted in yellow in the screenshot below:
system=manager
user=password
guest=password
sslclient=CN=localhost, OU=activemq.org, O=activemq.org, L=LA, ST=CA, C=US
<br><br>
\image html users.properties.before.PNG
<br><br>
When complete, the file should look like this:
<br><br>
\image html users.properties.after.PNG
<br><br>
+ Copy and paste the following text to the file <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\activemq.xml"</i>, inserting the text at the line shown in yellow in the screenshot below.
3. Install ActiveMQ as a service by navigating to the folder <i>bin\\win64</i>, right-clicking _InstallService.bat_, clicking _Run as administrator_, then click _Yes_.
4. Start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link.
5. ActiveMQ should now be installed and configured using the default credentials. You should go to the next section to change the default passwords. To test your installation, you can access the admin pages in your web browser via a URL like this (set your host): http://localhost:8161/admin. The default administrator username is _admin_ with a password of _admin_ and the default regular username is _user_ with a default password of _password_. You can change these passwords by following the instructions below. If you can see a page that looks like the following, it is ready to function.
<br><br>
\image html activemq.PNG
<br><br>
If you do not see a screen like the above screenshot and you have double checked that the ActiveMQ service is running, contact your network administrator. For the ActiveMQ service to be accessible by network clients you may need to configure your Windows firewall (and any other 3rd party firewall in use) to allow communication.
\subsection install_activemq_install_pw Configuring Authentication
You can optionally add authentication to your ActiveMQ server. The ActiveMQ communications are not encrypted and contain basic messages between the systems about when new data has been found.
The following directions allow you to set up credentials:
1. Copy and paste the following text to the file <i>"conf\groups.properties"</i>, overwriting the text highlighted in yellow in the screenshot below:
<BLOCKQUOTE>
admins=system,sslclient,client,broker1,broker2<br />
tempDestinationAdmins=system,user,sslclient,client,broker1,broker2<br />
users=system,user,sslclient,client,broker1,broker2<br />
guests=guest<br />
</BLOCKQUOTE>
<br><br>
\image html groups.properties.before.PNG
<br><br>
When complete, the file should look like this:
<br><br>
\image html groups.properties.after.PNG
<br><br>
2. Copy and paste the following text to the file <i>"conf\users.properties"</i>, overwriting the text highlighted in yellow in the screenshot below:
<BLOCKQUOTE>
system=manager<br />
user=password<br />
guest=password<br />
sslclient=CN=localhost, OU=activemq.org, O=activemq.org, L=LA, ST=CA, C=US<br />
</BLOCKQUOTE>
<br><br>
\image html users.properties.before.PNG
<br><br>
When complete, the file should look like this:
<br><br>
\image html users.properties.after.PNG
<br><br>
3. Copy and paste the following text to the file <i>"conf\activemq.xml"</i>, inserting the text at the line shown in yellow in the screenshot below.
<plugins>
<jaasAuthenticationPlugin configuration="activemq-domain" />
<simpleAuthenticationPlugin>
@ -67,41 +87,28 @@ If you do not have a JRE installed, proceed to step 2. If you have a JRE install
</simpleAuthenticationPlugin>
</plugins>
<br><br>
\image html insertTextHere.PNG
<br><br>
After insertion, the file should look like the screenshot below, with the inserted portion highlighted in yellow. This is where you can change the username and password for your ActiveMQ setup.
<br><br>
\image html insertedText.PNG
<br><br>
To add a new user or change the password:
+ Stop the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Stop the service_ link.
<br>
<br>
\image html StopActiveMQService.PNG
<br>
<br>
+ Edit <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\activemq.xml"</i> adding the desired line. Both _username_ and _password_ are case sensitive. You will very likely want to keep your new users in the _users_ group.
<br>
<br>
\image html newUserAndPassword.PNG
<br>
<br>
+ Start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link.
<br>
<br>
\image html StartActiveMQService.PNG
<br>
<br>
9. If not already started, start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link.
10. ActiveMQ should now be fully installed and configured. You can access the admin pages in your web browser via a URL like this (set your host): http://localhost:8161/admin. The default administrator username is _admin_ with a password of _admin_ and the default regular username is _user_ with a default password of _password_. You can change these passwords by editing the file <i>"C:\Program Files\apache-activemq-5.11.1-bin\apache-activemq-5.11.1\conf\activemq.xml"</i> on the ActiveMQ server as discussed above. If you can see a page that looks like the following, it is ready to function.
<br><br>
\image html activemq.PNG
<br>
<br>
If you do not see a screen like the above screenshot and you have double checked that the ActiveMQ service is running, contact your network administrator. For the ActiveMQ service to be accessible by network clients you may need to configure your Windows firewall (and any other 3rd party firewall in use) to allow communication.
\image html insertTextHere.PNG
<br><br>
After insertion, the file should look like the screenshot below, with the inserted portion highlighted in yellow. This is where you can change the username and password for your ActiveMQ setup.
<br><br>
\image html insertedText.PNG
<br><br>
To add a new user or change the password:
1. Stop the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Stop the service_ link.
<br><br>
\image html StopActiveMQService.PNG
<br><br>
2. Edit <i>"conf\activemq.xml"</i> adding the desired line. Both _username_ and _password_ are case sensitive. You will very likely want to keep your new users in the _users_ group.
<br><br>
\image html newUserAndPassword.PNG
<br><br>
3. Start the ActiveMQ service by pressing _Start_, type _services.msc_, and press _Enter_. Find _ActiveMQ_ in the list and press the _Start the service_ link.
<br><br>
\image html StartActiveMQService.PNG
<br><br>
*/

View File

@ -0,0 +1,31 @@
/*! \page install_multiuser_page Setting Up Multi-user Environment
\section multiuser_install Multi-user Installation
Autopsy can be setup to work in an environment where multiple users on different computers can have the same case open at the same time. To set up this type of environment, you will need to configure additional (free and open source) network-based services.
\subsection multiuser_install_services Network-based Services
You will need the following that all Autopsy clients can access:
- Centralized storage that all clients running Autopsy have access to. The central storage should be either mounted at the same Windows drive letter or UNC paths should be used everywhere. All clients need to be able to access data using the same path.
- A central PostgreSQL database. A database will be created for each case and will be stored on the local drive of the database server. Installation and configuration is explained in \ref install_postgresql.
- A central Solr text index. A Solr core will be created for each case and will be stored in the case folder (not on the local drive of the Solr server). We recommend using Bitnami Solr. This is explained in \ref install_solr.
- An ActiveMQ messaging server to allow the various clients to communicate with each other. This service has minimal storage requirements. This is explained in \ref install_activemq.
When you setup the above services, write down the addresses, user names, and passwords or each so that you can configure each of the client systems afterwards.
We recommend using at least 2 dedicated computers for this additional infrastructure. Spreading the services out across several machines can improve throughput.
If possible, place Solr on a machine by itself, as it is the largest RAM and CPU utilizer among the servers.
Ensure that the central storage and PostgreSQL servers are regularly backed up.
\subsection multiuser_install_clients Autopsy Clients
Once the infrastructure is in place, you will need to configure Autopsy to use them.
- Install Autopsy on each client system as normal using the steps from \ref installation_page.
- Start Autopsy and open the multi-user options panel from “Tools”, “Options”, “Multi-user”. As shown in the screenshot below, you can then enter all of the address and authentication information for the network-based services. Note that in order to create or open Multi-user cases, "Enable Multi-user cases" must be checked and the settings below must be correct.
\image html multiuser_settings.PNG
*/

View File

@ -1,32 +1,42 @@
/*! \page install_solr Install and Configure Solr
A central Solr server is needed to store keyword indexes. To install Solr, perform the following steps:
1. You need a 32-bit or 64-bit version of the Java Runtime Environment (JRE) installed, depending upon the version of Autopsy you have installed. You can test this by running _where java_ from the command line. If you see output like the yellow results below, you have a JRE.
\section install_solr_prereq Prerequisites
You will need:
- 64-bit version of the Java Runtime Environment (JRE) from http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html.
- Download the Apache Solr 4.10.3-0 installation package from https://bitnami.com/stack/solr/installer.
- Access to an installed version of Autopsy so that you can copy files from it.
\section install_solr_install Installation
\subsection install_solr_install_java JRE Installation
Install the Java JRE if needed. You can test this by running _where java_ from the command line. If you see output like the yellow results below, you have a JRE.
<br><br>
\image html wherejava.PNG
<br><br>
You can test if you have a 32-bit or 64-bit JRE installed via the following:
- _java -d32 -version_ &nbsp;&nbsp;&nbsp; for a 32-bit JRE
- _java -d64 -version_ &nbsp;&nbsp;&nbsp; for a 64-bit JRE
<br><br>
The screenshot below shows that there is a no 32-bit JRE on this machine, and there is a 64-bit JRE.
<br><br>
\image html JRE_bitness.PNG
<br><br>
If you do not have a JRE installed, proceed to step 2. If you have a JRE installed, proceed to step 3.
<br><br>
2. Install the appropriate 32 or 64-bit version of the JRE, depending upon the version of Autopsy you have installed. Download one from: http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html. If you installed 32-bit Autopsy, be sure to select a package that has "x86" in the name. If you installed 64-bit Autopsy, be sure to select a package that has "x64" in the name. Follow the installation prompts to install the JRE.
3. Download the Apache Solr 4.10.3-0 installation package from https://bitnami.com/stack/solr/installer. The following steps will configure Solr to run using an account that will have access to the network storage.
4. Run the Bitnami installer, <i>bitnami-solr-4.10.3-0-windows-installer.exe</i>
5. If Windows prompts with User Account Control, click _Yes_
6. Follow the prompts through to completion. You do not need to "Learn more about Bitnami cloud hosting" so you can clear the check box.
7. If you see an error dialog like the following, you may safely ignore it.
If you need the JRE, install it with the default settings.
\subsection install_solr_install_solr Solr Installation
The following steps will configure Solr to run using an account that will have access to the network storage.
1. Run the Bitnami installer, <i>bitnami-solr-4.10.3-0-windows-installer.exe</i>
2. If Windows prompts with User Account Control, click _Yes_
3. Follow the prompts through to completion. You do not need to "Learn more about Bitnami cloud hosting" so you can clear the check box.
4. If you see an error dialog like the following, you may safely ignore it.
<br><br>
\image html apachebadmessage.PNG
<br>
8. When the installation completes, clear the "Launch Bitnami Apache Solr Stack Now?" checkbox and click _Finish_.
9. Stop _solrApache_ and _solrJetty_ services by pressing _Start_, typing _services.msc_, pressing _Enter_, and locating the _solrApache_ and _solrJetty_ Windows services. Select the services one at a time, and press _Stop the service_ once for each of them. If the service is already stopped and there is no _Stop the service_ available, this is okay.
10. Edit the <i>C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\scripts\\serviceinstall.bat</i> script. You need administrator permission to change this file. The easiest way around this is to save a copy on the Desktop, edit the Desktop version, and copy the new one back over the top of the old. Windows will ask for permission to overwrite the old file; allow it. You should make the following changes to this file:
5. When the installation completes, clear the "Launch Bitnami Apache Solr Stack Now?" checkbox and click _Finish_.
\subsection install_solr_config Solr Configuration
1. Stop _solrApache_ and _solrJetty_ services by pressing _Start_, typing _services.msc_, pressing _Enter_, and locating the _solrApache_ and _solrJetty_ Windows services. Select the services one at a time, and press _Stop the service_ once for each of them. If the service is already stopped and there is no _Stop the service_ available, this is okay.
2. Edit the <i>C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\scripts\\serviceinstall.bat</i> script. You need administrator permission to change this file. The easiest way around this is to save a copy on the Desktop, edit the Desktop version, and copy the new one back over the top of the old. Windows will ask for permission to overwrite the old file; allow it. You should make the following changes to this file:
<br>
<br>
- Add the following options in the _JvmOptions_ section of the line that begins with <i>"C:\Bitnami\solr-4.10.3-0/apache-solr\scripts\prunsrv.exe"</i> :
@ -49,7 +59,7 @@ If you do not have a JRE installed, proceed to step 2. If you have a JRE install
<br><br>
\image html updatedServiceInstall.PNG
<br><br>
11. Edit <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\solr.xml"</i> to set the _transientCacheSize_ to the maximum number of cases expected to be open concurrently. If you expect ten concurrent cases, the text to add is
3. Edit <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\solr.xml"</i> to set the _transientCacheSize_ to the maximum number of cases expected to be open concurrently. If you expect ten concurrent cases, the text to add is
<i>\<int name="transientCacheSize">10\</int></i>
<br><br>
The added part is highlighted in yellow below. Ensure that it is inside the <i>\<solr></i> tag as follows:
@ -58,7 +68,7 @@ The added part is highlighted in yellow below. Ensure that it is inside the <i>\
<br>
Again you may have trouble saving to the file in the current location. If so, just save it out to the desktop and copy the edited file back over the top of the original.
<br><br>
12. Edit <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\resources/log4j.properties"</i> to configure Solr log settings:
4. Edit <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\resources/log4j.properties"</i> to configure Solr log settings:
- Increase the log rotation size threshold (_log4j\.appender\.file\.MaxFileSize_) from 4MB to 100MB.
- Remove the _CONSOLE_ appender from the _log4j\.rootLogger_ line.
<br><br>
@ -66,17 +76,17 @@ The log file should end up looking like this (modified lines are highlighted in
<br><br>
\image html log4j.PNG
<br><br>
13. Edit the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg"</i> to increase the _tickTime_ value to 15000 as shown in the screenshot below.
5. Edit the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg"</i> to increase the _tickTime_ value to 15000 as shown in the screenshot below.
<br><br>
\image html tickTime.PNG
<br><br>
14. Edit the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg"</i> to set the value <i>dataDir=C:/Bitnami/zookeeper</i> as shown in the screenshot below.
6. Edit the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg"</i> to set the value <i>dataDir=C:/Bitnami/zookeeper</i> as shown in the screenshot below.
<br><br>
\image html dataDir.PNG
<br><br>
15. Copy the folder _configsets_ from your Autopsy installation (<i>"C:\Program Files (x86)\Autopsy-4.0\autopsy\solr\solr"</i> for 32-bit or <i>"C:\Program Files\Autopsy-4.0\autopsy\solr\solr"</i> for 64-bit) to <i>"C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\solr"</i>
16. Copy the folder _lib_ from your Autopsy installation (<i>"C:\Program Files (x86)\Autopsy-4.0\autopsy\solr\solr"</i> for 32-bit or <i>"C:\Program Files\Autopsy-4.0\autopsy\solr\solr"</i> for 64-bit) to <i>"C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\solr"</i>
17. Right-click on the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat"</i> and click "Run As Administrator", selecting _Yes_ if prompted by User Account Control.
7. From an Autopsy installation, copy the folder <i>"C:\Program Files\Autopsy-4.0\autopsy\solr\solr\configsets"</i> to <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr"</i>.
8. From an Autopsy installation, copy the folder <i>"C:\Program Files\Autopsy-4.0\autopsy\solr\solr\lib"</i> to <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\solr"</i>.
9. Right-click on the file <i>"C:\Bitnami\solr-4.10.3-0\apache-solr\scripts\serviceinstall.bat"</i> and click "Run As Administrator", selecting _Yes_ if prompted by User Account Control.
If there is no "Run as administrator" option when you right-click the _serviceinstall.bat_ file, start a Windows command prompt as administrator by pressing _Start_, typing _command_, right clicking on _Command Prompt_, and clicking on _Run as administrator_. Then run the following command to install the _solrJetty_ service:
<br><br>
@ -86,13 +96,13 @@ The log file should end up looking like this (modified lines are highlighted in
<br><br>
\image html solrinstall1.PNG
<br><br>
18. Press _Start_, type _services.msc_, and press _Enter_. Find _solrJetty_. If the service is running, press _Stop the service_, then double click it, and switch to the _Log On_ tab to change the logon credentials to a user who will have access to read and write the primary shared drive. If the machine is on a domain, the Account Name will be in the form of _DOMAINNAME\\username_ as shown in the example below. Note that in the screenshot below, the domain name is _DOMAIN_ and the user name is _username_. These are just examples, not real values.
10. Press _Start_, type _services.msc_, and press _Enter_. Find _solrJetty_. If the service is running, press _Stop the service_, then double click it, and switch to the _Log On_ tab to change the logon credentials to a user who will have access to read and write the primary shared drive. If the machine is on a domain, the Account Name will be in the form of _DOMAINNAME\\username_ as shown in the example below. Note that in the screenshot below, the domain name is _DOMAIN_ and the user name is _username_. These are just examples, not real values.
<br><br>
\image html solrinstall2.PNG
<br>
If the machine is on a domain, **make sure** to select the domain with the mouse by going to the _Log On_ tab, clicking _Browse_, then clicking _Locations_ and selecting the domain of interest. Then enter the user name desired and press _Check Names_. When that completes, press _OK_, type in the password once for each box and press _OK_. You may see "The user has been granted the log on as a service right."
19. You should be able to see the Solr service in a web browser via the URL <i>http://localhost:8983/solr/#/</i> as shown in the screenshot below.
11. You should be able to see the Solr service in a web browser via the URL <i>http://localhost:8983/solr/#/</i> as shown in the screenshot below.
<br><br>
\image html solrinstall3.PNG
<br><br>

View File

@ -5,68 +5,28 @@
\section prereqs Prerequisites
It is _highly_ recommended to remove or disable any antivirus software from computers that will be processing or reviewing cases. Antivirus software will often conflict with forensic software, and may quarantine or even delete some of your results before you get a chance to look at them.
<br><br>
\section install Deployment Types
Starting with Autopsy 4.0, there are two ways to deploy Autopsy:
- **Single-User**: Cases can be open by only a single instance of Autopsy at a time. Autopsy installations do not communicate with each other. This is the easiest to install and deploy. This page outlines that installation process.
- **Multi-User**: Cases can be open by multiple users at the same time and users can see what each other is doing. This collaborative deployment requires installation and configuration of other network-based services. The installation of this deployment is covered in \ref install_multiuser_page.
\section download Download
Download Autopsy from the website:
http://sleuthkit.org/autopsy/download.php
The current version of Autopsy 3 runs only on Microsoft Windows.
We have gotten older versions to run on other platforms, such as Linux and OS X, but we do not have it in a state that makes it easy to distribute and find the needed libraries.
<br><br>
The current version of Autopsy 4 is distributed on sleuthkit.org only as a Windows installer. It can run on Linux and OS X, but requires some manual setup.
\section installation_section Installation
\section install_standalone Installation
To install Autopsy, perform the following steps:
1. Run the Autopsy _msi_ file
2. If Windows prompts with User Account Control, click _Yes_
3. Click through the dialog boxes until you click a button that says _Finish_
4. Core Autopsy should now be fully installed
<br>
\section deployment_types Deployment Types
There are two types of cases that Autopsy can create and use:
- **Standalone**: A single user with a single computer. Not intended to have multiple examiners working on the same case.
- **Collaborative**: A multi-user environment with multiple computers. Multiple examiners can work on the same case at the same time.
Both deployment types use the same analysis modules and the same base installer.
<br><br>
\subsection standalone_install Standalone (Single User) Installation
1. Install Autopsy as explained in \ref installation_section above. The Windows installer is self-contained and will place everything in the needed places. Simply follow the standard prompts for installation.
<br>
\subsection collab_install Collaborative (Multi-user) Installation
To use the Multi-user collaboration feature, three additional software packages are required. These packages install servers that need to be accessible to machines running Autopsy collaborative cases via the network. These servers do not have to be installed on the same machine as Autopsy, nor on the same machine as each other.
1. Install Autopsy just as in \ref standalone_install above
2. Install and configure Apache ActiveMQ on a machine accessible to Autopsy nodes. This is explained in \ref install_activemq.
3. Install and configure Bitnami Solr on a machine accessible to Autopsy nodes. This is explained in \ref install_solr.
4. Install and configure PostgreSQL on a machine accessible to Autopsy nodes. This is explained in \ref install_postgresql.
5. Configure Multi-user settings. This is explained in \ref multi_user_options.
<br>
While you may run all of the external services (ActiveMQ, Solr, and PostgreSQL) on the same machine that is running Autopsy, this is not ideal. Spreading the services out across several machines can improve throughput. Keep in mind that all the machines need to be able to communicate with each other and see the shared drive over the network.
If possible, place Solr on a machine by itself, as it is the largest RAM and CPU utilizer among the servers.
<br><br>
To use Multi-user cases, there needs to be a shared network drive accessible to all participating computers.
<br><br>
\section optimizing_performance Optimizing Performance
After installing Autopsy, there are several hardware-based things that we suggest you do to optimize performance:
1. Change the number of parallel pipelines used at run time. The default is two pipelines, but this can be increased if you are running on a system with several cores. To do this:
- Run Autopsy from the Start Menu or desktop
- When presented with the case creation splash screen, cancel/close the window
- Select "Tools", "Options"
- On the "Autopsy" tab, there is a drop down for _Number of threads to use for file ingest_. We recommend you set this value 4. If you set this number too high, performance can degrade because the pipelines are fighting for the same physical resources. Individual testing should be done to find an optimal setting.
- After each change, restart Autopsy to let this setting take effect.
<br><br>
\image html threadcount.PNG
<br><br>
2. In the screenshot above, there is an option to <i>Enable timeout to allow modules to automatically terminate after a set amount of time</i>. Enabling this feature by applying a checkmark and setting a number of hours puts a maximum amount of time an individual module may attempt to process before being stopped. If enabled and a module attempts to run for longer than this value, Autopsy stops the module and moves on to process the next module. This allows processing to continue even if a rogue module does not end appropriately in a reasonable amount of time.
<br><br>
3. When making a case, use different drives to store the case and the images. The case directory is where the SQLite database and keyword search index are stored in Single-user cases. This allows the maximum amount of data to be read and written at the same time. If using collaborative Multi-user mode, it is important that UNC paths are used to specifiy drive names. Fully-specified UNC paths should be in the form of <i>\\\\hostname\\sharename\\folder</i>.
<br><br>
4. We have had best performance using either solid state drives or fibre channel-attached SAN storage.
4. Autopsy should now be fully installed
*/

View File

@ -6,7 +6,7 @@ Overview
This is the User's Guide for the <a href="http://www.sleuthkit.org/autopsy/">open source Autopsy platform</a>. Autopsy allows you to examine a hard drive or mobile device and recover evidence from it. This guide should help you with using Autopsy. The <a href="http://www.sleuthkit.org/autopsy/docs/api-docs/3.1/"> developer's guide</a> will help you develop your own Autopsy modules.
Autopsy 3 is a complete rewrite from Autopsy 2, and none of this document is relevant to Autopsy 2.
Autopsy 4 (and 3) are a complete rewrite from Autopsy 2, and none of this document is relevant to Autopsy 2.
Help Topics
-------
@ -15,11 +15,6 @@ The following topics are available here:
- \subpage installation_page
- \subpage quick_start_guide "Quick Start Guide"
- \subpage workflow_page
- Multi-user Collaboration Setup
- \subpage install_activemq
- \subpage install_postgresql
- \subpage install_solr
- \subpage multiuser_page
- Cases and Adding Data Sources
- \subpage cases_page
- \subpage ds_page
@ -42,14 +37,21 @@ The following topics are available here:
- \subpage tree_viewer_page
- \subpage result_viewer_page
- \subpage content_viewer_page
<!-- - \subpage image_gallery_page Not released yet, coming soon-->
- \subpage image_gallery_page
- \subpage file_search_page
- \subpage timeline_page
- \subpage stix_page
- Reporting
- \subpage tagging_page
- \subpage reporting_page
- \subpage tagging_page
- \subpage reporting_page
- \subpage module_install_page
- \subpage performance_page
- Multi-user Collaborative Deployments
- \subpage install_multiuser_page
- \subpage install_activemq
- \subpage install_postgresql
- \subpage install_solr
- \subpage multiuser_page
If the topic you need is not listed, refer to the <a href="http://wiki.sleuthkit.org/index.php?title=Autopsy_User%27s_Guide">Autopsy Wiki</a> or join the <a href="https://lists.sourceforge.net/lists/listinfo/sleuthkit-users">SleuthKit User List</a> at SourceForge.

View File

@ -1,38 +1,42 @@
/*! \page multiuser_page Using Multi-user Collaboration
/*! \page multiuser_page Using Multi-user Cases
\section creating_multi_user_cases Creating Multi-user cases
The collaborative Multi-user capabilities enable Autopsy cases to be opened by multiple reviewers at the same time allowing simultaneous case review with multiple reviewers.
Multi-user cases allow multiple instances of Autopsy to have the same case open at the same time. When creating a case, users are now presented with a choice of Single-user or Multi-user as shown in the screenshot below.
When creating a case, users are now presented with a choice of Single-user or Multi-user as shown in the screenshot below.
<br><br>
\image html case-newcase.PNG
<br><br>
Single-user functions the same as always, with a back end SQLite database and a machine-local version of Solr.
Multi-user allows multiple computers to open the same case at the same time. In order to accomplish this, some setup needs to take place. To use a Multi-user setup, one must install PostgreSQL, Solr, and ActiveMQ (see \subpage installation_page) on machines connected to the network, and properly configure Autopsy to interact with these services. This configuration is done in the Multi-user options panel, discussed below.
<br><br>
To create a multi-user case, the following must occur:
- The network services must be installed, configured, and running. See \ref multiuser_install_services.
- The Case folder needs to be in a shared folder that all other clients can also access at the same path (UNC or drive letter).
- The data sources that are added with the Add Data Source wizard must be in a shared folder that all clients can access at the same path.
\section multi_user_other Other Multi-user Information
- When using a multi-user case, other nodes could be running data ingest on the same case. While this is happening, you will see a progress bar labelled with the hostname of the machine performing the ingest on the bottom right of Autopsy. The progress bar will continue to move back and forth until ingest has been completed or cancelled. You can still run ingest on your local machine while this is ongoing. This is shown in the screenshot below.
\section multi_user_options Multi-user options panel
As shown in the screenshot below, all three services need configuration of IP addresses and ports, and two of them need user names and passwords. Enter the correct information into the dialog and press okay. Note that in order to create or open Multi-user cases, "Enable Multi-user cases" must be checked and the settings below must be correct.
<br><br>
\image html multiuser_settings.PNG
<br><br>
Other Multi-user Information
=======
- When using a case in collaborative Multi-user mode, other nodes could be running data ingest. While this is happening, you will see a progress bar labelled with the hostname of the machine performing the ingest on the bottom right of Autopsy. The progress bar will continue to move back and forth until ingest has been completed or cancelled. You can still run ingest on your local machine while this is ongoing. This is shown in the screenshot below.
<br><br>
\image html othernodeingesting.PNG
<br><br>
- When issues occur, there is an information "bubble" on the bottom right of the screen. It has an "i" inside a circle, with the color of the circle changed based upon the message. It uses red for bad and blue for good. See the screenshot below.
- When issues occur, there is an information "bubble" on the bottom right of the screen. It has an "i" inside a circle, with the color of the circle changed based upon the message. It uses red for bad and blue for good. See the screenshot below.
<br><br>
\image html messagebubbles.PNG
<br><br>
- Clicking on the information "bubble" brings up the list of prior notifications that have not been dismissed by clicking on the "x". As you can see in the screenshot below, the network cable was unplugged from the machine and it lost all connection to the three services. When the cable was reconnected, it found the services again.
- Clicking on the information "bubble" brings up the list of prior notifications that have not been dismissed by clicking on the "x". As you can see in the screenshot below, the network cable was unplugged from the machine and it lost all connection to the three services. When the cable was reconnected, it found the services again.
<br><br>
\image html messagebubblesbigger.PNG
<br><br>
- While using collaborative Multi-user mode, it is important that UNC paths are used to specifiy drive names. Drive mapping will work, but it is sometimes difficult to get all the machines participating in a case to map to the same drive letters for the same resources. It is much simpler to use fully-specified UNC paths in the form of <i>\\\\hostname\\sharename\\folder</i>.
- When creating multi-user cases, we recommend using UNC paths to specify drive names. Drive mapping will work, but it is sometimes difficult to get all the machines participating in a case to map to the same drive letters for the same resources. It is much simpler to use fully-specified UNC paths in the form of <i>\\\\hostname\\sharename\\folder</i>.
*/

View File

@ -0,0 +1,19 @@
/*! \page performance_page Optimizing Performance
After installing Autopsy, there are several hardware-based things that we suggest you do to optimize performance:
1. Number of Threads: Change the number of parallel pipelines used at run time. The default is two pipelines, but this can be increased if you are running on a system with several cores. To do this:
- Run Autopsy from the Start Menu or desktop
- When presented with the case creation splash screen, cancel/close the window
- Select "Tools", "Options"
- On the "Autopsy" tab, there is a drop down for _Number of threads to use for file ingest_. We recommend you set this value 4. If you set this number too high, performance can degrade because the pipelines are fighting for the same physical resources. Individual testing should be done to find an optimal setting.
- After each change, restart Autopsy to let this setting take effect.
<br><br>
\image html threadcount.PNG
<br><br>
2. When making a case, use different drives to store the case and the images. This allows the maximum amount of data to be read and written at the same time.
3. We have had best performance using either solid state drives or fibre channel-attached SAN storage.
*/

View File

@ -38,7 +38,7 @@ PROJECT_NAME = "Autopsy"
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 3.1
PROJECT_NUMBER = 4.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
@ -1063,7 +1063,7 @@ GENERATE_HTML = YES
# The default directory is: html.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_OUTPUT = api-docs/3.1/
HTML_OUTPUT = api-docs/4.0/
# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
# generated HTML page (for example: .htm, .php, .asp).
@ -2058,7 +2058,7 @@ SKIP_FUNCTION_MACROS = YES
# the path). If a tag file is not located in the directory in which doxygen is
# run, you must also specify the path to the tagfile here.
TAGFILES = $(TSK_HOME)/tskjni_doxygen.tag=http://www.sleuthkit.org/sleuthkit/docs/jni-docs/
TAGFILES = $(TSK_HOME)/bindings/java/doxygen/tskjni_doxygen.tag=http://www.sleuthkit.org/sleuthkit/docs/jni-docs/4.3/
# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
# tag file that is based on the input files it reads. See section "Linking to

View File

@ -26,17 +26,25 @@ There are also a set of tutorials that Basis Technology published on their blog:
-You don't really need anything to develop a python Autopsy module except for the standard Autopsy and your favorite text editor. We recommend IntelliJ IDEA or the Jython plug-in to NetBeans.
To install NetBeans' plug-in:
-# Download and install the Jython 2.7 installer (http://www.jython.org/downloads.html).
-# Download and install the Jython 2.7 installer to desired location (http://www.jython.org/downloads.html).
-# Download NetBeans Python plug-in zip file (http://plugins.netbeans.org/plugin/56795/python4netbeans802).
-# Unpack the content (.nbm files) of the zip file to the desired location.
-# In NetBeans go to Tools->Plugins. In Download tab, click on Add Plugins, then choose extracted .nbm files.
-# Setup Jython path from Tools->Python Platform, click on new, then choose Jython.exe (usually in C:/Program files/Jython2.7/bin/)
-# In NetBeans go to Tools->Plugins. In Downloaded tab, click on Add Plugins, then choose extracted .nbm files.
-# Setup Jython path from Tools->Python Platforms, click on new, then choose Jython.exe (usually in C:/Program files/Jython2.7/bin/)
To install IntelliJ IDEA + Python plug-in:
-# Download and install IDEA https://www.jetbrains.com/idea/download/
-# In File->Settings->Plugins-> install Python Community Edition
-# In File->Project Structure->Project-> Project SDK-> choose IntelliJ IDEA Community Edition
-# In Libraries->add new libraries->choose desired autopsy modules (usually in C:\Program Files\Autopsy-3.1.3\autopsy\modules)
-# Download java JDK depending on platform. Install to desired location (http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html).
-# Download and install IDEA Community Edition to desired location (https://www.jetbrains.com/idea/download/).
-# Open IDEA and choose desired UI theme. Continue with default settings.
-# Choose to either create a new empty project or open an existing one.
-# It will ask you to modify Project Structure. Leave that for now and click OK.
-# In File->Settings. Go to Plugins tab and click on Install JetBrains Plugin.
-# Look for and install Python Community Edition. After the installation, it will ask you restart. Restart IDEA.
-# In File->Project Structure. In Project tab, Project SDK, click on New and choose IntelliJ Platform Plugin SKD.
-# It will ask you to configure the JKD first, click OK and navigate to the JDK folder location and click OK.
-# After that it will ask you to choose the IntelliJ Platform Plugin SKD. It will most likely take you to it's locaation automatically. (Usually in C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 14.1.5)
-# In the drop down menu next to New button, choose IntelliJ IDEA Community Edition.
-# Still in Project STructure, In Libraries tab, click on '+' to add new libraries. Choose desired autopsy modules (usually in C:\Program Files\Autopsy-3.1.3\autopsy\modules if you have executable version).
\section mod_dev_py_create Creating a Basic Python Module

View File

@ -8,14 +8,15 @@ The easiest guidance (from http://bits.netbeans.org/dev/javadoc/org-openide-modu
\section native_autopsy Autopsy Native Libraries
Autopsy has two types of native libraries:
- The libtsk_jni native library
- The libraries that libtsk_jni depends on
<tt>libtsk_jni</tt> is embedded inside of the <tt>Tsk_DataModel.jar</tt> file from The Sleuth Kit. The java code extracts the file from the jar into a local temp folder and launches it in the LibraryUtils.loadSleuthkitJNI() method.
The libraries that <tt>libtsk_jni</tt> depends on are launched by Autopsy in the Installer.loadDynLibraries() method. This is because if we wait until <tt>libtsk_jni</tt> needs them, then they will be located based on Windows search paths and the NetBeans paths are not in that set. So, we launch them before <tt>libtsk_jni</tt> needs them and from within Autopsy so that it uses the Autopsy search pathes.
There is code in build-windows.xml and build-unix.xml to copy the external libraries into their respective locations when a ZIP package is made of the program. These libraries must be accessible via normal launching methods when developing Autopsy (i.e. we only copy them into the Autopsy structure when building the ZIP).
Autopsy has three types of native libraries that it depends on (and ships with):
- The Sleuth Kit JNI Library(libtsk_jni)
- This is embedded inside of the <tt>Tsk_DataModel.jar</tt> file. It gets copied into there by the datamodel ant target.
- At runtime, java code extracts the file from the jar into a local temp folder and launches it in the LibraryUtils.loadSleuthkitJNI() method.
- The libraries that libtsk_jni depends on that are not the Visual Studio runtime dlls that were used to build libtsk_jni
- These are copied by Autopsy from TSK_HOME to a folder inside of Autopsy at compile time.
- They are loaded by Autopsy in the Installer.loadDynLibraries() method. This is because if we wait until <tt>libtsk_jni</tt> needs them, then they will be located based on Windows search paths and the NetBeans paths are not in that set. So, we launch them before <tt>libtsk_jni</tt> needs them and from within Autopsy so that it uses the Autopsy search paths.
- The Visual Studio runtime dlls that were used to build libtsk_jni
- These are only copied by Autopsy when a ZIP folder is created of the project (on the theory that the local system doesn't need copies of them because it is already building TSK). There is code in build-windows.xml and build-unix.xml to copy the external libraries (from thidrparty/crt) into their respective locations in the ZIP file.
- NOTE: If other versions of the runtimes need to be included (because of the other 3rd party libraries), then they should be treated like other 3rd party libraries and copied at compile time.
*/

View File

@ -1,4 +1,5 @@
branding.token=autopsy
nbjdk.active=JDK_1.8_66
# Version of platform that is automatically downloaded
# Note build.xml has similar definitions that should be kept in sync (manually)
netbeans-plat-version=8.0.2
@ -13,15 +14,12 @@ cluster.path=\
${nbplatform.active.dir}/java:\
${nbplatform.active.dir}/platform
disabled.modules=\
org.apache.tools.ant.module,\
org.netbeans.api.debugger.jpda,\
org.netbeans.api.java,\
org.netbeans.api.maven,\
org.netbeans.lib.nbjavac,\
org.netbeans.libs.cglib,\
org.netbeans.libs.javacapi,\
org.netbeans.libs.javacimpl,\
org.netbeans.libs.javafx,\
org.netbeans.libs.springframework,\
org.netbeans.modules.ant.browsetask,\
org.netbeans.modules.ant.debugger,\
@ -33,7 +31,6 @@ disabled.modules=\
org.netbeans.modules.dbschema,\
org.netbeans.modules.debugger.jpda,\
org.netbeans.modules.debugger.jpda.ant,\
org.netbeans.modules.debugger.jpda.js,\
org.netbeans.modules.debugger.jpda.kit,\
org.netbeans.modules.debugger.jpda.projects,\
org.netbeans.modules.debugger.jpda.ui,\
@ -46,8 +43,6 @@ disabled.modules=\
org.netbeans.modules.form.nb,\
org.netbeans.modules.form.refactoring,\
org.netbeans.modules.hibernate,\
org.netbeans.modules.hibernate4lib,\
org.netbeans.modules.hibernatelib,\
org.netbeans.modules.hudson.ant,\
org.netbeans.modules.hudson.maven,\
org.netbeans.modules.i18n,\
@ -69,21 +64,16 @@ disabled.modules=\
org.netbeans.modules.java.examples,\
org.netbeans.modules.java.freeform,\
org.netbeans.modules.java.guards,\
org.netbeans.modules.java.helpset,\
org.netbeans.modules.java.hints,\
org.netbeans.modules.java.hints.declarative,\
org.netbeans.modules.java.hints.declarative.test,\
org.netbeans.modules.java.hints.legacy.spi,\
org.netbeans.modules.java.hints.test,\
org.netbeans.modules.java.hints.ui,\
org.netbeans.modules.java.j2sedeploy,\
org.netbeans.modules.java.j2seembedded,\
org.netbeans.modules.java.j2seplatform,\
org.netbeans.modules.java.j2seprofiles,\
org.netbeans.modules.java.j2seproject,\
org.netbeans.modules.java.kit,\
org.netbeans.modules.java.lexer,\
org.netbeans.modules.java.metrics,\
org.netbeans.modules.java.navigation,\
org.netbeans.modules.java.platform,\
org.netbeans.modules.java.preprocessorbridge,\
@ -95,7 +85,6 @@ disabled.modules=\
org.netbeans.modules.java.sourceui,\
org.netbeans.modules.java.testrunner,\
org.netbeans.modules.javadoc,\
org.netbeans.modules.javaee.injection,\
org.netbeans.modules.javawebstart,\
org.netbeans.modules.jellytools.java,\
org.netbeans.modules.junit,\
@ -116,8 +105,6 @@ disabled.modules=\
org.netbeans.modules.maven.repository,\
org.netbeans.modules.maven.search,\
org.netbeans.modules.maven.spring,\
org.netbeans.modules.nashorn.execution,\
org.netbeans.modules.performance,\
org.netbeans.modules.performance.java,\
org.netbeans.modules.projectimport.eclipse.core,\
org.netbeans.modules.projectimport.eclipse.j2se,\
@ -130,8 +117,6 @@ disabled.modules=\
org.netbeans.modules.websvc.jaxws21,\
org.netbeans.modules.websvc.jaxws21api,\
org.netbeans.modules.websvc.saas.codegen.java,\
org.netbeans.modules.whitelist,\
org.netbeans.modules.xml.jaxb,\
org.netbeans.modules.xml.tools.java,\
org.netbeans.spi.java.hints

Binary file not shown.