mirror of
https://github.com/elisspace/autopsy.git
synced 2026-10-02 07:19:53 +00:00
Merge branch 'develop' of github.com:sleuthkit/autopsy into updateDocs
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
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
|
||||
@@ -85,14 +85,14 @@ public class Installer extends ModuleInstall {
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
logger.log(Level.SEVERE, "Error loading EWF library, ", e); //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
System.loadLibrary("libvmdk"); //NON-NLS
|
||||
logger.log(Level.INFO, "VMDK library loaded"); //NON-NLS
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
logger.log(Level.SEVERE, "Error loading VMDK library, ", e); //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
System.loadLibrary("libvhdi"); //NON-NLS
|
||||
logger.log(Level.INFO, "VHDI library loaded"); //NON-NLS
|
||||
@@ -107,7 +107,7 @@ public class Installer extends ModuleInstall {
|
||||
} 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
|
||||
@@ -122,18 +122,20 @@ public class Installer extends ModuleInstall {
|
||||
logger.log(Level.SEVERE, "Error loading SSLEAY32 library, ", e); //NON-NLS
|
||||
}
|
||||
|
||||
// This library name is different in 32-bit versus 64-bit
|
||||
String libintlName = "libintl-8"; //NON-NLS
|
||||
if (PlatformUtil.is64BitJVM() == false) {
|
||||
libintlName = "intl"; //NON-NLS
|
||||
}
|
||||
try {
|
||||
System.loadLibrary(libintlName); //NON-NLS
|
||||
logger.log(Level.INFO, libintlName + " library loaded"); //NON-NLS
|
||||
System.loadLibrary("libiconv-2"); //NON-NLS
|
||||
logger.log(Level.INFO, "libiconv-2 library loaded"); //NON-NLS
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
logger.log(Level.SEVERE, "Error loading " + libintlName + " library, ", e); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Error loading libiconv-2 library, ", e); //NON-NLS
|
||||
}
|
||||
|
||||
try {
|
||||
System.loadLibrary("libintl-8"); //NON-NLS
|
||||
logger.log(Level.INFO, "libintl-8 library loaded"); //NON-NLS
|
||||
} catch (UnsatisfiedLinkError e) {
|
||||
logger.log(Level.SEVERE, "Error loading libintl-8 library, ", e); //NON-NLS
|
||||
}
|
||||
|
||||
try {
|
||||
System.loadLibrary("libpq"); //NON-NLS
|
||||
logger.log(Level.INFO, "LIBPQ library loaded"); //NON-NLS
|
||||
@@ -156,7 +158,7 @@ public class Installer extends ModuleInstall {
|
||||
/**
|
||||
* Check if JavaFx initialized
|
||||
*
|
||||
* @return false if java fx not initialized (classes coult not load), true
|
||||
* @return false if java fx not initialized (classes could not load), true
|
||||
* if initialized
|
||||
*/
|
||||
public static boolean isJavaFxInited() {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2014 Basis Technology Corp.
|
||||
*
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* 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.
|
||||
@@ -18,12 +18,15 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.Action;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Sheet;
|
||||
import org.openide.util.Lookup;
|
||||
@@ -31,6 +34,9 @@ import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
@@ -38,7 +44,6 @@ import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
|
||||
/**
|
||||
* Node wrapping a blackboard artifact object. This is generated from several
|
||||
@@ -49,7 +54,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
private final BlackboardArtifact artifact;
|
||||
private final Content associated;
|
||||
private List<NodeProperty<? extends Object>> customProperties;
|
||||
static final Logger logger = Logger.getLogger(BlackboardArtifactNode.class.getName());
|
||||
private static final Logger LOGGER = Logger.getLogger(BlackboardArtifactNode.class.getName());
|
||||
/*
|
||||
* Artifact types which should have the full unique path of the associated
|
||||
* content as a property.
|
||||
@@ -100,13 +105,55 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
this.setIconBaseWithExtension(ExtractedContent.getIconFilePath(artifact.getArtifactTypeID())); //NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({
|
||||
"BlackboardArtifactNode.getAction.errorTitle=Error getting actions",
|
||||
"BlackboardArtifactNode.getAction.resultErrorMessage=There was a problem getting actions for the selected result."
|
||||
+ " The 'View Result in Timeline' action will not be available.",
|
||||
"BlackboardArtifactNode.getAction.linkedFileMessage=There was a problem getting actions for the selected result. "
|
||||
+ " The 'View File in Timeline' action will not be available."})
|
||||
public Action[] getActions(boolean context) {
|
||||
List<Action> actionsList = new ArrayList<>();
|
||||
actionsList.addAll(Arrays.asList(super.getActions(context)));
|
||||
|
||||
//if this artifact has a time stamp add the action to view it in the timeline
|
||||
try {
|
||||
if (ViewArtifactInTimelineAction.hasSupportedTimeStamp(artifact)) {
|
||||
actionsList.add(new ViewArtifactInTimelineAction(artifact));
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting arttribute(s) from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(Bundle.BlackboardArtifactNode_getAction_errorTitle(), Bundle.BlackboardArtifactNode_getAction_resultErrorMessage());
|
||||
}
|
||||
|
||||
// if the artifact links to another file, add an action to go to that file
|
||||
try {
|
||||
AbstractFile c = findLinked(artifact);
|
||||
if (c != null) {
|
||||
actionsList.add(ViewFileInTimelineAction.createViewFileAction(c));
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(Bundle.BlackboardArtifactNode_getAction_errorTitle(), Bundle.BlackboardArtifactNode_getAction_linkedFileMessage());
|
||||
}
|
||||
|
||||
//if this artifact has associated content, add the action to view the content in the timeline
|
||||
AbstractFile file = getLookup().lookup(AbstractFile.class);
|
||||
if (null != file) {
|
||||
|
||||
actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction(file));
|
||||
}
|
||||
|
||||
return actionsList.toArray(new Action[actionsList.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the filter node display name. The value will either be the file name
|
||||
* or something along the lines of e.g. "Messages Artifact" for keyword hits
|
||||
* on artifacts.
|
||||
*/
|
||||
private void setDisplayName() {
|
||||
String displayName = "";
|
||||
String displayName = ""; //NON-NLS
|
||||
if (associated != null) {
|
||||
displayName = associated.getName();
|
||||
}
|
||||
@@ -120,7 +167,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
if (attribute.getAttributeType().getTypeID() == ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()) {
|
||||
BlackboardArtifact associatedArtifact = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifact(attribute.getValueLong());
|
||||
if (associatedArtifact != null) {
|
||||
displayName = associatedArtifact.getDisplayName() + " Artifact"; // NON-NLS
|
||||
displayName = associatedArtifact.getDisplayName() + " Artifact";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,6 +178,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
this.setDisplayName(displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Sheet createSheet() {
|
||||
Sheet s = super.createSheet();
|
||||
Sheet.Set ss = s.get(Sheet.PROPERTIES);
|
||||
@@ -165,14 +213,14 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
|
||||
// If mismatch, add props for extension and file type
|
||||
if (artifactTypeId == BlackboardArtifact.ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID()) {
|
||||
String ext = "";
|
||||
String actualMimeType = "";
|
||||
String ext = ""; //NON-NLS
|
||||
String actualMimeType = ""; //NON-NLS
|
||||
if (associated instanceof AbstractFile) {
|
||||
AbstractFile af = (AbstractFile) associated;
|
||||
ext = af.getNameExtension();
|
||||
actualMimeType = af.getMIMEType();
|
||||
if (actualMimeType == null) {
|
||||
actualMimeType = "";
|
||||
actualMimeType = ""; //NON-NLS
|
||||
}
|
||||
}
|
||||
ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "BlackboardArtifactNode.createSheet.ext.name"),
|
||||
@@ -187,11 +235,11 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
}
|
||||
|
||||
if (Arrays.asList(SHOW_UNIQUE_PATH).contains(artifactTypeId)) {
|
||||
String sourcePath = "";
|
||||
String sourcePath = ""; //NON-NLS
|
||||
try {
|
||||
sourcePath = associated.getUniquePath();
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Failed to get unique path from: {0}", associated.getName()); //NON-NLS
|
||||
LOGGER.log(Level.WARNING, "Failed to get unique path from: {0}", associated.getName()); //NON-NLS
|
||||
}
|
||||
|
||||
if (sourcePath.isEmpty() == false) {
|
||||
@@ -235,7 +283,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
dataSourceStr = getRootParentName();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Failed to get image name from {0}", associated.getName()); //NON-NLS
|
||||
LOGGER.log(Level.WARNING, "Failed to get image name from {0}", associated.getName()); //NON-NLS
|
||||
}
|
||||
|
||||
if (dataSourceStr.isEmpty() == false) {
|
||||
@@ -258,7 +306,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
parentName = parent.getName();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.WARNING, "Failed to get parent name from {0}", associated.getName()); //NON-NLS
|
||||
LOGGER.log(Level.WARNING, "Failed to get parent name from {0}", associated.getName()); //NON-NLS
|
||||
return "";
|
||||
}
|
||||
return parentName;
|
||||
@@ -270,13 +318,12 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
*
|
||||
* @param np NodeProperty to add
|
||||
*/
|
||||
public <T> void addNodeProperty(NodeProperty<T> np) {
|
||||
public void addNodeProperty(NodeProperty<?> np) {
|
||||
if (null == customProperties) {
|
||||
//lazy create the list
|
||||
customProperties = new ArrayList<>();
|
||||
}
|
||||
customProperties.add(np);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,7 +343,6 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
|| attributeTypeID == ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT.getTypeID()
|
||||
|| attributeTypeID == ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()
|
||||
|| attributeTypeID == ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) {
|
||||
continue;
|
||||
} else if (attribute.getAttributeType().getValueType() == BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME) {
|
||||
map.put(attribute.getAttributeType().getDisplayName(), ContentUtils.getStringTime(attribute.getValueLong(), associated));
|
||||
} else if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_TOOL_OUTPUT.getTypeID()
|
||||
@@ -317,8 +363,8 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
map.put(attribute.getAttributeType().getDisplayName(), attribute.getDisplayString());
|
||||
}
|
||||
}
|
||||
} catch (TskException ex) {
|
||||
logger.log(Level.SEVERE, "Getting attributes failed", ex); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Getting attributes failed", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,13 +403,15 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
private static Content getAssociatedContent(BlackboardArtifact artifact) {
|
||||
try {
|
||||
return artifact.getSleuthkitCase().getContentById(artifact.getObjectID());
|
||||
} catch (TskException ex) {
|
||||
logger.log(Level.WARNING, "Getting file failed", ex); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.WARNING, "Getting file failed", ex); //NON-NLS
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.getAssocCont.exception.msg"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static TextMarkupLookup getHighlightLookup(BlackboardArtifact artifact, Content content) {
|
||||
if (artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
|
||||
return null;
|
||||
@@ -388,7 +436,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
}
|
||||
}
|
||||
if (keyword != null) {
|
||||
boolean isRegexp = (regexp != null && !regexp.equals(""));
|
||||
boolean isRegexp = StringUtils.isNotBlank(regexp);
|
||||
String origQuery;
|
||||
if (isRegexp) {
|
||||
origQuery = regexp;
|
||||
@@ -397,8 +445,8 @@ public class BlackboardArtifactNode extends DisplayableItemNode {
|
||||
}
|
||||
return highlightFactory.createInstance(objectId, keyword, isRegexp, origQuery);
|
||||
}
|
||||
} catch (TskException ex) {
|
||||
logger.log(Level.WARNING, "Failed to retrieve Blackboard Attributes", ex); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.WARNING, "Failed to retrieve Blackboard Attributes", ex); //NON-NLS
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
*
|
||||
* Copyright 2013-2014 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.Action;
|
||||
@@ -27,6 +29,11 @@ import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.actions.DeleteBlackboardArtifactTagAction;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
@@ -38,7 +45,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* either content or blackboard artifact tag nodes.
|
||||
*/
|
||||
public class BlackboardArtifactTagNode extends DisplayableItemNode {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(BlackboardArtifactTagNode.class.getName());
|
||||
private static final String ICON_PATH = "org/sleuthkit/autopsy/images/green-tag-icon-16.png"; //NON-NLS
|
||||
private final BlackboardArtifactTag tag;
|
||||
|
||||
@@ -93,11 +100,37 @@ public class BlackboardArtifactTagNode extends DisplayableItemNode {
|
||||
@Override
|
||||
public Action[] getActions(boolean context) {
|
||||
List<Action> actions = DataModelActionsFactory.getActions(tag.getContent(), true);
|
||||
for (Action a : super.getActions(true)) {
|
||||
actions.add(a);
|
||||
actions.addAll(Arrays.asList(super.getActions(context)));
|
||||
|
||||
BlackboardArtifact artifact = getLookup().lookup(BlackboardArtifact.class);
|
||||
//if this artifact has a time stamp add the action to view it in the timeline
|
||||
try {
|
||||
if (ViewArtifactInTimelineAction.hasSupportedTimeStamp(artifact)) {
|
||||
actions.add(new ViewArtifactInTimelineAction(artifact));
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting arttribute(s) from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(Bundle.BlackboardArtifactNode_getAction_errorTitle(), Bundle.BlackboardArtifactNode_getAction_resultErrorMessage());
|
||||
}
|
||||
actions.add(null); // Adds a menu item separator.
|
||||
|
||||
|
||||
// if the artifact links to another file, add an action to go to that file
|
||||
try {
|
||||
AbstractFile c = findLinked(artifact);
|
||||
if (c != null) {
|
||||
actions.add(ViewFileInTimelineAction.createViewFileAction(c));
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(Bundle.BlackboardArtifactNode_getAction_errorTitle(), Bundle.BlackboardArtifactNode_getAction_linkedFileMessage());
|
||||
}
|
||||
|
||||
//if this artifact has associated content, add the action to view the content in the timeline
|
||||
AbstractFile file = getLookup().lookup(AbstractFile.class);
|
||||
if (null != file) {
|
||||
|
||||
actions.add(ViewFileInTimelineAction.createViewSourceFileAction(file));
|
||||
}
|
||||
|
||||
actions.add(DeleteBlackboardArtifactTagAction.getInstance());
|
||||
return actions.toArray(new Action[0]);
|
||||
}
|
||||
|
||||
@@ -109,10 +109,6 @@ ExtractedContentNode.createSheet.name.name=Name
|
||||
ExtractedContentNode.createSheet.name.displayName=Name
|
||||
ExtractedContentNode.createSheet.name.desc=no description
|
||||
LocalFileNode.viewFileInDir.text=View File in Directory
|
||||
FileNode.viewFileInDir.text=View File in Directory
|
||||
FileNode.getActions.viewInNewWin.text=View in New Window
|
||||
FileNode.getActions.openInExtViewer.text=Open in External Viewer
|
||||
FileNode.getActions.searchFilesSameMD5.text=Search for files with the same MD5 hash
|
||||
FileSize.fileSizeRootNode.name=File Size
|
||||
FileSize.createSheet.name.name=Name
|
||||
FileSize.createSheet.name.displayName=Name
|
||||
|
||||
@@ -88,7 +88,7 @@ ExtractedContentNode.name.text=\u62bd\u51fa\u3055\u308c\u305f\u30b3\u30f3\u30c6\
|
||||
ExtractedContentNode.createSheet.name.name=\u540d\u524d
|
||||
ExtractedContentNode.createSheet.name.displayName=\u540d\u524d
|
||||
ExtractedContentNode.createSheet.name.desc=\u8aac\u660e\u304c\u3042\u308a\u307e\u305b\u3093
|
||||
FileNode.viewFileInDir.text=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u5185\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u8868\u793a
|
||||
FileNode.getActions.viewFileInDir.text=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u5185\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u8868\u793a
|
||||
FileNode.getActions.viewInNewWin.text=\u65b0\u898f\u30a6\u30a3\u30f3\u30c9\u30a6\u306b\u8868\u793a
|
||||
FileNode.getActions.openInExtViewer.text=\u5916\u90e8\u30d3\u30e5\u30fc\u30a2\u3067\u958b\u304f
|
||||
FileNode.getActions.searchFilesSameMD5.text=\u540c\u3058MD5\u30cf\u30c3\u30b7\u30e5\u3092\u6301\u3064\u30d5\u30a1\u30a4\u30eb\u3092\u691c\u7d22
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
/*
|
||||
* 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.
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.datamodel;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.Action;
|
||||
@@ -27,6 +28,7 @@ import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.actions.DeleteContentTagAction;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
@@ -107,12 +109,15 @@ class ContentTagNode extends DisplayableItemNode {
|
||||
@Override
|
||||
public Action[] getActions(boolean context) {
|
||||
List<Action> actions = DataModelActionsFactory.getActions(tag.getContent(), false);
|
||||
for (Action a : super.getActions(true)) {
|
||||
actions.add(a);
|
||||
actions.addAll(Arrays.asList(super.getActions(context)));
|
||||
|
||||
AbstractFile file = getLookup().lookup(AbstractFile.class);
|
||||
if (file != null) {
|
||||
actions.add(ViewFileInTimelineAction.createViewFileAction(file));
|
||||
}
|
||||
actions.add(null); // Adds a menu item separator.
|
||||
actions.add(DeleteContentTagAction.getInstance());
|
||||
return actions.toArray(new Action[0]);
|
||||
return actions.toArray(new Action[actions.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
|
||||
import org.sleuthkit.autopsy.directorytree.ExtractAction;
|
||||
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
|
||||
import org.sleuthkit.autopsy.directorytree.ViewContextAction;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Directory;
|
||||
import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
|
||||
@@ -80,12 +81,13 @@ public class DirectoryNode extends AbstractFsContentNode<AbstractFile> {
|
||||
actions.add(null); // creates a menu separator
|
||||
}
|
||||
actions.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "DirectoryNode.viewInNewWin.text"), this));
|
||||
actions.add(ViewFileInTimelineAction.createViewFileAction(getContent()));
|
||||
actions.add(null); // creates a menu separator
|
||||
actions.add(ExtractAction.getInstance());
|
||||
actions.add(null); // creates a menu separator
|
||||
actions.add(AddContentTagAction.getInstance());
|
||||
actions.addAll(ContextMenuExtensionPoint.getActions());
|
||||
return actions.toArray(new Action[0]);
|
||||
return actions.toArray(new Action[actions.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,6 +21,10 @@ package org.sleuthkit.autopsy.datamodel;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.util.Lookup;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A DisplayableItem is any node in the Autopsy directory tree. All of the nodes
|
||||
@@ -47,4 +51,27 @@ public abstract class DisplayableItemNode extends AbstractNode {
|
||||
* Added to support this feature.
|
||||
*/
|
||||
// public abstract String getItemType();
|
||||
/**
|
||||
* this code started as a cut and past of
|
||||
* DataResultFilterNode.GetPopupActionsDisplayableItemNodeVisitor.findLinked(BlackboardArtifactNode
|
||||
* ba)
|
||||
*
|
||||
*
|
||||
* @param artifact
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static AbstractFile findLinked(BlackboardArtifact artifact) throws TskCoreException {
|
||||
|
||||
BlackboardAttribute pathIDAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID));
|
||||
|
||||
if (pathIDAttribute != null) {
|
||||
long contentID = pathIDAttribute.getValueLong();
|
||||
if (contentID != -1) {
|
||||
return artifact.getSleuthkitCase().getAbstractFileById(contentID);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ public class EmailExtracted implements AutopsyVisitableItem {
|
||||
|
||||
private final class EmailResults extends Observable {
|
||||
|
||||
// NOTE: the map can be accessed by multiple worker threads and needs to be synchronized
|
||||
private final Map<String, Map<String, List<Long>>> accounts = new LinkedHashMap<>();
|
||||
|
||||
EmailResults() {
|
||||
@@ -79,20 +80,28 @@ public class EmailExtracted implements AutopsyVisitableItem {
|
||||
}
|
||||
|
||||
public Set<String> getAccounts() {
|
||||
return accounts.keySet();
|
||||
synchronized (accounts) {
|
||||
return accounts.keySet();
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getFolders(String account) {
|
||||
return accounts.get(account).keySet();
|
||||
synchronized (accounts) {
|
||||
return accounts.get(account).keySet();
|
||||
}
|
||||
}
|
||||
|
||||
public List<Long> getArtifactIds(String account, String folder) {
|
||||
return accounts.get(account).get(folder);
|
||||
synchronized (accounts) {
|
||||
return accounts.get(account).get(folder);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public void update() {
|
||||
accounts.clear();
|
||||
synchronized (accounts) {
|
||||
accounts.clear();
|
||||
}
|
||||
if (skCase == null) {
|
||||
return;
|
||||
}
|
||||
@@ -107,24 +116,26 @@ public class EmailExtracted implements AutopsyVisitableItem {
|
||||
|
||||
try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
|
||||
ResultSet resultSet = dbQuery.getResultSet();
|
||||
while (resultSet.next()) {
|
||||
final String path = resultSet.getString("value_text"); //NON-NLS
|
||||
final long artifactId = resultSet.getLong("artifact_id"); //NON-NLS
|
||||
final Map<String, String> parsedPath = parsePath(path);
|
||||
final String account = parsedPath.get(MAIL_ACCOUNT);
|
||||
final String folder = parsedPath.get(MAIL_FOLDER);
|
||||
synchronized (accounts) {
|
||||
while (resultSet.next()) {
|
||||
final String path = resultSet.getString("value_text"); //NON-NLS
|
||||
final long artifactId = resultSet.getLong("artifact_id"); //NON-NLS
|
||||
final Map<String, String> parsedPath = parsePath(path);
|
||||
final String account = parsedPath.get(MAIL_ACCOUNT);
|
||||
final String folder = parsedPath.get(MAIL_FOLDER);
|
||||
|
||||
Map<String, List<Long>> folders = accounts.get(account);
|
||||
if (folders == null) {
|
||||
folders = new LinkedHashMap<>();
|
||||
accounts.put(account, folders);
|
||||
Map<String, List<Long>> folders = accounts.get(account);
|
||||
if (folders == null) {
|
||||
folders = new LinkedHashMap<>();
|
||||
accounts.put(account, folders);
|
||||
}
|
||||
List<Long> messages = folders.get(folder);
|
||||
if (messages == null) {
|
||||
messages = new ArrayList<>();
|
||||
folders.put(folder, messages);
|
||||
}
|
||||
messages.add(artifactId);
|
||||
}
|
||||
List<Long> messages = folders.get(folder);
|
||||
if (messages == null) {
|
||||
messages = new ArrayList<>();
|
||||
folders.put(folder, messages);
|
||||
}
|
||||
messages.add(artifactId);
|
||||
}
|
||||
} catch (TskCoreException | SQLException ex) {
|
||||
logger.log(Level.WARNING, "Cannot initialize email extraction: ", ex); //NON-NLS
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011 - 2013 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -31,17 +31,20 @@ import org.sleuthkit.autopsy.directorytree.ExtractAction;
|
||||
import org.sleuthkit.autopsy.directorytree.HashSearchAction;
|
||||
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
|
||||
import org.sleuthkit.autopsy.directorytree.ViewContextAction;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
|
||||
import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM;
|
||||
|
||||
/**
|
||||
* This class is used to represent the "Node" for the file. It may have derived
|
||||
* files children.
|
||||
* This class is the Node for an AbstractFile. It may have derived files
|
||||
* children.
|
||||
*/
|
||||
public class FileNode extends AbstractFsContentNode<AbstractFile> {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param file underlying Content
|
||||
*/
|
||||
public FileNode(AbstractFile file) {
|
||||
@@ -69,44 +72,41 @@ public class FileNode extends AbstractFsContentNode<AbstractFile> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Right click action for this node
|
||||
*
|
||||
* @param popup
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@NbBundle.Messages({
|
||||
"FileNode.getActions.viewFileInDir.text=View File in Directory",
|
||||
"FileNode.getActions.viewInNewWin.text=View in New Window",
|
||||
"FileNode.getActions.openInExtViewer.text=Open in External Viewer",
|
||||
"FileNode.getActions.searchFilesSameMD5.text=Search for files with the same MD5 hash"})
|
||||
public Action[] getActions(boolean popup) {
|
||||
List<Action> actionsList = new ArrayList<>();
|
||||
for (Action a : super.getActions(true)) {
|
||||
actionsList.add(a);
|
||||
}
|
||||
if (!this.getDirectoryBrowseMode()) {
|
||||
actionsList.add(new ViewContextAction(NbBundle.getMessage(FileNode.class, "FileNode.viewFileInDir.text"), this));
|
||||
actionsList.add(new ViewContextAction(Bundle.FileNode_getActions_viewFileInDir_text(), this));
|
||||
actionsList.add(null); // creates a menu separator
|
||||
}
|
||||
actionsList.add(new NewWindowViewAction(
|
||||
NbBundle.getMessage(FileNode.class, "FileNode.getActions.viewInNewWin.text"), this));
|
||||
actionsList.add(new ExternalViewerAction(
|
||||
NbBundle.getMessage(FileNode.class, "FileNode.getActions.openInExtViewer.text"), this));
|
||||
actionsList.add(new NewWindowViewAction(Bundle.FileNode_getActions_viewInNewWin_text(), this));
|
||||
actionsList.add(new ExternalViewerAction(Bundle.FileNode_getActions_openInExtViewer_text(), this));
|
||||
actionsList.add(ViewFileInTimelineAction.createViewFileAction(getContent()));
|
||||
|
||||
actionsList.add(null); // creates a menu separator
|
||||
actionsList.add(ExtractAction.getInstance());
|
||||
actionsList.add(new HashSearchAction(
|
||||
NbBundle.getMessage(FileNode.class, "FileNode.getActions.searchFilesSameMD5.text"), this));
|
||||
actionsList.add(new HashSearchAction(Bundle.FileNode_getActions_searchFilesSameMD5_text(), this));
|
||||
actionsList.add(null); // creates a menu separator
|
||||
actionsList.add(AddContentTagAction.getInstance());
|
||||
actionsList.addAll(ContextMenuExtensionPoint.getActions());
|
||||
return actionsList.toArray(new Action[0]);
|
||||
return actionsList.toArray(new Action[actionsList.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(ContentNodeVisitor< T> v) {
|
||||
public <T> T accept(ContentNodeVisitor<T> v) {
|
||||
return v.visit(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T accept(DisplayableItemNodeVisitor< T> v) {
|
||||
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
|
||||
return v.visit(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ public class HashsetHits implements AutopsyVisitableItem {
|
||||
private class HashsetResults extends Observable {
|
||||
|
||||
// maps hashset name to list of artifacts for that set
|
||||
|
||||
// NOTE: the map can be accessed by multiple worker threads and needs to be synchronized
|
||||
private final Map<String, Set<Long>> hashSetHitsMap = new LinkedHashMap<>();
|
||||
|
||||
HashsetResults() {
|
||||
@@ -86,18 +86,25 @@ public class HashsetHits implements AutopsyVisitableItem {
|
||||
}
|
||||
|
||||
List<String> getSetNames() {
|
||||
List<String> names = new ArrayList<>(hashSetHitsMap.keySet());
|
||||
List<String> names;
|
||||
synchronized (hashSetHitsMap) {
|
||||
names = new ArrayList<>(hashSetHitsMap.keySet());
|
||||
}
|
||||
Collections.sort(names);
|
||||
return names;
|
||||
}
|
||||
|
||||
Set<Long> getArtifactIds(String hashSetName) {
|
||||
return hashSetHitsMap.get(hashSetName);
|
||||
synchronized (hashSetHitsMap) {
|
||||
return hashSetHitsMap.get(hashSetName);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
final void update() {
|
||||
hashSetHitsMap.clear();
|
||||
synchronized (hashSetHitsMap) {
|
||||
hashSetHitsMap.clear();
|
||||
}
|
||||
|
||||
if (skCase == null) {
|
||||
return;
|
||||
@@ -113,13 +120,15 @@ public class HashsetHits implements AutopsyVisitableItem {
|
||||
|
||||
try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
|
||||
ResultSet resultSet = dbQuery.getResultSet();
|
||||
while (resultSet.next()) {
|
||||
String setName = resultSet.getString("value_text"); //NON-NLS
|
||||
long artifactId = resultSet.getLong("artifact_id"); //NON-NLS
|
||||
if (!hashSetHitsMap.containsKey(setName)) {
|
||||
hashSetHitsMap.put(setName, new HashSet<Long>());
|
||||
synchronized (hashSetHitsMap) {
|
||||
while (resultSet.next()) {
|
||||
String setName = resultSet.getString("value_text"); //NON-NLS
|
||||
long artifactId = resultSet.getLong("artifact_id"); //NON-NLS
|
||||
if (!hashSetHitsMap.containsKey(setName)) {
|
||||
hashSetHitsMap.put(setName, new HashSet<Long>());
|
||||
}
|
||||
hashSetHitsMap.get(setName).add(artifactId);
|
||||
}
|
||||
hashSetHitsMap.get(setName).add(artifactId);
|
||||
}
|
||||
} catch (TskCoreException | SQLException ex) {
|
||||
logger.log(Level.WARNING, "SQL Exception occurred: ", ex); //NON-NLS
|
||||
|
||||
@@ -64,20 +64,28 @@ public class InterestingHits implements AutopsyVisitableItem {
|
||||
|
||||
private class InterestingResults extends Observable {
|
||||
|
||||
// NOTE: the map can be accessed by multiple worker threads and needs to be synchronized
|
||||
private final Map<String, Set<Long>> interestingItemsMap = new LinkedHashMap<>();
|
||||
|
||||
public List<String> getSetNames() {
|
||||
List<String> setNames = new ArrayList<>(interestingItemsMap.keySet());
|
||||
List<String> setNames;
|
||||
synchronized (interestingItemsMap) {
|
||||
setNames = new ArrayList<>(interestingItemsMap.keySet());
|
||||
}
|
||||
Collections.sort(setNames);
|
||||
return setNames;
|
||||
}
|
||||
|
||||
public Set<Long> getArtifactIds(String setName) {
|
||||
return interestingItemsMap.get(setName);
|
||||
synchronized (interestingItemsMap) {
|
||||
return interestingItemsMap.get(setName);
|
||||
}
|
||||
}
|
||||
|
||||
public void update() {
|
||||
interestingItemsMap.clear();
|
||||
synchronized (interestingItemsMap) {
|
||||
interestingItemsMap.clear();
|
||||
}
|
||||
loadArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
|
||||
loadArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT);
|
||||
setChanged();
|
||||
@@ -103,14 +111,16 @@ public class InterestingHits implements AutopsyVisitableItem {
|
||||
+ " AND blackboard_artifacts.artifact_type_id=" + artId; //NON-NLS
|
||||
|
||||
try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
|
||||
ResultSet resultSet = dbQuery.getResultSet();
|
||||
while (resultSet.next()) {
|
||||
String value = resultSet.getString("value_text"); //NON-NLS
|
||||
long artifactId = resultSet.getLong("artifact_id"); //NON-NLS
|
||||
if (!interestingItemsMap.containsKey(value)) {
|
||||
interestingItemsMap.put(value, new HashSet<>());
|
||||
synchronized (interestingItemsMap) {
|
||||
ResultSet resultSet = dbQuery.getResultSet();
|
||||
while (resultSet.next()) {
|
||||
String value = resultSet.getString("value_text"); //NON-NLS
|
||||
long artifactId = resultSet.getLong("artifact_id"); //NON-NLS
|
||||
if (!interestingItemsMap.containsKey(value)) {
|
||||
interestingItemsMap.put(value, new HashSet<>());
|
||||
}
|
||||
interestingItemsMap.get(value).add(artifactId);
|
||||
}
|
||||
interestingItemsMap.get(value).add(artifactId);
|
||||
}
|
||||
} catch (TskCoreException | SQLException ex) {
|
||||
logger.log(Level.WARNING, "SQL Exception occurred: ", ex); //NON-NLS
|
||||
|
||||
@@ -73,85 +73,94 @@ public class KeywordHits implements AutopsyVisitableItem {
|
||||
private final class KeywordResults extends Observable {
|
||||
|
||||
// Map from listName/Type to Map of keyword to set of artifact Ids
|
||||
private final Map<String, Map<String, Set<Long>>> topLevelMap;
|
||||
// NOTE: the map can be accessed by multiple worker threads and needs to be synchronized
|
||||
private final Map<String, Map<String, Set<Long>>> topLevelMap = new LinkedHashMap<>();
|
||||
|
||||
KeywordResults() {
|
||||
topLevelMap = new LinkedHashMap<>();
|
||||
update();
|
||||
}
|
||||
|
||||
List<String> getListNames() {
|
||||
List<String> names = new ArrayList<>(topLevelMap.keySet());
|
||||
// this causes the "Single ..." terms to be in the middle of the results,
|
||||
// which is wierd. Make a custom comparator or do something else to maek them on top
|
||||
//Collections.sort(names);
|
||||
return names;
|
||||
synchronized (topLevelMap) {
|
||||
List<String> names = new ArrayList<>(topLevelMap.keySet());
|
||||
// this causes the "Single ..." terms to be in the middle of the results,
|
||||
// which is wierd. Make a custom comparator or do something else to maek them on top
|
||||
//Collections.sort(names);
|
||||
return names;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> getKeywords(String listName) {
|
||||
List<String> keywords = new ArrayList<>(topLevelMap.get(listName).keySet());
|
||||
List<String> keywords;
|
||||
synchronized (topLevelMap) {
|
||||
keywords = new ArrayList<>(topLevelMap.get(listName).keySet());
|
||||
}
|
||||
Collections.sort(keywords);
|
||||
return keywords;
|
||||
}
|
||||
|
||||
Set<Long> getArtifactIds(String listName, String keyword) {
|
||||
return topLevelMap.get(listName).get(keyword);
|
||||
synchronized (topLevelMap) {
|
||||
return topLevelMap.get(listName).get(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
// populate maps based on artifactIds
|
||||
void populateMaps(Map<Long, Map<Long, String>> artifactIds) {
|
||||
topLevelMap.clear();
|
||||
synchronized (topLevelMap) {
|
||||
topLevelMap.clear();
|
||||
|
||||
// map of list name to keword to artifact IDs
|
||||
Map<String, Map<String, Set<Long>>> listsMap = new LinkedHashMap<>();
|
||||
// map of list name to keword to artifact IDs
|
||||
Map<String, Map<String, Set<Long>>> listsMap = new LinkedHashMap<>();
|
||||
|
||||
// Map from from literal keyword to artifact IDs
|
||||
Map<String, Set<Long>> literalMap = new LinkedHashMap<>();
|
||||
// Map from from literal keyword to artifact IDs
|
||||
Map<String, Set<Long>> literalMap = new LinkedHashMap<>();
|
||||
|
||||
// Map from regex keyword artifact IDs
|
||||
Map<String, Set<Long>> regexMap = new LinkedHashMap<>();
|
||||
// Map from regex keyword artifact IDs
|
||||
Map<String, Set<Long>> regexMap = new LinkedHashMap<>();
|
||||
|
||||
// top-level nodes
|
||||
topLevelMap.put(SIMPLE_LITERAL_SEARCH, literalMap);
|
||||
topLevelMap.put(SIMPLE_REGEX_SEARCH, regexMap);
|
||||
// top-level nodes
|
||||
topLevelMap.put(SIMPLE_LITERAL_SEARCH, literalMap);
|
||||
topLevelMap.put(SIMPLE_REGEX_SEARCH, regexMap);
|
||||
|
||||
for (Map.Entry<Long, Map<Long, String>> art : artifactIds.entrySet()) {
|
||||
long id = art.getKey();
|
||||
Map<Long, String> attributes = art.getValue();
|
||||
for (Map.Entry<Long, Map<Long, String>> art : artifactIds.entrySet()) {
|
||||
long id = art.getKey();
|
||||
Map<Long, String> attributes = art.getValue();
|
||||
|
||||
// I think we can use attributes.remove(...) here?
|
||||
String listName = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()));
|
||||
String word = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()));
|
||||
String reg = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID()));
|
||||
// I think we can use attributes.remove(...) here?
|
||||
String listName = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()));
|
||||
String word = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()));
|
||||
String reg = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID()));
|
||||
|
||||
// part of a list
|
||||
if (listName != null) {
|
||||
if (listsMap.containsKey(listName) == false) {
|
||||
listsMap.put(listName, new LinkedHashMap<String, Set<Long>>());
|
||||
// part of a list
|
||||
if (listName != null) {
|
||||
if (listsMap.containsKey(listName) == false) {
|
||||
listsMap.put(listName, new LinkedHashMap<String, Set<Long>>());
|
||||
}
|
||||
|
||||
Map<String, Set<Long>> listMap = listsMap.get(listName);
|
||||
if (listMap.containsKey(word) == false) {
|
||||
listMap.put(word, new HashSet<Long>());
|
||||
}
|
||||
|
||||
listMap.get(word).add(id);
|
||||
} // regular expression, single term
|
||||
else if (reg != null) {
|
||||
if (regexMap.containsKey(reg) == false) {
|
||||
regexMap.put(reg, new HashSet<Long>());
|
||||
}
|
||||
regexMap.get(reg).add(id);
|
||||
} // literal, single term
|
||||
else {
|
||||
if (literalMap.containsKey(word) == false) {
|
||||
literalMap.put(word, new HashSet<Long>());
|
||||
}
|
||||
literalMap.get(word).add(id);
|
||||
}
|
||||
|
||||
Map<String, Set<Long>> listMap = listsMap.get(listName);
|
||||
if (listMap.containsKey(word) == false) {
|
||||
listMap.put(word, new HashSet<Long>());
|
||||
}
|
||||
|
||||
listMap.get(word).add(id);
|
||||
} // regular expression, single term
|
||||
else if (reg != null) {
|
||||
if (regexMap.containsKey(reg) == false) {
|
||||
regexMap.put(reg, new HashSet<Long>());
|
||||
}
|
||||
regexMap.get(reg).add(id);
|
||||
} // literal, single term
|
||||
else {
|
||||
if (literalMap.containsKey(word) == false) {
|
||||
literalMap.put(word, new HashSet<Long>());
|
||||
}
|
||||
literalMap.get(word).add(id);
|
||||
topLevelMap.putAll(listsMap);
|
||||
}
|
||||
topLevelMap.putAll(listsMap);
|
||||
}
|
||||
|
||||
|
||||
setChanged();
|
||||
notifyObservers();
|
||||
}
|
||||
|
||||
@@ -93,6 +93,9 @@ public class HashDbIngestModule implements FileIngestModule {
|
||||
@Override
|
||||
public void startUp(org.sleuthkit.autopsy.ingest.IngestJobContext context) throws IngestModuleException {
|
||||
jobId = context.getJobId();
|
||||
if (!hashDbManager.verifyAllDatabasesLoadedCorrectly()) {
|
||||
throw new IngestModuleException("Could not load all hash databases");
|
||||
}
|
||||
updateEnabledHashSets(hashDbManager.getKnownBadFileHashSets(), knownBadHashSets);
|
||||
updateEnabledHashSets(hashDbManager.getKnownFileHashSets(), knownHashSets);
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.apache.commons.io.FilenameUtils;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.core.RuntimeProperties;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
@@ -63,6 +64,7 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
private Set<String> hashSetPaths = new HashSet<>();
|
||||
PropertyChangeSupport changeSupport = new PropertyChangeSupport(HashDbManager.class);
|
||||
private static final Logger logger = Logger.getLogger(HashDbManager.class.getName());
|
||||
private boolean allDatabasesLoadedCorrectly = false;
|
||||
|
||||
/**
|
||||
* Property change event support In events: For both of these enums, the old
|
||||
@@ -93,6 +95,10 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||
changeSupport.removePropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
synchronized boolean verifyAllDatabasesLoadedCorrectly(){
|
||||
return allDatabasesLoadedCorrectly;
|
||||
}
|
||||
|
||||
private HashDbManager() {
|
||||
loadHashsetsConfiguration();
|
||||
@@ -457,7 +463,7 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
*/
|
||||
@Messages({"# {0} - database name", "HashDbManager.noDbPath.message=Couldn't get valid database path for: {0}"})
|
||||
private void configureSettings(HashLookupSettings settings) {
|
||||
boolean dbInfoRemoved = false;
|
||||
allDatabasesLoadedCorrectly = true;
|
||||
List<HashDbInfo> hashDbInfoList = settings.getHashDbInfo();
|
||||
for (HashDbInfo hashDb : hashDbInfoList) {
|
||||
try {
|
||||
@@ -466,7 +472,7 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
addHashDatabase(SleuthkitJNI.openHashDatabase(dbPath), hashDb.getHashSetName(), hashDb.getSearchDuringIngest(), hashDb.getSendIngestMessages(), hashDb.getKnownFilesType());
|
||||
} else {
|
||||
logger.log(Level.WARNING, Bundle.HashDbManager_noDbPath_message(hashDb.getHashSetName()));
|
||||
dbInfoRemoved = true;
|
||||
allDatabasesLoadedCorrectly = false;
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashDbManager.class.getName()).log(Level.SEVERE, "Error opening hash database", ex); //NON-NLS
|
||||
@@ -475,13 +481,23 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
"HashDbManager.unableToOpenHashDbMsg", hashDb.getHashSetName()),
|
||||
NbBundle.getMessage(this.getClass(), "HashDbManager.openHashDbErr"),
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
dbInfoRemoved = true;
|
||||
allDatabasesLoadedCorrectly = false;
|
||||
}
|
||||
}
|
||||
if (dbInfoRemoved) {
|
||||
|
||||
/* NOTE: When RuntimeProperties.coreComponentsAreActive() is "false",
|
||||
I don't think we should overwrite hash db settings file because we
|
||||
were unable to load a database. The user should have to fix the issue or
|
||||
remove the database from settings. Overwiting the settings effectively removes
|
||||
the database from HashLookupSettings and the user may not know about this
|
||||
because the dialogs are not being displayed. The next time user starts Autopsy, HashDB
|
||||
will load without errors and the user may think that the problem was solved.*/
|
||||
if (!allDatabasesLoadedCorrectly && RuntimeProperties.coreComponentsAreActive()) {
|
||||
try {
|
||||
HashLookupSettings.writeSettings(new HashLookupSettings(this.knownHashSets, this.knownBadHashSets));
|
||||
allDatabasesLoadedCorrectly = true;
|
||||
} catch (HashLookupSettings.HashLookupSettingsException ex) {
|
||||
allDatabasesLoadedCorrectly = false;
|
||||
logger.log(Level.SEVERE, "Could not overwrite hash database settings.", ex);
|
||||
}
|
||||
}
|
||||
@@ -496,7 +512,8 @@ public class HashDbManager implements PropertyChangeListener {
|
||||
|
||||
// Give the user an opportunity to find the desired file.
|
||||
String newPath = null;
|
||||
if (JOptionPane.showConfirmDialog(null,
|
||||
if (RuntimeProperties.coreComponentsAreActive() &&
|
||||
JOptionPane.showConfirmDialog(null,
|
||||
NbBundle.getMessage(this.getClass(), "HashDbManager.dlgMsg.dbNotFoundAtLoc",
|
||||
hashSetName, configuredPath),
|
||||
NbBundle.getMessage(this.getClass(), "HashDbManager.dlgTitle.MissingDb"),
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline;
|
||||
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.Locale;
|
||||
import javafx.scene.control.ListCell;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* A ListCell for a ChronoField
|
||||
*/
|
||||
public class ChronoFieldListCell extends ListCell<ChronoField> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(ChronoField item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
String displayName = item.getDisplayName(Locale.getDefault());
|
||||
setText(StringUtils.splitByCharacterTypeCamelCase(displayName)[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -19,8 +19,6 @@
|
||||
package org.sleuthkit.autopsy.timeline;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.ImageIcon;
|
||||
@@ -38,51 +36,64 @@ import org.sleuthkit.autopsy.core.Installer;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
|
||||
/**
|
||||
* An Action that opens the Timeline window. Has methods to open the window in
|
||||
* various specific states (e.g., showing a specific artifact in the List View)
|
||||
*/
|
||||
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline")
|
||||
@ActionRegistration(displayName = "#CTL_MakeTimeline", lazy = false)
|
||||
@ActionReferences(value = {
|
||||
@ActionReference(path = "Menu/Tools", position = 100),
|
||||
@ActionReference(path = "Toolbars/Case", position = 102)})
|
||||
public class OpenTimelineAction extends CallableSystemAction implements Presenter.Toolbar {
|
||||
public final class OpenTimelineAction extends CallableSystemAction implements Presenter.Toolbar {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger LOGGER = Logger.getLogger(OpenTimelineAction.class.getName());
|
||||
|
||||
private static final boolean fxInited = Installer.isJavaFxInited();
|
||||
private static final boolean FX_INITED = Installer.isJavaFxInited();
|
||||
|
||||
private static TimeLineController timeLineController = null;
|
||||
|
||||
private JButton toolbarButton = new JButton();
|
||||
private final JButton toolbarButton = new JButton(getName(),
|
||||
new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png"))); //NON-NLS
|
||||
|
||||
|
||||
/**
|
||||
* Invalidate the reference to the controller so that a new one will be
|
||||
* instantiated the next time this action is invoked
|
||||
*/
|
||||
synchronized static void invalidateController() {
|
||||
timeLineController = null;
|
||||
}
|
||||
|
||||
public OpenTimelineAction() {
|
||||
toolbarButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
performAction();
|
||||
}
|
||||
});
|
||||
toolbarButton.addActionListener(actionEvent -> performAction());
|
||||
this.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
/**
|
||||
* we disabled the check to hasData() because if it is executed while a
|
||||
* data source is being added, it blocks the edt
|
||||
* We used to also check if Case.getCurrentCase().hasData() was true. We
|
||||
* disabled that check because if it is executed while a data source is
|
||||
* being added, it blocks the edt
|
||||
*/
|
||||
return Case.isCaseOpen() && fxInited;// && Case.getCurrentCase().hasData();
|
||||
return Case.isCaseOpen() && FX_INITED;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public void performAction() {
|
||||
showTimeline();
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"OpenTimelineAction.settingsErrorMessage=Failed to initialize timeline settings.",
|
||||
"OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources."})
|
||||
@Override
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public void performAction() {
|
||||
synchronized private void showTimeline(AbstractFile file, BlackboardArtifact artifact) {
|
||||
try {
|
||||
Case currentCase = Case.getCurrentCase();
|
||||
if (currentCase.hasData() == false) {
|
||||
@@ -97,7 +108,9 @@ public class OpenTimelineAction extends CallableSystemAction implements Presente
|
||||
timeLineController.shutDownTimeLine();
|
||||
timeLineController = new TimeLineController(currentCase);
|
||||
}
|
||||
timeLineController.openTimeLine();
|
||||
|
||||
timeLineController.showTimeLine(file, artifact);
|
||||
|
||||
} catch (IOException iOException) {
|
||||
MessageNotifyUtil.Message.error(Bundle.OpenTimelineAction_settingsErrorMessage());
|
||||
LOGGER.log(Level.SEVERE, "Failed to initialize per case timeline settings.", iOException);
|
||||
@@ -107,9 +120,41 @@ public class OpenTimelineAction extends CallableSystemAction implements Presente
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the Timeline window with the default initial view.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public void showTimeline() {
|
||||
showTimeline(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the Timeline window with the given file selected in ListView. The
|
||||
* user will be prompted to choose which timestamp to use for the file, and
|
||||
* how much time to show around it.
|
||||
*
|
||||
* @param file The AbstractFile to show in the Timeline.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public void showFileInTimeline(AbstractFile file) {
|
||||
showTimeline(file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the Timeline window with the given artifact selected in ListView.
|
||||
* The how much time to show around it.
|
||||
*
|
||||
* @param artifact The BlackboardArtifact to show in the Timeline.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public void showArtifactInTimeline(BlackboardArtifact artifact) {
|
||||
showTimeline(null, artifact);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages("OpenTimelineAction.displayName=Timeline")
|
||||
public String getName() {
|
||||
return NbBundle.getMessage(OpenTimelineAction.class, "CTL_MakeTimeline");
|
||||
return Bundle.OpenTimelineAction_displayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,10 +185,6 @@ public class OpenTimelineAction extends CallableSystemAction implements Presente
|
||||
*/
|
||||
@Override
|
||||
public Component getToolbarPresenter() {
|
||||
ImageIcon icon = new ImageIcon(getClass().getResource("images/btn_icon_timeline_colorized_26.png")); //NON-NLS
|
||||
toolbarButton.setIcon(icon);
|
||||
toolbarButton.setText(this.getName());
|
||||
|
||||
return toolbarButton;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ class PromptDialogManager {
|
||||
* @param dialog The dialog to set the title bar icon for.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
static private void setDialogIcons(Dialog<?> dialog) {
|
||||
static void setDialogIcons(Dialog<?> dialog) {
|
||||
((Stage) dialog.getDialogPane().getScene().getWindow()).getIcons().setAll(AUTOPSY_ICON);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.ComboBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.Spinner?>
|
||||
<?import javafx.scene.control.TableColumn?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<fx:root spacing="5.0" type="VBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<children>
|
||||
<Label fx:id="chooseEventLabel" text="Choose an event to show in timeline:" />
|
||||
<TableView fx:id="eventTable" maxHeight="-Infinity" prefHeight="200.0" prefWidth="410.0">
|
||||
<columns>
|
||||
<TableColumn fx:id="typeColumn" maxWidth="200.0" minWidth="150.0" prefWidth="200.0" resizable="false" text="Event Type" />
|
||||
<TableColumn fx:id="dateTimeColumn" maxWidth="200.0" minWidth="150.0" prefWidth="200.0" resizable="false" text="Date/Time" />
|
||||
</columns>
|
||||
<columnResizePolicy>
|
||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
|
||||
</columnResizePolicy>
|
||||
</TableView>
|
||||
<Label text="Choose the amount of time to show before and after the selected event:" />
|
||||
<HBox spacing="10.0">
|
||||
<children>
|
||||
<Spinner fx:id="amountSpinner" editable="true" prefHeight="25.0" prefWidth="80.0" />
|
||||
<ComboBox fx:id="unitComboBox" prefWidth="150.0" />
|
||||
</children>
|
||||
<VBox.margin>
|
||||
<Insets bottom="5.0" />
|
||||
</VBox.margin>
|
||||
</HBox>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||
</padding>
|
||||
</fx:root>
|
||||
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.control.ButtonBar;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Dialog;
|
||||
import javafx.scene.control.DialogPane;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.SpinnerValueFactory;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.util.converter.IntegerStringConverter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.apache.commons.lang3.text.WordUtils;
|
||||
import org.controlsfx.validation.ValidationMessage;
|
||||
import org.controlsfx.validation.ValidationSupport;
|
||||
import org.controlsfx.validation.Validator;
|
||||
import org.joda.time.Interval;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.events.ViewInTimelineRequestedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A Dialog that, given an AbstractFile or BlackBoardArtifact, allows the user
|
||||
* to choose a specific event and a time range around it to show in the Timeline
|
||||
* List View.
|
||||
*/
|
||||
final class ShowInTimelineDialog extends Dialog<ViewInTimelineRequestedEvent> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(ShowInTimelineDialog.class.getName());
|
||||
|
||||
@NbBundle.Messages({"ShowInTimelineDialog.showTimelineButtonType.text=Show Timeline"})
|
||||
private static final ButtonType SHOW = new ButtonType(Bundle.ShowInTimelineDialog_showTimelineButtonType_text(), ButtonBar.ButtonData.OK_DONE);
|
||||
|
||||
/**
|
||||
* List of ChronoUnits the user can select from when choosing a time range
|
||||
* to show.
|
||||
*/
|
||||
private static final List<ChronoField> SCROLL_BY_UNITS = Arrays.asList(
|
||||
ChronoField.YEAR,
|
||||
ChronoField.MONTH_OF_YEAR,
|
||||
ChronoField.DAY_OF_MONTH,
|
||||
ChronoField.HOUR_OF_DAY,
|
||||
ChronoField.MINUTE_OF_HOUR,
|
||||
ChronoField.SECOND_OF_MINUTE);
|
||||
|
||||
@FXML
|
||||
private TableView<SingleEvent> eventTable;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SingleEvent, EventType> typeColumn;
|
||||
|
||||
@FXML
|
||||
private TableColumn<SingleEvent, Long> dateTimeColumn;
|
||||
|
||||
@FXML
|
||||
private Spinner<Integer> amountSpinner;
|
||||
|
||||
@FXML
|
||||
private ComboBox<ChronoField> unitComboBox;
|
||||
|
||||
@FXML
|
||||
private Label chooseEventLabel;
|
||||
|
||||
private final VBox contentRoot = new VBox();
|
||||
|
||||
private final TimeLineController controller;
|
||||
|
||||
private final ValidationSupport validationSupport = new ValidationSupport();
|
||||
|
||||
/**
|
||||
* Common Private Constructor
|
||||
*
|
||||
* @param controller The controller for this Dialog.
|
||||
* @param eventIDS A List of eventIDs to present to the user to choose
|
||||
* from.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"ShowInTimelineDialog.amountValidator.message=The entered amount must only contain digits."
|
||||
})
|
||||
private ShowInTimelineDialog(TimeLineController controller, List<Long> eventIDS) {
|
||||
this.controller = controller;
|
||||
|
||||
//load dialog content fxml
|
||||
final String name = "nbres:/" + StringUtils.replace(ShowInTimelineDialog.class.getPackage().getName(), ".", "/") + "/ShowInTimelineDialog.fxml"; // NON-NLS
|
||||
try {
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(new URL(name));
|
||||
fxmlLoader.setRoot(contentRoot);
|
||||
fxmlLoader.setController(this);
|
||||
|
||||
fxmlLoader.load();
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unable to load FXML, node initialization may not be complete.", ex); //NON-NLS
|
||||
}
|
||||
//assert that fxml loading happened correctly
|
||||
assert eventTable != null : "fx:id=\"eventTable\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
|
||||
assert typeColumn != null : "fx:id=\"typeColumn\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
|
||||
assert dateTimeColumn != null : "fx:id=\"dateTimeColumn\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
|
||||
assert amountSpinner != null : "fx:id=\"amountsSpinner\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
|
||||
assert unitComboBox != null : "fx:id=\"unitChoiceBox\" was not injected: check your FXML file 'ShowInTimelineDialog.fxml'.";
|
||||
|
||||
//validat that spinner has a integer in the text field.
|
||||
validationSupport.registerValidator(amountSpinner.getEditor(), false,
|
||||
Validator.createPredicateValidator(NumberUtils::isDigits, Bundle.ShowInTimelineDialog_amountValidator_message()));
|
||||
|
||||
//configure dialog properties
|
||||
PromptDialogManager.setDialogIcons(this);
|
||||
initModality(Modality.APPLICATION_MODAL);
|
||||
|
||||
//add scenegraph loaded from fxml to this dialog.
|
||||
DialogPane dialogPane = getDialogPane();
|
||||
dialogPane.setContent(contentRoot);
|
||||
//add buttons to dialog
|
||||
dialogPane.getButtonTypes().setAll(SHOW, ButtonType.CANCEL);
|
||||
|
||||
///configure dialog controls
|
||||
amountSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 1000));
|
||||
amountSpinner.getValueFactory().setConverter(new IntegerStringConverter() {
|
||||
/**
|
||||
* Convert the String to an Integer using Integer.valueOf, but if
|
||||
* that throws a NumberFormatException, reset the spinner to the
|
||||
* last valid value.
|
||||
*
|
||||
* @param string The String to convert
|
||||
*
|
||||
* @return The Integer value of string.
|
||||
*/
|
||||
@Override
|
||||
public Integer fromString(String string) {
|
||||
try {
|
||||
return super.fromString(string);
|
||||
} catch (NumberFormatException ex) {
|
||||
return amountSpinner.getValue();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
unitComboBox.setButtonCell(new ChronoFieldListCell());
|
||||
unitComboBox.setCellFactory(comboBox -> new ChronoFieldListCell());
|
||||
unitComboBox.getItems().setAll(SCROLL_BY_UNITS);
|
||||
unitComboBox.getSelectionModel().select(ChronoField.MINUTE_OF_HOUR);
|
||||
|
||||
typeColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().getEventType()));
|
||||
typeColumn.setCellFactory(param -> new TypeTableCell<>());
|
||||
|
||||
dateTimeColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue().getStartMillis()));
|
||||
dateTimeColumn.setCellFactory(param -> new DateTimeTableCell<>());
|
||||
|
||||
//add events to table
|
||||
eventTable.getItems().setAll(eventIDS.stream().map(controller.getEventsModel()::getEventById).collect(Collectors.toSet()));
|
||||
eventTable.setPrefHeight(Math.min(200, 24 * eventTable.getItems().size() + 28));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for artifact based dialog. suppressed the choosing event
|
||||
* aspect as each artifact is assumed to have only one associated event.
|
||||
*
|
||||
* @param controller The controller for this Dialog
|
||||
* @param artifact The BlackboardArtifact to configure this dialog for.
|
||||
*/
|
||||
@NbBundle.Messages({"ShowInTimelineDialog.artifactTitle=View Result in Timeline."})
|
||||
ShowInTimelineDialog(TimeLineController controller, BlackboardArtifact artifact) {
|
||||
//get events IDs from artifact
|
||||
this(controller, controller.getEventsModel().getEventIDsForArtifact(artifact));
|
||||
|
||||
//hide instructional label and autoselect first(and only) event.
|
||||
chooseEventLabel.setVisible(false);
|
||||
chooseEventLabel.setManaged(false);
|
||||
eventTable.getSelectionModel().select(0);
|
||||
|
||||
//require validation of ammount spinner to enable show button
|
||||
getDialogPane().lookupButton(SHOW).disableProperty().bind(validationSupport.invalidProperty());
|
||||
|
||||
//set result converter that does not require selection.
|
||||
setResultConverter(buttonType -> (buttonType == SHOW)
|
||||
? makeEventInTimeRange(eventTable.getItems().get(0))
|
||||
: null
|
||||
);
|
||||
setTitle(Bundle.ShowInTimelineDialog_artifactTitle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for file based dialog. Allows the user to choose an event
|
||||
* (MAC time) derived from the given file
|
||||
*
|
||||
* @param controller The controller for this Dialog.
|
||||
* @param file The AbstractFile to configure this dialog for.
|
||||
*/
|
||||
@NbBundle.Messages({"# {0} - file path",
|
||||
"ShowInTimelineDialog.fileTitle=View {0} in timeline.",
|
||||
"ShowInTimelineDialog.eventSelectionValidator.message=You must select an event."})
|
||||
ShowInTimelineDialog(TimeLineController controller, AbstractFile file) {
|
||||
this(controller, controller.getEventsModel().getEventIDsForFile(file, false));
|
||||
|
||||
/*
|
||||
* since ValidationSupport does not support list selection, we will
|
||||
* manually apply and remove decoration in response to selection
|
||||
* property changes.
|
||||
*/
|
||||
eventTable.getSelectionModel().selectedItemProperty().isNull().addListener((selectedItemNullProperty, wasNull, isNull) -> {
|
||||
if (isNull) {
|
||||
validationSupport.getValidationDecorator().applyValidationDecoration(
|
||||
ValidationMessage.error(eventTable, Bundle.ShowInTimelineDialog_eventSelectionValidator_message()));
|
||||
} else {
|
||||
validationSupport.getValidationDecorator().removeDecorations(eventTable);
|
||||
}
|
||||
});
|
||||
|
||||
//require selection and validation of ammount spinner to enable show button
|
||||
getDialogPane().lookupButton(SHOW).disableProperty().bind(Bindings.or(
|
||||
validationSupport.invalidProperty(),
|
||||
eventTable.getSelectionModel().selectedItemProperty().isNull()
|
||||
));
|
||||
|
||||
//set result converter that uses selection.
|
||||
setResultConverter(buttonType -> (buttonType == SHOW)
|
||||
? makeEventInTimeRange(eventTable.getSelectionModel().getSelectedItem())
|
||||
: null
|
||||
);
|
||||
|
||||
setTitle(Bundle.ShowInTimelineDialog_fileTitle(StringUtils.abbreviateMiddle(getContentPathSafe(file), " ... ", 50)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique path for the content, or if that fails, just return the
|
||||
* name.
|
||||
*
|
||||
* NOTE: This was copied from IamgeUtils and should be refactored to avoid
|
||||
* duplication.
|
||||
*
|
||||
* @param content
|
||||
*
|
||||
* @return the unique path for the content, or if that fails, just the name.
|
||||
*/
|
||||
static String getContentPathSafe(Content content) {
|
||||
try {
|
||||
return content.getUniquePath();
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
String contentName = content.getName();
|
||||
LOGGER.log(Level.SEVERE, "Failed to get unique path for " + contentName, tskCoreException); //NON-NLS
|
||||
return contentName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct this Dialog's "result" from the given event.
|
||||
*
|
||||
* @param selectedEvent The SingleEvent to include in the EventInTimeRange
|
||||
*
|
||||
* @return The EventInTimeRange that is the "result" of this dialog.
|
||||
*/
|
||||
private ViewInTimelineRequestedEvent makeEventInTimeRange(SingleEvent selectedEvent) {
|
||||
Duration selectedDuration = unitComboBox.getSelectionModel().getSelectedItem().getBaseUnit().getDuration().multipliedBy(amountSpinner.getValue());
|
||||
Interval range = IntervalUtils.getIntervalAround(Instant.ofEpochMilli(selectedEvent.getStartMillis()), selectedDuration);
|
||||
return new ViewInTimelineRequestedEvent(Collections.singleton(selectedEvent.getEventID()), range);
|
||||
}
|
||||
|
||||
/**
|
||||
* ListCell that shows a ChronoUnit
|
||||
*/
|
||||
static private class ChronoUnitListCell extends ListCell<ChronoUnit> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(ChronoUnit item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(WordUtils.capitalizeFully(item.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TableCell that shows a formatted date/time for a given millisecond since
|
||||
* the unix epoch
|
||||
*
|
||||
* @param <X> Anything
|
||||
*/
|
||||
static private class DateTimeTableCell<X> extends TableCell<X, Long> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Long item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item == null || empty) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(TimeLineController.getZonedFormatter().print(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TableCell that shows a EventType including the associated icon.
|
||||
*
|
||||
* @param <X> Anything
|
||||
*/
|
||||
static private class TypeTableCell<X> extends TableCell<X, EventType> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(EventType item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item == null || empty) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
setText(item.getDisplayName());
|
||||
setGraphic(new ImageView(item.getFXImage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline;
|
||||
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.IOException;
|
||||
@@ -26,6 +27,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -81,6 +83,7 @@ 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.db.EventsRepository;
|
||||
import org.sleuthkit.autopsy.timeline.events.ViewInTimelineRequestedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
||||
@@ -89,6 +92,8 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.TimeUnits;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
|
||||
/**
|
||||
* Controller in the MVC design along with FilteredEventsModel TimeLineView.
|
||||
@@ -141,6 +146,7 @@ public class TimeLineController {
|
||||
private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper();
|
||||
|
||||
private final ReadOnlyStringWrapper statusMessage = new ReadOnlyStringWrapper();
|
||||
private EventBus eventbus = new EventBus("TimeLineController_EventBus");
|
||||
|
||||
/**
|
||||
* Status is a string that will be displayed in the status bar as a kind of
|
||||
@@ -218,7 +224,7 @@ public class TimeLineController {
|
||||
|
||||
//selected events (ie shown in the result viewer)
|
||||
@GuardedBy("this")
|
||||
private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>synchronizedObservableList(FXCollections.<Long>observableArrayList());
|
||||
private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>observableArrayList();
|
||||
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>();
|
||||
@@ -384,7 +390,9 @@ public class TimeLineController {
|
||||
/**
|
||||
* Rebuild the repo using the given repoBuilder (expected to be a member
|
||||
* reference to EventsRepository.rebuildRepository() or
|
||||
* EventsRepository.rebuildTags()) and display the ui when it is done.
|
||||
* EventsRepository.rebuildTags()) and display the UI when it is done. If
|
||||
* either file or artifact is not null the user will be prompted to choose a
|
||||
* derived event and time range to show in the Timeline List View.
|
||||
*
|
||||
* @param repoBuilder A Function from Consumer<Worker.State> to
|
||||
* CancellationProgressTask<?>. Ie a function that
|
||||
@@ -395,12 +403,16 @@ public class TimeLineController {
|
||||
* EventsRepository.rebuildTags()
|
||||
* @param markDBNotStale After the repo is rebuilt should it be marked not
|
||||
* stale
|
||||
* @param file The AbstractFile from which to choose an event to
|
||||
* show in the List View.
|
||||
* @param artifact The BlackboardArtifact to show in the List View.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
@NbBundle.Messages({
|
||||
"TimeLineController.setIngestRunning.errMsgRunning=Failed to mark the timeline db as populated while ingest was running. Some results may be out of date or missing.",
|
||||
"TimeLinecontroller.setIngestRunning.errMsgNotRunning=Failed to mark the timeline db as populated while ingest was not running. Some results may be out of date or missing."})
|
||||
private void rebuildRepoHelper(Function<Consumer<Worker.State>, CancellationProgressTask<?>> repoBuilder, Boolean markDBNotStale) {
|
||||
private void rebuildRepoHelper(Function<Consumer<Worker.State>, CancellationProgressTask<?>> repoBuilder, Boolean markDBNotStale, AbstractFile file, BlackboardArtifact artifact) {
|
||||
|
||||
boolean ingestRunning = IngestManager.getInstance().isIngestRunning();
|
||||
//if there is an existing prompt or progressdialog, just show that
|
||||
if (promptDialogManager.bringCurrentDialogToFront()) {
|
||||
@@ -412,34 +424,51 @@ public class TimeLineController {
|
||||
return; //if they cancel, do nothing.
|
||||
}
|
||||
|
||||
//get a task that rebuilds the repo with the bellow state listener attached
|
||||
final CancellationProgressTask<?> rebuildRepositoryTask = repoBuilder.apply(newSate -> {
|
||||
//this will be on JFX thread
|
||||
switch (newSate) {
|
||||
case SUCCEEDED:
|
||||
/*
|
||||
* Record if ingest was running the last time the db was
|
||||
* rebuilt, and hence it might stale.
|
||||
*/
|
||||
try {
|
||||
perCaseTimelineProperties.setIngestRunning(ingestRunning);
|
||||
} catch (IOException ex) {
|
||||
MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(),
|
||||
ingestRunning ? Bundle.TimeLineController_setIngestRunning_errMsgRunning()
|
||||
: Bundle.TimeLinecontroller_setIngestRunning_errMsgNotRunning());
|
||||
LOGGER.log(Level.SEVERE, "Error marking the ingest state while the timeline db was populated.", ex); //NON-NLS
|
||||
}
|
||||
if (markDBNotStale) {
|
||||
setEventsDBStale(false);
|
||||
filteredEvents.postDBUpdated();
|
||||
}
|
||||
SwingUtilities.invokeLater(this::showWindow);
|
||||
break;
|
||||
|
||||
case FAILED:
|
||||
case CANCELLED:
|
||||
setEventsDBStale(true);
|
||||
break;
|
||||
//get a task that rebuilds the repo with the below state listener attached
|
||||
final CancellationProgressTask<?> rebuildRepositoryTask;
|
||||
rebuildRepositoryTask = repoBuilder.apply(new Consumer<Worker.State>() {
|
||||
@Override
|
||||
public void accept(Worker.State newSate) {
|
||||
//this will be on JFX thread
|
||||
switch (newSate) {
|
||||
case SUCCEEDED:
|
||||
/*
|
||||
* Record if ingest was running the last time the db was
|
||||
* rebuilt, and hence it might stale.
|
||||
*/
|
||||
try {
|
||||
perCaseTimelineProperties.setIngestRunning(ingestRunning);
|
||||
} catch (IOException ex) {
|
||||
MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(),
|
||||
ingestRunning ? Bundle.TimeLineController_setIngestRunning_errMsgRunning()
|
||||
: Bundle.TimeLinecontroller_setIngestRunning_errMsgNotRunning());
|
||||
LOGGER.log(Level.SEVERE, "Error marking the ingest state while the timeline db was populated.", ex); //NON-NLS
|
||||
}
|
||||
if (markDBNotStale) {
|
||||
setEventsDBStale(false);
|
||||
filteredEvents.postDBUpdated();
|
||||
}
|
||||
if (file == null && artifact == null) {
|
||||
SwingUtilities.invokeLater(TimeLineController.this::showWindow);
|
||||
TimeLineController.this.showFullRange();
|
||||
} else {
|
||||
//prompt user to pick specific event and time range
|
||||
ShowInTimelineDialog showInTimelineDilaog =
|
||||
(file == null)
|
||||
? new ShowInTimelineDialog(TimeLineController.this, artifact)
|
||||
: new ShowInTimelineDialog(TimeLineController.this, file);
|
||||
Optional<ViewInTimelineRequestedEvent> dialogResult = showInTimelineDilaog.showAndWait();
|
||||
dialogResult.ifPresent(viewInTimelineRequestedEvent -> {
|
||||
SwingUtilities.invokeLater(TimeLineController.this::showWindow);
|
||||
showInListView(viewInTimelineRequestedEvent); //show requested event in list view
|
||||
});
|
||||
}
|
||||
break;
|
||||
case FAILED:
|
||||
case CANCELLED:
|
||||
setEventsDBStale(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -456,27 +485,62 @@ public class TimeLineController {
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
public void rebuildRepo() {
|
||||
rebuildRepoHelper(eventsRepository::rebuildRepository, true);
|
||||
rebuildRepo(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the entire repo in the background, and show the timeline when
|
||||
* done.
|
||||
*
|
||||
* @param file The AbstractFile from which to choose an event to show in
|
||||
* the List View.
|
||||
* @param artifact The BlackboardArtifact to show in the List View.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private void rebuildRepo(AbstractFile file, BlackboardArtifact artifact) {
|
||||
rebuildRepoHelper(eventsRepository::rebuildRepository, true, file, artifact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop the tags table and rebuild it in the background, and show the
|
||||
* timeline when done.
|
||||
*
|
||||
* @param file The AbstractFile from which to choose an event to show in
|
||||
* the List View.
|
||||
* @param artifact The BlackboardArtifact to show in the List View.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
void rebuildTagsTable() {
|
||||
rebuildRepoHelper(eventsRepository::rebuildTags, false);
|
||||
private void rebuildTagsTable(AbstractFile file, BlackboardArtifact artifact) {
|
||||
rebuildRepoHelper(eventsRepository::rebuildTags, false, file, artifact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the entire range of the timeline.
|
||||
*/
|
||||
public boolean showFullRange() {
|
||||
private boolean showFullRange() {
|
||||
synchronized (filteredEvents) {
|
||||
return pushTimeRange(filteredEvents.getSpanningInterval());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the events and the amount of time indicated in the given
|
||||
* ViewInTimelineRequestedEvent in the List View.
|
||||
*
|
||||
* @param requestEvent Contains the ID of the requested events and the
|
||||
* timerange to show.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private void showInListView(ViewInTimelineRequestedEvent requestEvent) {
|
||||
synchronized (filteredEvents) {
|
||||
setViewMode(ViewMode.LIST);
|
||||
selectEventIDs(requestEvent.getEventIDs());
|
||||
if (pushTimeRange(requestEvent.getInterval()) == false) {
|
||||
eventbus.post(requestEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* "Shut down" Timeline. Remove all the case and ingest listers. Close the
|
||||
* timeline window.
|
||||
@@ -497,9 +561,13 @@ public class TimeLineController {
|
||||
/**
|
||||
* Add the case and ingest listeners, prompt for rebuilding the database if
|
||||
* necessary, and show the timeline window.
|
||||
*
|
||||
* @param file The AbstractFile from which to choose an event to show in
|
||||
* the List View.
|
||||
* @param artifact The BlackboardArtifact to show in the List View.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
void openTimeLine() {
|
||||
void showTimeLine(AbstractFile file, BlackboardArtifact artifact) {
|
||||
// listen for case changes (specifically images being added, and case changes).
|
||||
if (Case.isCaseOpen() && !listeningToAutopsy) {
|
||||
IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener);
|
||||
@@ -508,18 +576,21 @@ public class TimeLineController {
|
||||
listeningToAutopsy = true;
|
||||
}
|
||||
|
||||
Platform.runLater(this::promptForRebuild);
|
||||
Platform.runLater(() -> promptForRebuild(file, artifact));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user to confirm rebuilding the db. Checks if a database
|
||||
* rebuild is necessary and includes the reasons in the prompt. If the user
|
||||
* confirms, rebuilds the database. Shows the timeline window when the
|
||||
* rebuild is done, or immediately if the rebuild is not confirmed. F
|
||||
* rebuild is done, or immediately if the rebuild is not confirmed.
|
||||
*
|
||||
* @param file The AbstractFile from which to choose an event to show in
|
||||
* the List View.
|
||||
* @param artifact The BlackboardArtifact to show in the List View.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private void promptForRebuild() {
|
||||
|
||||
private void promptForRebuild(AbstractFile file, BlackboardArtifact artifact) {
|
||||
//if there is an existing prompt or progressdialog, just show that
|
||||
if (promptDialogManager.bringCurrentDialogToFront()) {
|
||||
return;
|
||||
@@ -527,7 +598,7 @@ public class TimeLineController {
|
||||
|
||||
//if the repo is empty just (re)build it with out asking, the user can always cancel part way through
|
||||
if (eventsRepository.countAllEvents() == 0) {
|
||||
rebuildRepo();
|
||||
rebuildRepo(file, artifact);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -535,7 +606,7 @@ public class TimeLineController {
|
||||
List<String> rebuildReasons = getRebuildReasons();
|
||||
if (false == rebuildReasons.isEmpty()) {
|
||||
if (promptDialogManager.confirmRebuild(rebuildReasons)) {
|
||||
rebuildRepo();
|
||||
rebuildRepo(file, artifact);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -547,7 +618,7 @@ public class TimeLineController {
|
||||
*
|
||||
* //TODO: can we check the tags to see if we need to do this?
|
||||
*/
|
||||
rebuildTagsTable();
|
||||
rebuildTagsTable(file, artifact);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -599,8 +670,7 @@ public class TimeLineController {
|
||||
*/
|
||||
synchronized public void pushPeriod(ReadablePeriod period) {
|
||||
synchronized (filteredEvents) {
|
||||
final DateTime middleOf = IntervalUtils.middleOf(filteredEvents.timeRangeProperty().get());
|
||||
pushTimeRange(IntervalUtils.getIntervalAround(middleOf, period));
|
||||
pushTimeRange(IntervalUtils.getIntervalAroundMiddle(filteredEvents.getTimeRange(), period));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,31 +690,6 @@ public class TimeLineController {
|
||||
pushTimeRange(new Interval(start, end));
|
||||
}
|
||||
|
||||
public void selectEventIDs(Collection<Long> events) {
|
||||
final LoggedTask<Interval> selectEventIDsTask = new LoggedTask<Interval>("Select Event IDs", true) { //NON-NLS
|
||||
@Override
|
||||
protected Interval call() throws Exception {
|
||||
return filteredEvents.getSpanningInterval(events);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
super.succeeded();
|
||||
try {
|
||||
synchronized (TimeLineController.this) {
|
||||
selectedTimeRange.set(get());
|
||||
selectedEventIDs.setAll(events);
|
||||
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
LOGGER.log(Level.SEVERE, getTitle() + " Unexpected error", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
monitorTask(selectEventIDsTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the timeline TimeLineTopComponent. This method will construct a new
|
||||
* instance of TimeLineTopComponent if necessary.
|
||||
@@ -672,15 +717,35 @@ public class TimeLineController {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("AssignmentToMethodParameter") //clamp timerange to case
|
||||
/**
|
||||
* Set the new interval to view, and record it in the history. The interval
|
||||
* will be clamped to the span of events in the current case.
|
||||
*
|
||||
* @param timeRange The Interval to view.
|
||||
*
|
||||
* @return True if the interval was changed. False if the interval was the
|
||||
* same as the existing one and no change happened.
|
||||
*/
|
||||
synchronized public boolean pushTimeRange(Interval timeRange) {
|
||||
timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
|
||||
//clamp timerange to case
|
||||
Interval clampedTimeRange;
|
||||
if (timeRange == null) {
|
||||
clampedTimeRange = this.filteredEvents.getSpanningInterval();
|
||||
} else {
|
||||
Interval spanningInterval = this.filteredEvents.getSpanningInterval();
|
||||
if (spanningInterval.overlaps(timeRange)) {
|
||||
clampedTimeRange = spanningInterval.overlap(timeRange);
|
||||
} else {
|
||||
clampedTimeRange = spanningInterval;
|
||||
}
|
||||
}
|
||||
|
||||
ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
|
||||
if (currentZoom == null) {
|
||||
advance(InitialZoomState.withTimeRange(timeRange));
|
||||
advance(InitialZoomState.withTimeRange(clampedTimeRange));
|
||||
return true;
|
||||
} else if (currentZoom.hasTimeRange(timeRange) == false) {
|
||||
advance(currentZoom.withTimeRange(timeRange));
|
||||
} else if (currentZoom.hasTimeRange(clampedTimeRange) == false) {
|
||||
advance(currentZoom.withTimeRange(clampedTimeRange));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -748,6 +813,17 @@ public class TimeLineController {
|
||||
historyManager.advance(newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the given event IDs and set their spanning interval as the
|
||||
* selected time range.
|
||||
*
|
||||
* @param eventIDs The eventIDs to select
|
||||
*/
|
||||
synchronized public void selectEventIDs(Collection<Long> eventIDs) {
|
||||
selectedTimeRange.set(filteredEvents.getSpanningInterval(eventIDs));
|
||||
selectedEventIDs.setAll(eventIDs);
|
||||
}
|
||||
|
||||
public void selectTimeAndType(Interval interval, EventType type) {
|
||||
final Interval timeRange = filteredEvents.getSpanningInterval().overlap(interval);
|
||||
|
||||
@@ -833,8 +909,28 @@ public class TimeLineController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given object to receive events.
|
||||
*
|
||||
* @param o The object to register. Must implement public methods annotated
|
||||
* with Subscribe.
|
||||
*/
|
||||
synchronized public void registerForEvents(Object o) {
|
||||
eventbus.register(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-register the given object, so it no longer receives events.
|
||||
*
|
||||
* @param o The object to un-register.
|
||||
*/
|
||||
synchronized public void unRegisterForEvents(Object o) {
|
||||
eventbus.unregister(0);
|
||||
}
|
||||
|
||||
static synchronized public void setTimeZone(TimeZone timeZone) {
|
||||
TimeLineController.timeZone.set(timeZone);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.control.Tab;
|
||||
@@ -98,7 +97,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
private final InvalidationListener selectedEventsListener = new InvalidationListener() {
|
||||
@Override
|
||||
public void invalidated(Observable observable) {
|
||||
ObservableList<Long> selectedEventIDs = controller.getSelectedEventIDs();
|
||||
List<Long> selectedEventIDs = controller.getSelectedEventIDs();
|
||||
|
||||
//depending on the active view mode, we either update the dataResultPanel, or update the contentViewerPanel directly.
|
||||
switch (controller.getViewMode()) {
|
||||
@@ -124,7 +123,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
LOGGER.log(Level.SEVERE, "Selecting the event node was vetoed.", ex); // NON-NLS
|
||||
}
|
||||
//if there is only one event selected push it into content viewer.
|
||||
if (selectedEventIDs.size() == 1) {
|
||||
if (childArray.length == 1) {
|
||||
contentViewerPanel.setNode(childArray[0]);
|
||||
} else {
|
||||
contentViewerPanel.setNode(null);
|
||||
@@ -137,7 +136,7 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
Notifications.create()
|
||||
.owner(jFXViewPanel.getScene().getWindow())
|
||||
.owner(jFXViewPanel.getScene().getWindow())
|
||||
.text(Bundle.TimelineTopComponent_selectedEventListener_errorMsg())
|
||||
.showError();
|
||||
});
|
||||
@@ -159,6 +158,36 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
}
|
||||
};
|
||||
|
||||
private void syncViewMode() {
|
||||
switch (controller.getViewMode()) {
|
||||
case COUNTS:
|
||||
case DETAIL:
|
||||
/*
|
||||
* For counts and details mode, restore the result table at the
|
||||
* bottom left.
|
||||
*/
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
splitYPane.remove(contentViewerPanel);
|
||||
if ((horizontalSplitPane.getParent() == splitYPane) == false) {
|
||||
splitYPane.setBottomComponent(horizontalSplitPane);
|
||||
horizontalSplitPane.setRightComponent(contentViewerPanel);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case LIST:
|
||||
/*
|
||||
* For list mode, remove the result table, and let the content
|
||||
* viewer expand across the bottom.
|
||||
*/
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
splitYPane.setBottomComponent(contentViewerPanel);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown ViewMode: " + controller.getViewMode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@@ -190,35 +219,8 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
controller.getSelectedEventIDs().addListener(selectedEventsListener);
|
||||
|
||||
//Listen to ViewMode and adjust GUI componenets as needed.
|
||||
controller.viewModeProperty().addListener(viewMode -> {
|
||||
switch (controller.getViewMode()) {
|
||||
case COUNTS:
|
||||
case DETAIL:
|
||||
/*
|
||||
* For counts and details mode, restore the result table at
|
||||
* the bottom left.
|
||||
*/
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
splitYPane.remove(contentViewerPanel);
|
||||
if ((horizontalSplitPane.getParent() == splitYPane) == false) {
|
||||
splitYPane.setBottomComponent(horizontalSplitPane);
|
||||
horizontalSplitPane.setRightComponent(contentViewerPanel);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case LIST:
|
||||
/*
|
||||
* For list mode, remove the result table, and let the
|
||||
* content viewer expand across the bottom.
|
||||
*/
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
splitYPane.setBottomComponent(contentViewerPanel);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown ViewMode: " + controller.getViewMode());
|
||||
}
|
||||
});
|
||||
controller.viewModeProperty().addListener(viewMode -> syncViewMode());
|
||||
syncViewMode();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.actions;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.actions.SystemAction;
|
||||
import org.sleuthkit.autopsy.timeline.OpenTimelineAction;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.ArtifactEventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* An action that shows the given artifact in the Timeline List View.
|
||||
*/
|
||||
public final class ViewArtifactInTimelineAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Set<ArtifactEventType> ARTIFACT_EVENT_TYPES =
|
||||
EventType.allTypes.stream()
|
||||
.filter((EventType t) -> t instanceof ArtifactEventType)
|
||||
.map(ArtifactEventType.class::cast)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
private final BlackboardArtifact artifact;
|
||||
|
||||
@NbBundle.Messages({"ViewArtifactInTimelineAction.displayName=View Result in Timeline... "})
|
||||
public ViewArtifactInTimelineAction(BlackboardArtifact artifact) {
|
||||
super(Bundle.ViewArtifactInTimelineAction_displayName());
|
||||
this.artifact = artifact;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
SystemAction.get(OpenTimelineAction.class).showArtifactInTimeline(artifact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the given artifact have a type that Timeline supports, and does it
|
||||
* have a positive timestamp in the supported attribute?
|
||||
*
|
||||
* @param artifact The artifact to test for a supported timestamp
|
||||
*
|
||||
* @return True if this artifact has a timestamp supported by Timeline.
|
||||
*/
|
||||
public static boolean hasSupportedTimeStamp(BlackboardArtifact artifact) throws TskCoreException {
|
||||
//see if the given artifact is a supported type ...
|
||||
for (ArtifactEventType artEventType : ARTIFACT_EVENT_TYPES) {
|
||||
if (artEventType.getArtifactTypeID() == artifact.getArtifactTypeID()) {
|
||||
//... and has a non-bogus timestamp in the supported attribute
|
||||
BlackboardAttribute attribute = artifact.getAttribute(artEventType.getDateTimeAttributeType());
|
||||
if (null != attribute && attribute.getValueLong() > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.actions;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.AbstractAction;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.actions.SystemAction;
|
||||
import org.sleuthkit.autopsy.timeline.OpenTimelineAction;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
|
||||
/**
|
||||
* An action to prompt the user to pick an timestamp/event associated with the
|
||||
* given file and show it in the Timeline List View
|
||||
*/
|
||||
public final class ViewFileInTimelineAction extends AbstractAction {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final AbstractFile file;
|
||||
|
||||
private ViewFileInTimelineAction(AbstractFile file, String displayName) {
|
||||
super(displayName);
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"ViewFileInTimelineAction.viewFile.displayName=View File in Timeline... "})
|
||||
public static ViewFileInTimelineAction createViewFileAction(AbstractFile file) {
|
||||
return new ViewFileInTimelineAction(file, Bundle.ViewFileInTimelineAction_viewFile_displayName());
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"ViewFileInTimelineAction.viewSourceFile.displayName=View Source File in Timeline... "})
|
||||
public static ViewFileInTimelineAction createViewSourceFileAction(AbstractFile file) {
|
||||
return new ViewFileInTimelineAction(file, Bundle.ViewFileInTimelineAction_viewSourceFile_displayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
SystemAction.get(OpenTimelineAction.class).showFileInTimeline(file);
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,11 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.datamodel;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import org.python.google.common.collect.ImmutableSet;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
|
||||
/**
|
||||
@@ -98,8 +98,8 @@ public class CombinedEvent {
|
||||
*
|
||||
* @return The event IDs of the combined events.
|
||||
*/
|
||||
public Collection<Long> getEventIDs() {
|
||||
return eventTypeMap.values();
|
||||
public ImmutableSet<Long> getEventIDs() {
|
||||
return ImmutableSet.copyOf(eventTypeMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,6 +62,7 @@ import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
@@ -113,7 +114,7 @@ public final class FilteredEventsModel {
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper<ZoomParams> requestedZoomParamters = new ReadOnlyObjectWrapper<>();
|
||||
|
||||
private final EventBus eventbus = new EventBus("Event_Repository_EventBus"); //NON-NLS
|
||||
private final EventBus eventbus = new EventBus("FilteredEventsModel_EventBus"); //NON-NLS
|
||||
|
||||
/**
|
||||
* The underlying repo for events. Atomic access to repo is synchronized
|
||||
@@ -429,6 +430,38 @@ public final class FilteredEventsModel {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of event IDs for the events that are derived from the given
|
||||
* file.
|
||||
*
|
||||
* @param file The AbstractFile to get derived event IDs
|
||||
* for.
|
||||
* @param includeDerivedArtifacts If true, also get event IDs for events
|
||||
* derived from artifacts derived form this
|
||||
* file. If false, only gets events derived
|
||||
* directly from this file (file system
|
||||
* timestamps).
|
||||
*
|
||||
* @return A List of event IDs for the events that are derived from the
|
||||
* given file.
|
||||
*/
|
||||
public List<Long> getEventIDsForFile(AbstractFile file, boolean includedDerivedArtifacts) {
|
||||
return repo.getEventIDsForFile(file, includedDerivedArtifacts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of event IDs for the events that are derived from the given
|
||||
* artifact.
|
||||
*
|
||||
* @param artifact The BlackboardArtifact to get derived event IDs for.
|
||||
*
|
||||
* @return A List of event IDs for the events that are derived from the
|
||||
* given artifact.
|
||||
*/
|
||||
public List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) {
|
||||
return repo.getEventIDsForArtifact(artifact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a TagsAddedEvent to all registered subscribers, if the given set of
|
||||
* updated event IDs is not empty.
|
||||
|
||||
@@ -34,14 +34,30 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
public interface ArtifactEventType extends EventType {
|
||||
|
||||
public static final Logger LOGGER = Logger.getLogger(ArtifactEventType.class.getName());
|
||||
static final EmptyExtractor EMPTY_EXTRACTOR = new EmptyExtractor();
|
||||
|
||||
/**
|
||||
* @return the Artifact type this event type is derived from
|
||||
* Get the artifact type this event type is derived from.
|
||||
*
|
||||
* @return The artifact type this event type is derived from.
|
||||
*/
|
||||
public BlackboardArtifact.Type getArtifactType();
|
||||
|
||||
public BlackboardAttribute.Type getDateTimeAttrubuteType();
|
||||
/**
|
||||
* The attribute type this event type is derived from.
|
||||
*
|
||||
* @return The attribute type this event type is derived from.
|
||||
*/
|
||||
public BlackboardAttribute.Type getDateTimeAttributeType();
|
||||
|
||||
/**
|
||||
* Get the ID of the the artifact type that this EventType is derived from.
|
||||
*
|
||||
* @return the ID of the the artifact type that this EventType is derived
|
||||
* from.
|
||||
*/
|
||||
public default int getArtifactTypeID() {
|
||||
return getArtifactType().getTypeID();
|
||||
}
|
||||
|
||||
/**
|
||||
* given an artifact, pull out the time stamp, and compose the descriptions.
|
||||
@@ -57,7 +73,7 @@ public interface ArtifactEventType extends EventType {
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
default AttributeEventDescription parseAttributesHelper(BlackboardArtifact artf) throws TskCoreException {
|
||||
final BlackboardAttribute dateTimeAttr = artf.getAttribute(getDateTimeAttrubuteType());
|
||||
final BlackboardAttribute dateTimeAttr = artf.getAttribute(getDateTimeAttributeType());
|
||||
|
||||
long time = dateTimeAttr.getValueLong();
|
||||
String shortDescription = getShortExtractor().apply(artf);
|
||||
@@ -144,10 +160,10 @@ public interface ArtifactEventType extends EventType {
|
||||
static public AttributeEventDescription buildEventDescription(ArtifactEventType type, BlackboardArtifact artf) throws TskCoreException {
|
||||
//if we got passed an artifact that doesn't correspond to the type of the event,
|
||||
//something went very wrong. throw an exception.
|
||||
if (type.getArtifactType().getTypeID() != artf.getArtifactTypeID()) {
|
||||
if (type.getArtifactTypeID() != artf.getArtifactTypeID()) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (artf.getAttribute(type.getDateTimeAttrubuteType()) == null) {
|
||||
if (artf.getAttribute(type.getDateTimeAttributeType()) == null) {
|
||||
LOGGER.log(Level.WARNING, "Artifact {0} has no date/time attribute, skipping it.", artf.getArtifactID()); // NON-NLS
|
||||
return null;
|
||||
}
|
||||
@@ -184,8 +200,10 @@ public interface ArtifactEventType extends EventType {
|
||||
try {
|
||||
return artf.getAttribute(attrType);
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting extracting attribute from artifact {0}.", artf.getArtifactID()), ex); // NON-NLS
|
||||
LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting attribute from artifact {0}.", artf.getArtifactID()), ex); // NON-NLS
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public enum MiscTypes implements EventType, ArtifactEventType {
|
||||
final BlackboardAttribute latitude = getAttributeSafe(artf, new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE));
|
||||
return stringValueOf(latitude) + " " + stringValueOf(longitude); // NON-NLS
|
||||
},
|
||||
EMPTY_EXTRACTOR),
|
||||
new EmptyExtractor()),
|
||||
CALL_LOG(NbBundle.getMessage(MiscTypes.class, "MiscTypes.Calls.name"), "calllog.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_CALLLOG),
|
||||
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_START),
|
||||
@@ -104,7 +104,7 @@ public enum MiscTypes implements EventType, ArtifactEventType {
|
||||
|
||||
@Override
|
||||
public AttributeEventDescription parseAttributesHelper(BlackboardArtifact artf) throws TskCoreException {
|
||||
final BlackboardAttribute dateTimeAttr = artf.getAttribute(getDateTimeAttrubuteType());
|
||||
final BlackboardAttribute dateTimeAttr = artf.getAttribute(getDateTimeAttributeType());
|
||||
|
||||
long time = dateTimeAttr.getValueLong();
|
||||
|
||||
@@ -120,8 +120,8 @@ public enum MiscTypes implements EventType, ArtifactEventType {
|
||||
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_INSTALLED_PROG),
|
||||
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME),
|
||||
new AttributeExtractor(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_PROG_NAME)),
|
||||
EMPTY_EXTRACTOR,
|
||||
EMPTY_EXTRACTOR),
|
||||
new EmptyExtractor(),
|
||||
new EmptyExtractor()),
|
||||
EXIF(NbBundle.getMessage(MiscTypes.class, "MiscTypes.exif.name"), "camera-icon-16.png", // NON-NLS
|
||||
new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_METADATA_EXIF),
|
||||
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED),
|
||||
@@ -199,7 +199,7 @@ public enum MiscTypes implements EventType, ArtifactEventType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlackboardAttribute.Type getDateTimeAttrubuteType() {
|
||||
public BlackboardAttribute.Type getDateTimeAttributeType() {
|
||||
return dateTimeAttributeType;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ public enum WebTypes implements EventType, ArtifactEventType {
|
||||
|
||||
@Override
|
||||
public AttributeEventDescription parseAttributesHelper(BlackboardArtifact artf) throws TskCoreException {
|
||||
long time = artf.getAttribute(getDateTimeAttrubuteType()).getValueLong();
|
||||
long time = artf.getAttribute(getDateTimeAttributeType()).getValueLong();
|
||||
String domain = getShortExtractor().apply(artf);
|
||||
String path = getMedExtractor().apply(artf);
|
||||
String fileName = StringUtils.substringAfterLast(path, "/");
|
||||
@@ -103,7 +103,7 @@ public enum WebTypes implements EventType, ArtifactEventType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlackboardAttribute.Type getDateTimeAttrubuteType() {
|
||||
public BlackboardAttribute.Type getDateTimeAttributeType() {
|
||||
return dateTimeAttributeType;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.Tag;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
@@ -667,6 +669,69 @@ public class EventDB {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of event IDs for the events that are derived from the given
|
||||
* artifact.
|
||||
*
|
||||
* @param artifact The BlackboardArtifact to get derived event IDs for.
|
||||
*
|
||||
* @return A List of event IDs for the events that are derived from the
|
||||
* given artifact.
|
||||
*/
|
||||
List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) {
|
||||
DBLock.lock();
|
||||
|
||||
String query = "SELECT event_id FROM events WHERE artifact_id == " + artifact.getArtifactID();
|
||||
|
||||
ArrayList<Long> results = new ArrayList<>();
|
||||
try (Statement stmt = con.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(query);) {
|
||||
while (rs.next()) {
|
||||
results.add(rs.getLong("event_id"));
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error executing getEventIDsForArtifact query.", ex); // NON-NLS
|
||||
} finally {
|
||||
DBLock.unlock();
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of event IDs for the events that are derived from the given
|
||||
* file.
|
||||
*
|
||||
* @param file The AbstractFile to get derived event IDs
|
||||
* for.
|
||||
* @param includeDerivedArtifacts If true, also get event IDs for events
|
||||
* derived from artifacts derived form this
|
||||
* file. If false, only gets events derived
|
||||
* directly from this file (file system
|
||||
* timestamps).
|
||||
*
|
||||
* @return A List of event IDs for the events that are derived from the
|
||||
* given file.
|
||||
*/
|
||||
List<Long> getEventIDsForFile(AbstractFile file, boolean includeDerivedArtifacts) {
|
||||
DBLock.lock();
|
||||
|
||||
String query = "SELECT event_id FROM events WHERE file_id == " + file.getId()
|
||||
+ (includeDerivedArtifacts ? "" : " AND artifact_id IS NULL");
|
||||
|
||||
ArrayList<Long> results = new ArrayList<>();
|
||||
try (Statement stmt = con.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(query);) {
|
||||
while (rs.next()) {
|
||||
results.add(rs.getLong("event_id"));
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Error executing getEventIDsForFile query.", ex); // NON-NLS
|
||||
} finally {
|
||||
DBLock.unlock();
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* create the tags table if it doesn't already exist. This is broken out as
|
||||
* a separate method so it can be used by {@link #reInitializeTags() }
|
||||
|
||||
@@ -207,6 +207,38 @@ public class EventsRepository {
|
||||
return eventDB.countAllEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of event IDs for the events that are derived from the given
|
||||
* file.
|
||||
*
|
||||
* @param file The AbstractFile to get derived event IDs
|
||||
* for.
|
||||
* @param includeDerivedArtifacts If true, also get event IDs for events
|
||||
* derived from artifacts derived form this
|
||||
* file. If false, only gets events derived
|
||||
* directly from this file (file system
|
||||
* timestamps).
|
||||
*
|
||||
* @return A List of event IDs for the events that are derived from the
|
||||
* given file.
|
||||
*/
|
||||
public List<Long> getEventIDsForFile(AbstractFile file, boolean includedDerivedArtifacts) {
|
||||
return eventDB.getEventIDsForFile(file, includedDerivedArtifacts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a List of event IDs for the events that are derived from the given
|
||||
* artifact.
|
||||
*
|
||||
* @param artifact The BlackboardArtifact to get derived event IDs for.
|
||||
*
|
||||
* @return A List of event IDs for the events that are derived from the
|
||||
* given artifact.
|
||||
*/
|
||||
public List<Long> getEventIDsForArtifact(BlackboardArtifact artifact) {
|
||||
return eventDB.getEventIDsForArtifact(artifact);
|
||||
}
|
||||
|
||||
private void invalidateCaches() {
|
||||
minCache.invalidateAll();
|
||||
maxCache.invalidateAll();
|
||||
@@ -597,10 +629,10 @@ public class EventsRepository {
|
||||
timeMap.put(FileSystemTypes.FILE_MODIFIED, f.getMtime());
|
||||
|
||||
/*
|
||||
* if there are no legitimate ( greater than zero ) time stamps ( eg,
|
||||
* logical/local files) skip the rest of the event generation: this
|
||||
* should result in droping logical files, since they do not have
|
||||
* legitimate time stamps.
|
||||
* if there are no legitimate ( greater than zero ) time stamps (
|
||||
* eg, logical/local files) skip the rest of the event generation:
|
||||
* this should result in dropping logical files, since they do not
|
||||
* have legitimate time stamps.
|
||||
*/
|
||||
if (Collections.max(timeMap.values()) > 0) {
|
||||
final String uniquePath = f.getUniquePath();
|
||||
@@ -655,7 +687,7 @@ public class EventsRepository {
|
||||
private void populateEventType(final ArtifactEventType type, EventDB.EventTransaction trans) {
|
||||
try {
|
||||
//get all the blackboard artifacts corresponding to the given event sub_type
|
||||
final ArrayList<BlackboardArtifact> blackboardArtifacts = skCase.getBlackboardArtifacts(type.getArtifactType().getTypeID());
|
||||
final ArrayList<BlackboardArtifact> blackboardArtifacts = skCase.getBlackboardArtifacts(type.getArtifactTypeID());
|
||||
final int numArtifacts = blackboardArtifacts.size();
|
||||
restartProgressHandle(Bundle.progressWindow_populatingXevents(type.getDisplayName()), "", 0D, numArtifacts, true);
|
||||
for (int i = 0; i < numArtifacts; i++) {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.events;
|
||||
|
||||
import java.util.Set;
|
||||
import org.joda.time.Interval;
|
||||
|
||||
/**
|
||||
* Encapsulates the result of the ShowInTimelineDialog: a Set of event IDs and
|
||||
* an Interval.
|
||||
*/
|
||||
public final class ViewInTimelineRequestedEvent {
|
||||
|
||||
private final Set<Long> eventIDs;
|
||||
private final Interval range;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param eventIDs The event IDs to include.
|
||||
* @param range The Interval to show.
|
||||
*/
|
||||
public ViewInTimelineRequestedEvent(Set<Long> eventIDs, Interval range) {
|
||||
this.eventIDs = eventIDs;
|
||||
this.range = range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event IDs.
|
||||
*
|
||||
* @return The event IDs
|
||||
*/
|
||||
public Set<Long> getEventIDs() {
|
||||
return eventIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Interval.
|
||||
*
|
||||
* @return The Interval.
|
||||
*/
|
||||
public Interval getInterval() {
|
||||
return range;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.timeline.explorernodes;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -33,16 +34,18 @@ import org.openide.util.NbBundle;
|
||||
import org.openide.util.lookup.Lookups;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.datamodel.DataModelActionsFactory;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
|
||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
@@ -107,15 +110,41 @@ public class EventNode extends DisplayableItemNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({
|
||||
"EventNode.getAction.errorTitle=Error getting actions",
|
||||
"EventNode.getAction.linkedFileMessage=There was a problem getting actions for the selected result. "
|
||||
+ " The 'View File in Timeline' action will not be available."})
|
||||
public Action[] getActions(boolean context) {
|
||||
Action[] superActions = super.getActions(context);
|
||||
List<Action> actionsList = new ArrayList<>();
|
||||
actionsList.addAll(Arrays.asList(superActions));
|
||||
|
||||
final Content content = getLookup().lookup(Content.class);
|
||||
final BlackboardArtifact artifact = getLookup().lookup(BlackboardArtifact.class);
|
||||
final AbstractFile sourceFile = getLookup().lookup(AbstractFile.class);
|
||||
|
||||
final List<Action> factoryActions = DataModelActionsFactory.getActions(content, artifact != null);
|
||||
/*
|
||||
* if this event is derived from an artifact, add actions to view the
|
||||
* source file and a "linked" file, if present.
|
||||
*/
|
||||
final BlackboardArtifact artifact = getLookup().lookup(BlackboardArtifact.class);
|
||||
if (artifact != null) {
|
||||
try {
|
||||
AbstractFile linkedfile = findLinked(artifact);
|
||||
if (linkedfile != null) {
|
||||
actionsList.add(ViewFileInTimelineAction.createViewFileAction(linkedfile));
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting linked file from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(Bundle.EventNode_getAction_errorTitle(), Bundle.EventNode_getAction_linkedFileMessage());
|
||||
}
|
||||
|
||||
//if this event has associated content, add the action to view the content in the timeline
|
||||
if (null != sourceFile) {
|
||||
actionsList.add(ViewFileInTimelineAction.createViewSourceFileAction(sourceFile));
|
||||
}
|
||||
}
|
||||
|
||||
//get default actions for the source file
|
||||
final List<Action> factoryActions = DataModelActionsFactory.getActions(sourceFile, artifact != null);
|
||||
|
||||
actionsList.addAll(factoryActions);
|
||||
return actionsList.toArray(new Action[actionsList.size()]);
|
||||
@@ -207,4 +236,30 @@ public class EventNode extends DisplayableItemNode {
|
||||
return new EventNode(eventById, file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this code started as a cut and past of
|
||||
* DataResultFilterNode.GetPopupActionsDisplayableItemNodeVisitor.findLinked(BlackboardArtifactNode
|
||||
* ba)
|
||||
*
|
||||
* It is now in DisplayableItemNode too, but is not accesible across
|
||||
* packages
|
||||
*
|
||||
* @param artifact
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
static AbstractFile findLinked(BlackboardArtifact artifact) throws TskCoreException {
|
||||
|
||||
BlackboardAttribute pathIDAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID));
|
||||
|
||||
if (pathIDAttribute != null) {
|
||||
long contentID = pathIDAttribute.getValueLong();
|
||||
if (contentID != -1) {
|
||||
return artifact.getSleuthkitCase().getAbstractFileById(contentID);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +227,7 @@ public abstract class AbstractTimeLineView extends BorderPane {
|
||||
TimeLineController.getTimeZone().removeListener(updateListener);
|
||||
updateListener = null;
|
||||
filteredEvents.unRegisterForEvents(this);
|
||||
controller.unRegisterForEvents(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true" rotate="0.0" smooth="true" style="-fx-background-color:white;" x="2.0" y="1.0">
|
||||
<image>
|
||||
<Image url="@../images/20140521121247760_easyicon_net_32_colorized.png" />
|
||||
<Image url="@../images/btn_icon_timeline_colorized_32.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
|
||||
@@ -394,13 +394,8 @@ final public class ViewFrame extends BorderPane {
|
||||
zoomMenuButton.getItems().clear();
|
||||
for (ZoomRanges zoomRange : ZoomRanges.values()) {
|
||||
zoomMenuButton.getItems().add(ActionUtils.createMenuItem(
|
||||
new Action(zoomRange.getDisplayName(), event -> {
|
||||
if (zoomRange != ZoomRanges.ALL) {
|
||||
controller.pushPeriod(zoomRange.getPeriod());
|
||||
} else {
|
||||
controller.showFullRange();
|
||||
}
|
||||
})));
|
||||
new Action(zoomRange.getDisplayName(), event -> controller.pushPeriod(zoomRange.getPeriod()))
|
||||
));
|
||||
}
|
||||
zoomMenuButton.setText(Bundle.ViewFrame_zoomMenuButton_text());
|
||||
ActionUtils.configureButton(new ZoomOut(controller), zoomOutButton);
|
||||
@@ -643,55 +638,54 @@ final public class ViewFrame extends BorderPane {
|
||||
private void syncViewMode() {
|
||||
ViewMode newViewMode = controller.getViewMode();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
//clear out old view.
|
||||
if (hostedView != null) {
|
||||
hostedView.dispose();
|
||||
}
|
||||
//clear out old view.
|
||||
if (hostedView != null) {
|
||||
hostedView.dispose();
|
||||
}
|
||||
|
||||
//Set a new AbstractTimeLineView as the one hosted by this ViewFrame.
|
||||
switch (newViewMode) {
|
||||
case LIST:
|
||||
hostedView = new ListViewPane(controller);
|
||||
//TODO: should remove listeners from events tree
|
||||
break;
|
||||
case COUNTS:
|
||||
hostedView = new CountsViewPane(controller);
|
||||
//TODO: should remove listeners from events tree
|
||||
break;
|
||||
case DETAIL:
|
||||
DetailViewPane detailViewPane = new DetailViewPane(controller);
|
||||
//link events tree to detailview instance.
|
||||
detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents());
|
||||
eventsTree.setDetailViewPane(detailViewPane);
|
||||
hostedView = detailViewPane;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown ViewMode: " + newViewMode.toString());//NON-NLS
|
||||
}
|
||||
//Set a new AbstractTimeLineView as the one hosted by this ViewFrame.
|
||||
switch (newViewMode) {
|
||||
case LIST:
|
||||
hostedView = new ListViewPane(controller);
|
||||
//TODO: should remove listeners from events tree
|
||||
break;
|
||||
case COUNTS:
|
||||
hostedView = new CountsViewPane(controller);
|
||||
//TODO: should remove listeners from events tree
|
||||
break;
|
||||
case DETAIL:
|
||||
DetailViewPane detailViewPane = new DetailViewPane(controller);
|
||||
//link events tree to detailview instance.
|
||||
detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents());
|
||||
eventsTree.setDetailViewPane(detailViewPane);
|
||||
hostedView = detailViewPane;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown ViewMode: " + newViewMode.toString());//NON-NLS
|
||||
}
|
||||
controller.registerForEvents(hostedView);
|
||||
|
||||
viewModeToggleGroup.setValue(newViewMode); //this selects the right toggle automatically
|
||||
viewModeToggleGroup.setValue(newViewMode); //this selects the right toggle automatically
|
||||
|
||||
//configure settings and time navigation nodes
|
||||
setViewSettingsControls(hostedView.getSettingsControls());
|
||||
setTimeNavigationControls(hostedView.hasCustomTimeNavigationControls()
|
||||
? hostedView.getTimeNavigationControls()
|
||||
: defaultTimeNavigationNodes);
|
||||
//configure settings and time navigation nodes
|
||||
setViewSettingsControls(hostedView.getSettingsControls());
|
||||
setTimeNavigationControls(hostedView.hasCustomTimeNavigationControls()
|
||||
? hostedView.getTimeNavigationControls()
|
||||
: defaultTimeNavigationNodes);
|
||||
|
||||
//do further setup of new view.
|
||||
ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new view
|
||||
hostedView.refresh();
|
||||
notificationPane.setContent(hostedView);
|
||||
//listen to has events property and show "dialog" if it is false.
|
||||
hostedView.hasVisibleEventsProperty().addListener(hasEvents -> {
|
||||
notificationPane.setContent(hostedView.hasVisibleEvents()
|
||||
? hostedView
|
||||
: new StackPane(hostedView,
|
||||
NO_EVENTS_BACKGROUND,
|
||||
new NoEventsDialog(() -> notificationPane.setContent(hostedView))
|
||||
)
|
||||
);
|
||||
});
|
||||
//do further setup of new view.
|
||||
ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new view
|
||||
hostedView.refresh();
|
||||
notificationPane.setContent(hostedView);
|
||||
//listen to has events property and show "dialog" if it is false.
|
||||
hostedView.hasVisibleEventsProperty().addListener(hasEvents -> {
|
||||
notificationPane.setContent(hostedView.hasVisibleEvents()
|
||||
? hostedView
|
||||
: new StackPane(hostedView,
|
||||
NO_EVENTS_BACKGROUND,
|
||||
new NoEventsDialog(() -> notificationPane.setContent(hostedView))
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public enum ZoomRanges {
|
||||
THREE_YEARS(NbBundle.getMessage(ZoomRanges.class, "Timeline.ui.ZoomRanges.threeyears.text"), Years.THREE),
|
||||
FIVE_YEARS(NbBundle.getMessage(ZoomRanges.class, "Timeline.ui.ZoomRanges.fiveyears.text"), Years.years(5)),
|
||||
TEN_YEARS(NbBundle.getMessage(ZoomRanges.class, "Timeline.ui.ZoomRanges.tenyears.text"), Years.years(10)),
|
||||
ALL(NbBundle.getMessage(ZoomRanges.class, "Timeline.ui.ZoomRanges.all.text"), Minutes.ONE);
|
||||
ALL(NbBundle.getMessage(ZoomRanges.class, "Timeline.ui.ZoomRanges.all.text"), Years.years(1_000_000));
|
||||
|
||||
private ZoomRanges(String displayName, ReadablePeriod period) {
|
||||
this.displayName = displayName;
|
||||
|
||||
@@ -367,7 +367,7 @@ public class DetailViewPane extends AbstractTimelineChart<DateTime, EventStripe,
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"DetailViewPane.loggedTask.queryDb=Retreiving event data",
|
||||
"DetailViewPane.loggedTask.queryDb=Retrieving event data",
|
||||
"DetailViewPane.loggedTask.name=Updating Details View",
|
||||
"DetailViewPane.loggedTask.updateUI=Populating view",
|
||||
"DetailViewPane.loggedTask.continueButton=Continue",
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
package org.sleuthkit.autopsy.timeline.ui.listvew;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.math.DoubleMath;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
@@ -30,7 +32,6 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
@@ -40,14 +41,12 @@ import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.IntegerBinding;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
@@ -55,7 +54,6 @@ import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.OverrunStyle;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
@@ -73,7 +71,6 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.JMenuItem;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.controlsfx.control.Notifications;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.openide.awt.Actions;
|
||||
@@ -82,6 +79,7 @@ import org.openide.util.actions.Presenter;
|
||||
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.timeline.ChronoFieldListCell;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
|
||||
@@ -111,9 +109,10 @@ class ListTimeline extends BorderPane {
|
||||
private static final Image LAST = new Image("/org/sleuthkit/autopsy/timeline/images/resultset_last.png"); //NON-NLS
|
||||
|
||||
/**
|
||||
* call-back used to wrap the CombinedEvent in a ObservableValue
|
||||
* call-back used to wrap a CombinedEvent in a ObservableValue
|
||||
*/
|
||||
private static final Callback<TableColumn.CellDataFeatures<CombinedEvent, CombinedEvent>, ObservableValue<CombinedEvent>> CELL_VALUE_FACTORY = param -> new SimpleObjectProperty<>(param.getValue());
|
||||
|
||||
private static final List<ChronoField> SCROLL_BY_UNITS = Arrays.asList(
|
||||
ChronoField.YEAR,
|
||||
ChronoField.MONTH_OF_YEAR,
|
||||
@@ -122,6 +121,8 @@ class ListTimeline extends BorderPane {
|
||||
ChronoField.MINUTE_OF_HOUR,
|
||||
ChronoField.SECOND_OF_MINUTE);
|
||||
|
||||
private static final int DEFAULT_ROW_HEIGHT = 24;
|
||||
|
||||
@FXML
|
||||
private HBox navControls;
|
||||
|
||||
@@ -160,23 +161,36 @@ class ListTimeline extends BorderPane {
|
||||
private TableColumn<CombinedEvent, CombinedEvent> hashHitColumn;
|
||||
|
||||
/**
|
||||
* Observable list used to track selected events.
|
||||
* Since TableView does not expose what cells/items are visible, we track
|
||||
* them in this set. It is sorted by index in the TableView's model.
|
||||
*/
|
||||
private final ObservableList<Long> selectedEventIDs = FXCollections.observableArrayList();
|
||||
|
||||
private final ConcurrentSkipListSet<CombinedEvent> visibleEvents;
|
||||
private final SortedSet<CombinedEvent> visibleEvents;
|
||||
|
||||
private final TimeLineController controller;
|
||||
private final SleuthkitCase sleuthkitCase;
|
||||
private final TagsManager tagsManager;
|
||||
|
||||
/**
|
||||
* Listener attached to the table's selection model that pushes that
|
||||
* selection to the controller. Maps from Combined event in table to EventID
|
||||
* in controller via CombinedEvent.getRepresentativeEventID.
|
||||
*/
|
||||
private final ListChangeListener<CombinedEvent> selectedEventListener = new ListChangeListener<CombinedEvent>() {
|
||||
@Override
|
||||
public void onChanged(ListChangeListener.Change<? extends CombinedEvent> c) {
|
||||
controller.selectEventIDs(table.getSelectionModel().getSelectedItems().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(CombinedEvent::getRepresentativeEventID)
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The controller for this timeline
|
||||
*/
|
||||
ListTimeline(TimeLineController controller) {
|
||||
|
||||
this.controller = controller;
|
||||
sleuthkitCase = controller.getAutopsyCase().getSleuthkitCase();
|
||||
tagsManager = controller.getAutopsyCase().getServices().getTagsManager();
|
||||
@@ -197,17 +211,17 @@ class ListTimeline extends BorderPane {
|
||||
assert typeColumn != null : "fx:id=\"typeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
assert knownColumn != null : "fx:id=\"knownColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
|
||||
//configure scroll controls
|
||||
scrollInrementComboBox.setButtonCell(new ChronoFieldListCell());
|
||||
scrollInrementComboBox.setCellFactory(comboBox -> new ChronoFieldListCell());
|
||||
scrollInrementComboBox.getItems().setAll(SCROLL_BY_UNITS);
|
||||
scrollInrementComboBox.getSelectionModel().select(ChronoField.YEAR);
|
||||
|
||||
ActionUtils.configureButton(new ScrollToFirst(), firstButton);
|
||||
ActionUtils.configureButton(new ScrollToPrevious(), previousButton);
|
||||
ActionUtils.configureButton(new ScrollToNext(), nextButton);
|
||||
ActionUtils.configureButton(new ScrollToLast(), lastButton);
|
||||
|
||||
//override default row with one that provides context menus
|
||||
//override default table row with one that provides context menus
|
||||
table.setRowFactory(tableView -> new EventRow());
|
||||
|
||||
//remove idColumn (can be restored for debugging).
|
||||
@@ -247,22 +261,10 @@ class ListTimeline extends BorderPane {
|
||||
}
|
||||
});
|
||||
|
||||
// use listener to keep controller selection in sync with table selection.
|
||||
table.getSelectionModel().getSelectedItems().addListener(selectedEventListener);
|
||||
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
table.getSelectionModel().getSelectedItems().addListener((Observable observable) -> {
|
||||
//keep the selectedEventsIDs in sync with the table's selection model, via getRepresentitiveEventID().
|
||||
selectedEventIDs.setAll(table.getSelectionModel().getSelectedItems().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(CombinedEvent::getRepresentativeEventID)
|
||||
.collect(Collectors.toSet()));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the events out of the table.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
void clear() {
|
||||
table.getItems().clear();
|
||||
selectEvents(controller.getSelectedEventIDs()); //grab initial selection
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,51 +277,83 @@ class ListTimeline extends BorderPane {
|
||||
table.getItems().setAll(events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an ObservableList of IDs of events that are selected in this table.
|
||||
*
|
||||
* @return An ObservableList of IDs of events that are selected in this
|
||||
* table.
|
||||
*/
|
||||
ObservableList<Long> getSelectedEventIDs() {
|
||||
return selectedEventIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an ObservableList of combined events that are selected in this table.
|
||||
*
|
||||
* @return An ObservableList of combined events that are selected in this
|
||||
* table.
|
||||
*/
|
||||
ObservableList<CombinedEvent> getSelectedEvents() {
|
||||
return table.getSelectionModel().getSelectedItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the combined events that are selected in this view.
|
||||
*
|
||||
* @param selectedEvents The events that should be selected.
|
||||
* @param selectedEventIDs The events that should be selected.
|
||||
*/
|
||||
void selectEvents(Collection<CombinedEvent> selectedEvents) {
|
||||
CombinedEvent firstSelected = selectedEvents.stream().min(Comparator.comparing(CombinedEvent::getStartMillis)).orElse(null);
|
||||
table.getSelectionModel().clearSelection();
|
||||
table.scrollTo(firstSelected);
|
||||
selectedEvents.forEach(table.getSelectionModel()::select);
|
||||
table.requestFocus();
|
||||
void selectEvents(Collection<Long> selectedEventIDs) {
|
||||
if (selectedEventIDs.isEmpty()) {
|
||||
//this is the final selection, so we don't need to mess with the listener
|
||||
table.getSelectionModel().clearSelection();
|
||||
} else {
|
||||
/*
|
||||
* Changes in the table selection are propogated to the controller
|
||||
* by a listener. There is no API on TableView's selection model to
|
||||
* clear the selection and select multiple rows as one "action".
|
||||
* Therefore we clear the selection and then make the new selection,
|
||||
* but we don't want this intermediate state of no selection to be
|
||||
* pushed to the controller as it interferes with maintaining the
|
||||
* right selection. To avoid notifying the controller, we remove the
|
||||
* listener, clear the selection, then re-attach it.
|
||||
*/
|
||||
table.getSelectionModel().getSelectedItems().removeListener(selectedEventListener);
|
||||
|
||||
table.getSelectionModel().clearSelection();
|
||||
|
||||
table.getSelectionModel().getSelectedItems().addListener(selectedEventListener);
|
||||
|
||||
//find the indices of the CombinedEvents that will be selected
|
||||
int[] selectedIndices = table.getItems().stream()
|
||||
.filter(combinedEvent -> Collections.disjoint(combinedEvent.getEventIDs(), selectedEventIDs) == false)
|
||||
.mapToInt(table.getItems()::indexOf)
|
||||
.toArray();
|
||||
|
||||
//select indices and scroll to the first one
|
||||
if (selectedIndices.length > 0) {
|
||||
Integer firstSelectedIndex = selectedIndices[0];
|
||||
table.getSelectionModel().selectIndices(firstSelectedIndex, selectedIndices);
|
||||
scrollTo(firstSelectedIndex);
|
||||
table.requestFocus(); //grab focus so selection is clearer to user
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Node> getNavControls() {
|
||||
/**
|
||||
* Get the time navigation controls that this ListTimeline's parent
|
||||
* ListViewPane will provide to its host ViewFrame.
|
||||
*
|
||||
* @return A List of time navigation controls in the from of JavaFX scene
|
||||
* graph Nodes.
|
||||
*/
|
||||
List<Node> getTimeNavigationControls() {
|
||||
return Collections.singletonList(navControls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the table to the given index (if it is not already visible) and
|
||||
* focus it.
|
||||
*
|
||||
* @param index The index of the item that should be scrolled in to view and
|
||||
* focused.
|
||||
*/
|
||||
private void scrollToAndFocus(Integer index) {
|
||||
table.requestFocus();
|
||||
if (visibleEvents.contains(table.getItems().get(index)) == false) {
|
||||
table.scrollTo(index);
|
||||
}
|
||||
scrollTo(index);
|
||||
table.getFocusModel().focus(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the table to the given index (if it is not already visible).
|
||||
*
|
||||
* @param index The index of the item that should be scrolled in to view.
|
||||
*/
|
||||
private void scrollTo(Integer index) {
|
||||
if (visibleEvents.contains(table.getItems().get(index)) == false) {
|
||||
table.scrollTo(DoubleMath.roundToInt(index - ((table.getHeight() / DEFAULT_ROW_HEIGHT)) / 2, RoundingMode.HALF_EVEN));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TableCell to show the (sub) type of an event.
|
||||
*/
|
||||
@@ -483,7 +517,7 @@ class ListTimeline extends BorderPane {
|
||||
setTooltip(null);
|
||||
} else {
|
||||
/*
|
||||
* if the cell is not empty and the event's file is a hash hit,
|
||||
* If the cell is not empty and the event's file is a hash hit,
|
||||
* show the hash hit icon, and show a list of hash set names in
|
||||
* the tooltip
|
||||
*/
|
||||
@@ -650,21 +684,6 @@ class ListTimeline extends BorderPane {
|
||||
}
|
||||
}
|
||||
|
||||
private class ChronoFieldListCell extends ListCell<ChronoField> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(ChronoField item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
String displayName = item.getDisplayName(Locale.getDefault());
|
||||
setText(String.join(" ", StringUtils.splitByCharacterTypeCamelCase(displayName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ScrollToFirst extends org.controlsfx.control.action.Action {
|
||||
|
||||
ScrollToFirst() {
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
package org.sleuthkit.autopsy.timeline.ui.listvew;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.HashSet;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.scene.Node;
|
||||
import org.joda.time.Interval;
|
||||
@@ -31,6 +32,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.ViewMode;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.events.ViewInTimelineRequestedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimeLineView;
|
||||
|
||||
/**
|
||||
@@ -47,15 +49,12 @@ public class ListViewPane extends AbstractTimeLineView {
|
||||
*/
|
||||
public ListViewPane(TimeLineController controller) {
|
||||
super(controller);
|
||||
|
||||
listTimeline = new ListTimeline(controller);
|
||||
|
||||
//initialize chart;
|
||||
setCenter(listTimeline);
|
||||
|
||||
//keep controller's list of selected event IDs in sync with this list's
|
||||
listTimeline.getSelectedEventIDs().addListener((Observable selectedIDs) -> {
|
||||
controller.selectEventIDs(listTimeline.getSelectedEventIDs());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,9 +62,14 @@ public class ListViewPane extends AbstractTimeLineView {
|
||||
return new ListUpdateTask();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is supposed to clear all the data from this View, but it
|
||||
* might have been interfering with the "View in Timeline" action and was
|
||||
* not strictly necessary so this implementation is a no-op.
|
||||
*/
|
||||
@Override
|
||||
protected void clearData() {
|
||||
listTimeline.clear();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,7 +84,7 @@ public class ListViewPane extends AbstractTimeLineView {
|
||||
|
||||
@Override
|
||||
protected ImmutableList<Node> getTimeNavigationControls() {
|
||||
return ImmutableList.copyOf(listTimeline.getNavControls());
|
||||
return ImmutableList.copyOf(listTimeline.getTimeNavigationControls());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,10 +92,15 @@ public class ListViewPane extends AbstractTimeLineView {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void handleViewInTimelineRequested(ViewInTimelineRequestedEvent event) {
|
||||
listTimeline.selectEvents(event.getEventIDs());
|
||||
}
|
||||
|
||||
private class ListUpdateTask extends ViewRefreshTask<Interval> {
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ListViewPane.loggedTask.queryDb=Retreiving event data",
|
||||
"ListViewPane.loggedTask.queryDb=Retrieving event data",
|
||||
"ListViewPane.loggedTask.name=Updating List View",
|
||||
"ListViewPane.loggedTask.updateUI=Populating view"})
|
||||
ListUpdateTask() {
|
||||
@@ -107,8 +116,12 @@ public class ListViewPane extends AbstractTimeLineView {
|
||||
|
||||
FilteredEventsModel eventsModel = getEventsModel();
|
||||
|
||||
Set<Long> selectedEventIDs;
|
||||
TimeLineController controller = getController();
|
||||
//grab the currently selected event
|
||||
HashSet<CombinedEvent> selectedEvents = new HashSet<>(listTimeline.getSelectedEvents());
|
||||
synchronized (controller) {
|
||||
selectedEventIDs = ImmutableSet.copyOf(controller.getSelectedEventIDs());
|
||||
}
|
||||
|
||||
//clear the chart and set the time range.
|
||||
resetView(eventsModel.getTimeRange());
|
||||
@@ -121,12 +134,11 @@ public class ListViewPane extends AbstractTimeLineView {
|
||||
Platform.runLater(() -> {
|
||||
//put the combined events into the table.
|
||||
listTimeline.setCombinedEvents(combinedEvents);
|
||||
//restore the selected event
|
||||
listTimeline.selectEvents(selectedEvents);
|
||||
//restore the selected events
|
||||
listTimeline.selectEvents(selectedEventIDs);
|
||||
});
|
||||
|
||||
return combinedEvents.isEmpty() == false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.utils;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.TemporalAmount;
|
||||
import java.util.Collection;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
@@ -68,6 +70,13 @@ public class IntervalUtils {
|
||||
return newInterval;
|
||||
}
|
||||
|
||||
static public Interval getIntervalAround(Instant aroundInstant, TemporalAmount temporalAmount) {
|
||||
long start = aroundInstant.minus(temporalAmount).toEpochMilli();
|
||||
long end = aroundInstant.plusMillis(1).plus(temporalAmount).toEpochMilli();
|
||||
final Interval newInterval = new Interval(start, Math.max(start + 1, end));
|
||||
return newInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an interval the length of the given period, centered around the
|
||||
* center of the given interval.
|
||||
|
||||
@@ -454,7 +454,7 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis
|
||||
chRegex.setSelected(false);
|
||||
addWordField.setText("");
|
||||
pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
|
||||
|
||||
setFocusOnKeywordTextBox();
|
||||
setButtonStates();
|
||||
}//GEN-LAST:event_addWordButtonActionPerformed
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
@@ -65,6 +66,12 @@ class GlobalListsManagementPanel extends javax.swing.JPanel implements OptionsPa
|
||||
listsTable.setRowSelectionAllowed(true);
|
||||
tableModel.resync();
|
||||
|
||||
listsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
globalListSettingsPanel.setFocusOnKeywordTextBox();
|
||||
}
|
||||
});
|
||||
/*
|
||||
* XmlKeywordSearchList.getCurrent().addPropertyChangeListener(new
|
||||
* PropertyChangeListener() {
|
||||
|
||||
+3
-1
@@ -53,6 +53,7 @@
|
||||
<include name="libeay32.dll"/>
|
||||
<include name="ssleay32.dll"/>
|
||||
<include name="libintl-8.dll"/>
|
||||
<include name="libiconv-2.dll"/>
|
||||
<include name="libpq.dll"/>
|
||||
<include name="msvcr120.dll"/>
|
||||
</fileset>
|
||||
@@ -80,7 +81,8 @@
|
||||
<fileset dir="${win32.TskLib.postgres_path}" id="postgres32dlls">
|
||||
<include name="libeay32.dll"/>
|
||||
<include name="ssleay32.dll"/>
|
||||
<include name="intl.dll"/>
|
||||
<include name="libintl-8.dll"/>
|
||||
<include name="libiconv-2.dll"/>
|
||||
<include name="libpq.dll"/>
|
||||
<include name="msvcr120.dll"/>
|
||||
</fileset>
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
<include name="libvhdi.dll"/>
|
||||
<include name="zlib.dll" />
|
||||
<include name="libintl-8.dll"/>
|
||||
<include name="libiconv-2.dll"/>
|
||||
<include name="ssleay32.dll"/>
|
||||
<include name="libeay32.dll"/>
|
||||
<include name="libpq.dll"/>
|
||||
|
||||
Regular → Executable
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 102 KiB |
Executable → Regular
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@@ -5,7 +5,7 @@ To install ActiveMQ, perform the following steps:
|
||||
|
||||
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
|
||||
- Download ActiveMQ-5.13.3 from: http://activemq.apache.org/activemq-5133-release.html
|
||||
|
||||
|
||||
\section install_activemq_install Installation
|
||||
@@ -20,7 +20,7 @@ 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.
|
||||
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.13.3</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>"&wireFormat.maxInactivityDuration=0"</i> to the URI for the _transportConnector_ named _openwire_. Add the text highlighted in yellow below:
|
||||
<br><br>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/*! \page install_postgresql Install and Configure PostgreSQL
|
||||
To install PostgreSQL, perform the following steps:
|
||||
|
||||
1. Download a 64-bit PostgreSQL version 9.4.1 installer from http://www.enterprisedb.com/products-services-training/pgdownload#windows Choose the one that says _Win X86-64_.
|
||||
1. Download a 64-bit PostgreSQL version 9.5.3 installer from http://www.enterprisedb.com/products-services-training/pgdownload#windows Choose the one that says _Win X86-64_.
|
||||
|
||||
2. Run _postgresql-9.4.4-1-windows-x64.exe_
|
||||
2. Run _postgresql-9.5.3-1-windows-x64.exe_
|
||||
|
||||
3. You may accept defaults for all items except for the password as you work through the wizard. Do not lose the password you enter in. This is the PostgreSQL administrator login password.
|
||||
|
||||
@@ -49,7 +49,7 @@ When you see the _CREATE ROLE_ output as shown in the screenshot below, the new
|
||||
\image html postgresqlinstall2.PNG
|
||||
<br>
|
||||
|
||||
6. Edit <i>C:\\Program Files\\PostgreSQL\\9.4\\data\\pg_hba.conf</i> to add an entry to allow external computers to connect via the network.
|
||||
6. Edit <i>C:\\Program Files\\PostgreSQL\\9.5\\data\\pg_hba.conf</i> to add an entry to allow external computers to connect via the network.
|
||||
<br><br>
|
||||
First, find your machine's IPv4 address and Subnet Mask (Press _Start_, type _cmd_, type _ipconfig_ and parse the results. The IP address is shown in yellow below.
|
||||
<br>
|
||||
@@ -72,7 +72,7 @@ Add the line highlighted in yellow below, formatted with spaces between the entr
|
||||
If you intend to use PostgreSQL from machines on a different subnet, you need an entry in the _pg_hba.conf_ file for each subnet.
|
||||
<br><br>
|
||||
|
||||
7. Uncomment the following entires in the configuration file located at <i>C:\\Program Files\\PostgreSQL\\9.4\\data\\postgresql.conf</i> by removing the leading "#", and change their values <i>"off"</i> as shown below.
|
||||
7. Uncomment the following entires in the configuration file located at <i>C:\\Program Files\\PostgreSQL\\9.5\\data\\postgresql.conf</i> by removing the leading "#", and change their values <i>"off"</i> as shown below.
|
||||
<br>
|
||||
> fsync = off<br>
|
||||
> synchronous_commit = off<br>
|
||||
@@ -89,12 +89,12 @@ To this:
|
||||
Note the removal of the leading number symbol-this uncomments that entry.
|
||||
<br><br>
|
||||
|
||||
8. Still in <i id="max_connections">"C:\Program Files\PostgreSQL\9.4\data\postgresql.conf"</i>, find the entry named _max_connections_ and set it to the number of suggested connections for your configuration. A rule of thumb is add 100 connections for each Automated Ingest Node and 100 connections for each Reviewer node you plan to have in the network. More information is available at 5.1.1. See the screenshot below.
|
||||
8. Still in <i id="max_connections">"C:\Program Files\PostgreSQL\9.5\data\postgresql.conf"</i>, find the entry named _max_connections_ and set it to the number of suggested connections for your configuration. A rule of thumb is add 100 connections for each Automated Ingest Node and 100 connections for each Reviewer node you plan to have in the network. More information is available at 5.1.1. See the screenshot below.
|
||||
<br><br>
|
||||
\image html maxConnections.PNG
|
||||
<br><br>
|
||||
|
||||
9. Press _Start_, type _services.msc_, and press _Enter_. Select _postgresql-x64-9.4 PostgreSQL Server 9.4_ in the services list and click the link that says _Stop the service_ then click the link that says _Start the service_ as shown in the screenshot below.
|
||||
9. Press _Start_, type _services.msc_, and press _Enter_. Select _postgresql-x64-9.5_ in the services list and click the link that says _Stop the service_ then click the link that says _Start the service_ as shown in the screenshot below.
|
||||
<br><br>
|
||||
\image html postgresqlinstall7.PNG
|
||||
<br><br>
|
||||
|
||||
@@ -7,7 +7,7 @@ A central Solr server is needed to store keyword indexes. To install Solr, perfo
|
||||
|
||||
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.
|
||||
- Download the Apache Solr 4.10.3-0 installation package from https://sourceforge.net/projects/autopsy/files/CollaborativeServices/Solr or <a href="https://sourceforge.net/projects/autopsy/files/CollaborativeServices/Solr/bitnami-solr-4.10.3-0-windows-installer.exe/download">Direct Download Link</a>
|
||||
- Access to an installed version of Autopsy so that you can copy files from it.
|
||||
- A network-accessible machine to install Solr upon. Note that the Solr process will need to write data out to the main shared storage drive, and needs adequate permissions to write to this location, which may be across a network.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user