mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-16 17:57:43 +00:00
Merge remote-tracking branch 'upstream/TL-list-view' into 479-view-in-timeline
This commit is contained in:
commit
6cd071577e
@ -58,7 +58,7 @@ the needed places (i.e. '/usr/local').
|
||||
3) For 32-bit targets, get GStreamer Setup. GStreamer is used to view video files.
|
||||
You can either download it and install it or manually by unzipping the
|
||||
version that is included in the 'thirdparty/gstreamer' folder. You
|
||||
will need the 'bin' and 'lib/gstreamer-1.0' folders to be in your
|
||||
will need the 'bin' and 'lib/gstreamer-0.10' folders to be in your
|
||||
Windows PATH environment variable.
|
||||
|
||||
NOTE: This has not been fully tested in non-Windows environments
|
||||
|
@ -15,6 +15,22 @@
|
||||
<specification-version>1.28.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.netbeans.api.progress.compat8</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>1.46.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.netbeans.api.progress.nb</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>1.46.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.netbeans.core</code-name-base>
|
||||
<build-prerequisite/>
|
||||
@ -123,6 +139,22 @@
|
||||
<specification-version>7.62.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.openide.filesystems.compat8</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>9.7.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.openide.filesystems.nb</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>9.7.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.openide.modules</code-name-base>
|
||||
<build-prerequisite/>
|
||||
@ -163,6 +195,14 @@
|
||||
<specification-version>8.15.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.openide.util.ui</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>9.4.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.openide.windows</code-name-base>
|
||||
<build-prerequisite/>
|
||||
|
@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2015 Basis Technology Corp.
|
||||
*
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* 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.
|
||||
@ -19,6 +19,7 @@
|
||||
package org.sleuthkit.autopsy.actions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
@ -26,8 +27,8 @@ import org.openide.util.NbBundle;
|
||||
import org.openide.util.Utilities;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
@ -38,7 +39,6 @@ public class AddBlackboardArtifactTagAction extends AddTagAction {
|
||||
// This class is a singleton to support multi-selection of nodes, since
|
||||
// org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every
|
||||
// node in the array returns a reference to the same action object from Node.getActions(boolean).
|
||||
|
||||
private static AddBlackboardArtifactTagAction instance;
|
||||
|
||||
public static synchronized AddBlackboardArtifactTagAction getInstance() {
|
||||
@ -63,7 +63,14 @@ public class AddBlackboardArtifactTagAction extends AddTagAction {
|
||||
|
||||
@Override
|
||||
protected void addTag(TagName tagName, String comment) {
|
||||
final Collection<? extends BlackboardArtifact> selectedArtifacts = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class);
|
||||
/*
|
||||
* The documentation for Lookup.lookupAll() explicitly says that the
|
||||
* collection it returns may contain duplicates. Within this invocation
|
||||
* of addTag(), we don't want to tag the same BlackboardArtifact more
|
||||
* than once, so we dedupe the BlackboardArtifacts by stuffing them into
|
||||
* a HashSet.
|
||||
*/
|
||||
final Collection<BlackboardArtifact> selectedArtifacts = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class));
|
||||
|
||||
new Thread(() -> {
|
||||
for (BlackboardArtifact artifact : selectedArtifacts) {
|
||||
|
@ -1,15 +1,15 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2015 Basis Technology Corp.
|
||||
*
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* 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.
|
||||
@ -19,13 +19,13 @@
|
||||
package org.sleuthkit.autopsy.actions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.Utilities;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
@ -40,7 +40,6 @@ public class AddContentTagAction extends AddTagAction {
|
||||
// This class is a singleton to support multi-selection of nodes, since
|
||||
// org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every
|
||||
// node in the array returns a reference to the same action object from Node.getActions(boolean).
|
||||
|
||||
private static AddContentTagAction instance;
|
||||
|
||||
public static synchronized AddContentTagAction getInstance() {
|
||||
@ -63,7 +62,13 @@ public class AddContentTagAction extends AddTagAction {
|
||||
|
||||
@Override
|
||||
protected void addTag(TagName tagName, String comment) {
|
||||
final Collection<? extends AbstractFile> selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class);
|
||||
/*
|
||||
* The documentation for Lookup.lookupAll() explicitly says that the
|
||||
* collection it returns may contain duplicates. Within this invocation
|
||||
* of addTag(), we don't want to tag the same AbstractFile more than
|
||||
* once, so we dedupe the AbstractFiles by stuffing them into a HashSet.
|
||||
*/
|
||||
final Collection<AbstractFile> selectedFiles = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
||||
|
||||
new Thread(() -> {
|
||||
for (AbstractFile file : selectedFiles) {
|
||||
|
@ -25,17 +25,30 @@ import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskDataException;
|
||||
|
||||
/**
|
||||
* Represents the blackboard, a place where artifacts and their attributes are
|
||||
* posted.
|
||||
* A representation of the blackboard, a place where artifacts and their
|
||||
* attributes are posted.
|
||||
*
|
||||
* NOTE: This API of this class is under development.
|
||||
*/
|
||||
public final class Blackboard implements Closeable {
|
||||
|
||||
private SleuthkitCase caseDb;
|
||||
|
||||
/**
|
||||
* Constructs a representation of the blackboard, a place where artifacts
|
||||
* and their attributes are posted.
|
||||
*
|
||||
* @param casedb The case database.
|
||||
*/
|
||||
Blackboard(SleuthkitCase casedb) {
|
||||
this.caseDb = casedb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indexes the text associated with the an artifact.
|
||||
*
|
||||
@ -43,7 +56,10 @@ public final class Blackboard implements Closeable {
|
||||
*
|
||||
* @throws BlackboardException If there is a problem indexing the artifact.
|
||||
*/
|
||||
public void indexArtifact(BlackboardArtifact artifact) throws BlackboardException {
|
||||
public synchronized void indexArtifact(BlackboardArtifact artifact) throws BlackboardException {
|
||||
if (null == caseDb) {
|
||||
throw new BlackboardException("Blackboard has been closed");
|
||||
}
|
||||
KeywordSearchService searchService = Lookup.getDefault().lookup(KeywordSearchService.class);
|
||||
if (null == searchService) {
|
||||
throw new BlackboardException("Keyword search service not found");
|
||||
@ -67,12 +83,15 @@ public final class Blackboard implements Closeable {
|
||||
* @throws BlackboardBlackboardException If there is a problem getting or
|
||||
* adding the artifact type.
|
||||
*/
|
||||
public BlackboardArtifact.Type getOrAddArtifactType(String typeName, String displayName) throws BlackboardException {
|
||||
public synchronized BlackboardArtifact.Type getOrAddArtifactType(String typeName, String displayName) throws BlackboardException {
|
||||
if (null == caseDb) {
|
||||
throw new BlackboardException("Blackboard has been closed");
|
||||
}
|
||||
try {
|
||||
return Case.getCurrentCase().getSleuthkitCase().addBlackboardArtifactType(typeName, displayName);
|
||||
return caseDb.addBlackboardArtifactType(typeName, displayName);
|
||||
} catch (TskDataException typeExistsEx) {
|
||||
try {
|
||||
return Case.getCurrentCase().getSleuthkitCase().getArtifactType(typeName);
|
||||
return caseDb.getArtifactType(typeName);
|
||||
} catch (TskCoreException ex) {
|
||||
throw new BlackboardException("Failed to get or add artifact type", ex);
|
||||
}
|
||||
@ -94,12 +113,15 @@ public final class Blackboard implements Closeable {
|
||||
* @throws BlackboardBlackboardException If there is a problem getting or
|
||||
* adding the attribute type.
|
||||
*/
|
||||
public BlackboardAttribute.Type getOrAddAttributeType(String typeName, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE valueType, String displayName) throws BlackboardException {
|
||||
public synchronized BlackboardAttribute.Type getOrAddAttributeType(String typeName, BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE valueType, String displayName) throws BlackboardException {
|
||||
if (null == caseDb) {
|
||||
throw new BlackboardException("Blackboard has been closed");
|
||||
}
|
||||
try {
|
||||
return Case.getCurrentCase().getSleuthkitCase().addArtifactAttributeType(typeName, valueType, displayName);
|
||||
return caseDb.addArtifactAttributeType(typeName, valueType, displayName);
|
||||
} catch (TskDataException typeExistsEx) {
|
||||
try {
|
||||
return Case.getCurrentCase().getSleuthkitCase().getAttributeType(typeName);
|
||||
return caseDb.getAttributeType(typeName);
|
||||
} catch (TskCoreException ex) {
|
||||
throw new BlackboardException("Failed to get or add attribute type", ex);
|
||||
}
|
||||
@ -109,13 +131,16 @@ public final class Blackboard implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cloese this blackboard and releases any resources associated with it.
|
||||
* @throws IOException
|
||||
* Closes the blackboard.
|
||||
*
|
||||
* @throws IOException If there is a problem closing the blackboard.
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
public synchronized void close() throws IOException {
|
||||
caseDb = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A blackboard exception.
|
||||
*/
|
||||
|
@ -1,16 +1,3 @@
|
||||
FileManager.findFiles.exception.msg=Attempted to use FileManager after it was closed.
|
||||
FileManager.findFiles2.exception.msg=Attempted to use FileManager after it was closed.
|
||||
FileManager.findFiles3.exception.msg=Attempted to use FileManager after it was closed.
|
||||
FileManager.openFiles.exception.msg=Attempted to use FileManager after it was closed.
|
||||
FileManager.addDerivedFile.exception.msg=Attempted to use FileManager after it was closed.
|
||||
FileManager.addCarvedFile.exception.msg=Attempted to use FileManager after it was closed.
|
||||
FileManager.addLocalFilesDirs.exception.notReadable.msg=One of the local files/dirs to add is not readable\: {0}, aborting the process before any files added
|
||||
FileManager.addLocalFilesDirs.exception.cantAdd.msg=One of the local files/dirs could not be added\: {0}
|
||||
FileManager.addLocalFileSetRootDir.exception.errCreateDir.msg=Error creating local file set dir\: {0}
|
||||
FileManager.addLocalDirInt.exception.closed.msg=Attempted to use FileManager after it was closed.
|
||||
FileManager.addLocalDirInt.exception.doesntExist.msg=Attempted to add a local dir that does not exist\: {0}
|
||||
FileManager.addLocalDirInt.exception.notReadable.msg=Attempted to add a local dir that is not readable\: {0}
|
||||
FileManager.addLocalDirInt2.exception.closed.msg=Attempted to use FileManager after it was closed.
|
||||
TagsManager.addContentTag.exception.beginByteOffsetOOR.msg=beginByteOffset \= {0} out of content size range (0 - {1})
|
||||
TagsManager.addContentTag.exception.endByteOffsetOOR.msg=endByteOffset \= {0} out of content size range (0 - {1})
|
||||
TagsManager.addContentTag.exception.endLTbegin.msg=endByteOffset < beginByteOffset
|
||||
|
@ -1,16 +1,3 @@
|
||||
FileManager.findFiles.exception.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002
|
||||
FileManager.findFiles2.exception.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002
|
||||
FileManager.findFiles3.exception.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002
|
||||
FileManager.openFiles.exception.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002
|
||||
FileManager.addDerivedFile.exception.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002
|
||||
FileManager.addCarvedFile.exception.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002
|
||||
FileManager.addLocalFilesDirs.exception.notReadable.msg=\u8ffd\u52a0\u3059\u308b\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\uff0f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u4e2d\u306b\u8aad\u307f\u53d6\u308c\u306a\u3044\u3082\u306e\u304c\uff11\u500b\u3042\u308a\u307e\u3059\uff1a{0}\u3001\u30d5\u30a1\u30a4\u30eb\u304c\u8ffd\u52a0\u3055\u308c\u308b\u524d\u306b\u51e6\u7406\u3092\u4e2d\u6b62\u3057\u307e\u3059
|
||||
FileManager.addLocalFilesDirs.exception.cantAdd.msg=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\uff0f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\uff11\u500b\u306f\u8ffd\u52a0\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff1a{0}
|
||||
FileManager.addLocalFileSetRootDir.exception.errCreateDir.msg=\u30ed\u30fc\u30ab\u30eb\u30d5\u30a1\u30a4\u30eb\u30bb\u30c3\u30c8\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u3092\u4f5c\u6210\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u8d77\u3053\u308a\u307e\u3057\u305f\uff1a {0}
|
||||
FileManager.addLocalDirInt.exception.closed.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002
|
||||
FileManager.addLocalDirInt.exception.doesntExist.msg=\u5b58\u5728\u3057\u306a\u3044\u30ed\u30fc\u30ab\u30eb\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u8ffd\u52a0\u3092\u8a66\u307f\u307e\u3057\u305f\: {0}
|
||||
FileManager.addLocalDirInt.exception.notReadable.msg=\u8aad\u307f\u53d6\u308a\u3067\u304d\u306a\u3044\u30ed\u30fc\u30ab\u30eb\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u8ffd\u52a0\u3092\u8a66\u307f\u307e\u3057\u305f\: {0}
|
||||
FileManager.addLocalDirInt2.exception.closed.msg=FileManager\u3092\u9589\u3058\u305f\u5f8c\u306b\u4f7f\u7528\u3092\u8a66\u307f\u307e\u3057\u305f\u3002
|
||||
TagsManager.addContentTag.exception.beginByteOffsetOOR.msg=beginByteOffset \= {0} \u30b3\u30f3\u30c6\u30f3\u30c4\u30b5\u30a4\u30ba\u7bc4\u56f2(0 - {1})\u306e\u5916\u3067\u3059
|
||||
TagsManager.addContentTag.exception.endByteOffsetOOR.msg=endByteOffset \= {0} \u30b3\u30f3\u30c6\u30f3\u30c4\u30b5\u30a4\u30ba\u7bc4\u56f2(0 - {1})\u306e\u5916\u3067\u3059
|
||||
TagsManager.addContentTag.exception.endLTbegin.msg=endByteOffset < beginByteOffset
|
||||
|
@ -1,19 +1,19 @@
|
||||
/*
|
||||
*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
*
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
*
|
||||
*
|
||||
* Copyright 2012 42six Solutions.
|
||||
* Contact: aebadirad <at> 42six <dot> com
|
||||
* Project Contact/Architect: 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.
|
||||
@ -25,10 +25,9 @@ package org.sleuthkit.autopsy.casemodule.services;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode;
|
||||
import org.sleuthkit.autopsy.ingest.IngestServices;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
|
||||
@ -36,7 +35,6 @@ import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.DerivedFile;
|
||||
import org.sleuthkit.datamodel.LayoutFile;
|
||||
import org.sleuthkit.datamodel.LocalFile;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
@ -45,58 +43,94 @@ import org.sleuthkit.datamodel.VirtualDirectory;
|
||||
import org.sleuthkit.datamodel.CarvedFileContainer;
|
||||
import org.sleuthkit.datamodel.LocalFilesDataSource;
|
||||
import org.sleuthkit.datamodel.TskDataException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Abstraction to facilitate access to localFiles and directories.
|
||||
* A manager that provides methods for retrieving files from the current case
|
||||
* and for adding local files, carved files, and derived files to the current
|
||||
* case.
|
||||
*/
|
||||
public class FileManager implements Closeable {
|
||||
|
||||
private SleuthkitCase tskCase;
|
||||
private static final Logger logger = Logger.getLogger(FileManager.class.getName());
|
||||
private volatile int curNumFileSets; //current number of filesets (root virt dir objects)
|
||||
|
||||
public FileManager(SleuthkitCase tskCase) {
|
||||
this.tskCase = tskCase;
|
||||
init();
|
||||
}
|
||||
|
||||
private SleuthkitCase caseDb;
|
||||
|
||||
/**
|
||||
* initialize the file manager for the case
|
||||
* Constructs a manager that provides methods for retrieving files from the
|
||||
* current case and for adding local files, carved files, and derived files
|
||||
* to the current case.
|
||||
*
|
||||
* @param caseDb The case database.
|
||||
*/
|
||||
private synchronized void init() {
|
||||
//get the number of local file sets in db
|
||||
List<VirtualDirectory> virtRoots;
|
||||
curNumFileSets = 0;
|
||||
try {
|
||||
virtRoots = tskCase.getVirtualDirectoryRoots();
|
||||
for (VirtualDirectory vd : virtRoots) {
|
||||
if (vd.getName().startsWith(VirtualDirectoryNode.LOGICAL_FILE_SET_PREFIX)) {
|
||||
++curNumFileSets;
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error initializing FileManager and getting number of local file sets"); //NON-NLS
|
||||
}
|
||||
|
||||
public FileManager(SleuthkitCase caseDb) {
|
||||
this.caseDb = caseDb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a set of localFiles that meets the name criteria in all data
|
||||
* sources in the current case.
|
||||
* Finds all files with types that match one of a collection of MIME types.
|
||||
*
|
||||
* @param fileName Pattern of the name of the file or directory to match
|
||||
* (case insensitive, used in LIKE SQL statement).
|
||||
* @param mimeTypes The MIME types.
|
||||
*
|
||||
* @return a list of AbstractFile for localFiles/directories whose name
|
||||
* matches the given fileName
|
||||
* @return The files.
|
||||
*
|
||||
* @throws TskCoreException If there is a problem querying the case
|
||||
* database.
|
||||
*/
|
||||
public synchronized List<AbstractFile> findFilesByMimeType(Collection<String> mimeTypes) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
return caseDb.findAllFilesWhere(createFileTypeInCondition(mimeTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all files in a given data source (image, local/logical files set,
|
||||
* etc.) with types that match one of a collection of MIME types.
|
||||
*
|
||||
* @param dataSource The data source.
|
||||
* @param mimeTypes The MIME types.
|
||||
*
|
||||
* @return The files.
|
||||
*
|
||||
* @throws TskCoreException If there is a problem querying the case
|
||||
* database.
|
||||
*/
|
||||
public synchronized List<AbstractFile> findFilesByMimeType(Content dataSource, Collection<String> mimeTypes) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
return caseDb.findAllFilesWhere("data_source_obj_id = " + dataSource.getId() + " AND " + createFileTypeInCondition(mimeTypes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of MIME types into an SQL "mime_type IN" condition.
|
||||
*
|
||||
* @param mimeTypes The MIIME types.
|
||||
*
|
||||
* @return The condition string.
|
||||
*/
|
||||
private static String createFileTypeInCondition(Collection<String> mimeTypes) {
|
||||
String types = StringUtils.join(mimeTypes, "', '");
|
||||
return "mime_type IN ('" + types + "')";
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all files and directories with a given file name. The name search
|
||||
* is for full or partial matches and is case insensitive (a case
|
||||
* insensitive SQL LIKE clause is used to query the case database).
|
||||
*
|
||||
* @param fileName The full or partial file name.
|
||||
*
|
||||
* @return The matching files and directories.
|
||||
*
|
||||
* @throws TskCoreException if there is a problem querying the case
|
||||
* database.
|
||||
*/
|
||||
public synchronized List<AbstractFile> findFiles(String fileName) throws TskCoreException {
|
||||
List<AbstractFile> result = new ArrayList<>();
|
||||
|
||||
if (tskCase == null) {
|
||||
throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles.exception.msg"));
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
List<Content> dataSources = tskCase.getRootObjects();
|
||||
List<AbstractFile> result = new ArrayList<>();
|
||||
List<Content> dataSources = caseDb.getRootObjects();
|
||||
for (Content dataSource : dataSources) {
|
||||
result.addAll(findFiles(dataSource, fileName));
|
||||
}
|
||||
@ -104,113 +138,122 @@ public class FileManager implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a set of localFiles that meets the name criteria in all data
|
||||
* sources in the current case.
|
||||
* Finds all files and directories with a given file name and parent file or
|
||||
* directory name. The name searches are for full or partial matches and are
|
||||
* case insensitive (a case insensitive SQL LIKE clause is used to query the
|
||||
* case database).
|
||||
*
|
||||
* @param fileName Pattern of the name of the file or directory to match
|
||||
* (case insensitive, used in LIKE SQL statement).
|
||||
* @param dirName Pattern of the name of the parent directory to use as the
|
||||
* root of the search (case insensitive, used in LIKE SQL
|
||||
* statement).
|
||||
* @param fileName The full or partial file name.
|
||||
* @param parentName The full or partial parent file or directory name.
|
||||
*
|
||||
* @return a list of AbstractFile for localFiles/directories whose name
|
||||
* matches fileName and whose parent directory contains dirName.
|
||||
* @return The matching files and directories.
|
||||
*
|
||||
* @throws TskCoreException if there is a problem querying the case
|
||||
* database.
|
||||
*/
|
||||
public synchronized List<AbstractFile> findFiles(String fileName, String dirName) throws TskCoreException {
|
||||
List<AbstractFile> result = new ArrayList<>();
|
||||
|
||||
if (tskCase == null) {
|
||||
throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles2.exception.msg"));
|
||||
public synchronized List<AbstractFile> findFiles(String fileName, String parentName) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
List<Content> dataSources = tskCase.getRootObjects();
|
||||
List<AbstractFile> result = new ArrayList<>();
|
||||
List<Content> dataSources = caseDb.getRootObjects();
|
||||
for (Content dataSource : dataSources) {
|
||||
result.addAll(findFiles(dataSource, fileName, dirName));
|
||||
result.addAll(findFiles(dataSource, fileName, parentName));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a set of localFiles that meets the name criteria in all data
|
||||
* sources in the current case.
|
||||
* Finds all files and directories with a given file name and parent file or
|
||||
* directory. The name search is for full or partial matches and is case
|
||||
* insensitive (a case insensitive SQL LIKE clause is used to query the case
|
||||
* database).
|
||||
*
|
||||
* @param fileName Pattern of the name of the file or directory to match
|
||||
* (case insensitive, used in LIKE SQL statement).
|
||||
* @param parentFile Object of root/parent directory to restrict search to.
|
||||
* @param fileName The full or partial file name.
|
||||
* @param parent The parent file or directory.
|
||||
*
|
||||
* @return a list of AbstractFile for localFiles/directories whose name
|
||||
* matches fileName and that were inside a directory described by
|
||||
* parentFsContent.
|
||||
* @return The matching files and directories.
|
||||
*
|
||||
* @throws TskCoreException if there is a problem querying the case
|
||||
* database.
|
||||
*/
|
||||
public synchronized List<AbstractFile> findFiles(String fileName, AbstractFile parentFile) throws TskCoreException {
|
||||
List<AbstractFile> result = new ArrayList<>();
|
||||
|
||||
if (tskCase == null) {
|
||||
throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles3.exception.msg"));
|
||||
public synchronized List<AbstractFile> findFiles(String fileName, AbstractFile parent) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
List<Content> dataSources = tskCase.getRootObjects();
|
||||
List<AbstractFile> result = new ArrayList<>();
|
||||
List<Content> dataSources = caseDb.getRootObjects();
|
||||
for (Content dataSource : dataSources) {
|
||||
result.addAll(findFiles(dataSource, fileName, parentFile));
|
||||
result.addAll(findFiles(dataSource, fileName, parent));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a set of localFiles that meets the name criteria.
|
||||
* Finds all files and directories with a given file name in a given data
|
||||
* source (image, local/logical files set, etc.). The name search is for
|
||||
* full or partial matches and is case insensitive (a case insensitive SQL
|
||||
* LIKE clause is used to query the case database).
|
||||
*
|
||||
* @param dataSource Root data source to limit search results to (Image,
|
||||
* VirtualDirectory, etc.).
|
||||
* @param fileName Pattern of the name of the file or directory to match
|
||||
* (case insensitive, used in LIKE SQL statement).
|
||||
* @param dataSource The data source.
|
||||
* @param fileName The full or partial file name.
|
||||
*
|
||||
* @return a list of AbstractFile for localFiles/directories whose name
|
||||
* matches the given fileName
|
||||
* @return The matching files and directories.
|
||||
*
|
||||
* @throws TskCoreException if there is a problem querying the case
|
||||
* database.
|
||||
*/
|
||||
public synchronized List<AbstractFile> findFiles(Content dataSource, String fileName) throws TskCoreException {
|
||||
if (tskCase == null) {
|
||||
throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles.exception.msg"));
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
return tskCase.findFiles(dataSource, fileName);
|
||||
return caseDb.findFiles(dataSource, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a set of localFiles that meets the name criteria.
|
||||
* Finds all files and directories with a given file name and parent file or
|
||||
* directory name in a given data source (image, local/logical files set,
|
||||
* etc.). The name searches are for full or partial matches and are case
|
||||
* insensitive (a case insensitive SQL LIKE clause is used to query the case
|
||||
* database).
|
||||
*
|
||||
* @param dataSource Root data source to limit search results to (Image,
|
||||
* VirtualDirectory, etc.).
|
||||
* @param fileName Pattern of the name of the file or directory to match
|
||||
* (case insensitive, used in LIKE SQL statement).
|
||||
* @param dirName Pattern of the name of the parent directory to use as
|
||||
* the root of the search (case insensitive, used in LIKE
|
||||
* SQL statement).
|
||||
* @param dataSource The data source.
|
||||
* @param fileName The full or partial file name.
|
||||
* @param parentName The full or partial parent file or directory name.
|
||||
*
|
||||
* @return a list of AbstractFile for localFiles/directories whose name
|
||||
* matches fileName and whose parent directory contains dirName.
|
||||
* @return The matching files and directories.
|
||||
*
|
||||
* @throws TskCoreException if there is a problem querying the case
|
||||
* database.
|
||||
*/
|
||||
public synchronized List<AbstractFile> findFiles(Content dataSource, String fileName, String dirName) throws TskCoreException {
|
||||
if (tskCase == null) {
|
||||
throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles2.exception.msg"));
|
||||
public synchronized List<AbstractFile> findFiles(Content dataSource, String fileName, String parentName) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
return tskCase.findFiles(dataSource, fileName, dirName);
|
||||
return caseDb.findFiles(dataSource, fileName, parentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a set of localFiles that meets the name criteria.
|
||||
* Finds all files and directories with a given file name and given parent
|
||||
* file or directory in a given data source (image, local/logical files set,
|
||||
* etc.). The name search is for full or partial matches and is case
|
||||
* insensitive (a case insensitive SQL LIKE clause is used to query the case
|
||||
* database).
|
||||
*
|
||||
* @param dataSource Root data source to limit search results to (Image,
|
||||
* VirtualDirectory, etc.).
|
||||
* @param fileName Pattern of the name of the file or directory to match
|
||||
* (case insensitive, used in LIKE SQL statement).
|
||||
* @param parentFile Object of root/parent directory to restrict search to.
|
||||
* @param dataSource The data source.
|
||||
* @param fileName The full or partial file name.
|
||||
* @param parent The parent file or directory.
|
||||
*
|
||||
* @return a list of AbstractFile for localFiles/directories whose name
|
||||
* matches fileName and that were inside a directory described by
|
||||
* parentFsContent.
|
||||
* @return The matching files and directories.
|
||||
*
|
||||
* @throws TskCoreException if there is a problem querying the case
|
||||
* database.
|
||||
*/
|
||||
public synchronized List<AbstractFile> findFiles(Content dataSource, String fileName, AbstractFile parentFile) throws TskCoreException {
|
||||
if (tskCase == null) {
|
||||
throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles3.exception.msg"));
|
||||
public synchronized List<AbstractFile> findFiles(Content dataSource, String fileName, AbstractFile parent) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
return findFiles(dataSource, fileName, parentFile.getName());
|
||||
return findFiles(dataSource, fileName, parent.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -221,173 +264,127 @@ public class FileManager implements Closeable {
|
||||
*
|
||||
* @return a list of AbstractFile that have the given file path.
|
||||
*/
|
||||
/**
|
||||
* Finds all files and directories with a given file name and path in a
|
||||
* given data source (image, local/logical files set, etc.). The name search
|
||||
* is for full or partial matches and is case insensitive (a case
|
||||
* insensitive SQL LIKE clause is used to query the case database). Any path
|
||||
* components at the volume level and above are removed for the search.
|
||||
*
|
||||
* @param dataSource The data source.
|
||||
* @param fileName The full or partial file name.
|
||||
* @param filePath The file path (path components volume at the volume
|
||||
* level or above will be removed).
|
||||
*
|
||||
* @return The matching files and directories.
|
||||
*
|
||||
* @throws TskCoreException if there is a problem querying the case
|
||||
* database.
|
||||
*/
|
||||
public synchronized List<AbstractFile> openFiles(Content dataSource, String filePath) throws TskCoreException {
|
||||
if (tskCase == null) {
|
||||
throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.openFiles.exception.msg"));
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
return tskCase.openFiles(dataSource, filePath);
|
||||
return caseDb.openFiles(dataSource, filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a derived file, adds it to the database and returns it.
|
||||
* Adds a derived file to the case.
|
||||
*
|
||||
* @param fileName file name the derived file
|
||||
* @param localPath local path of the derived file, including the file
|
||||
* name. The path is relative to the case folder.
|
||||
* @param size size of the derived file in bytes
|
||||
* @param ctime
|
||||
* @param crtime
|
||||
* @param atime
|
||||
* @param mtime
|
||||
* @param isFile whether a file or directory, true if a file
|
||||
* @param parentFile the parent file object this the new file was
|
||||
* derived from, either a fs file or parent derived
|
||||
* file/dikr\\r
|
||||
* @param rederiveDetails details needed to re-derive file (will be specific
|
||||
* to the derivation method), currently unused
|
||||
* @param toolName name of derivation method/tool, currently unused
|
||||
* @param toolVersion version of derivation method/tool, currently
|
||||
* unused
|
||||
* @param otherDetails details of derivation method/tool, currently
|
||||
* unused
|
||||
* @param fileName The name of the file.
|
||||
* @param localPath The local path of the file, relative to the case
|
||||
* folder and including the file name.
|
||||
* @param size The size of the file in bytes.
|
||||
* @param ctime The change time of the file.
|
||||
* @param crtime The create time of the file
|
||||
* @param atime The accessed time of the file.
|
||||
* @param mtime The modified time of the file.
|
||||
* @param isFile True if a file, false if a directory.
|
||||
* @param parentFile The parent file from which the file was derived.
|
||||
* @param rederiveDetails The details needed to re-derive file (will be
|
||||
* specific to the derivation method), currently
|
||||
* unused.
|
||||
* @param toolName The name of the derivation method or tool,
|
||||
* currently unused.
|
||||
* @param toolVersion The version of the derivation method or tool,
|
||||
* currently unused.
|
||||
* @param otherDetails Other details of the derivation method or tool,
|
||||
* currently unused.
|
||||
*
|
||||
* @return newly created derived file object added to the database
|
||||
*
|
||||
* @throws TskCoreException exception thrown if the object creation failed
|
||||
* due to a critical system error or of the file
|
||||
* manager has already been closed
|
||||
* @return A DerivedFile object representing the derived file.
|
||||
*
|
||||
* @throws TskCoreException if there is a problem adding the file to the
|
||||
* case database.
|
||||
*/
|
||||
public synchronized DerivedFile addDerivedFile(String fileName, String localPath, long size,
|
||||
public synchronized DerivedFile addDerivedFile(String fileName,
|
||||
String localPath,
|
||||
long size,
|
||||
long ctime, long crtime, long atime, long mtime,
|
||||
boolean isFile, AbstractFile parentFile,
|
||||
boolean isFile,
|
||||
AbstractFile parentFile,
|
||||
String rederiveDetails, String toolName, String toolVersion, String otherDetails) throws TskCoreException {
|
||||
|
||||
if (tskCase == null) {
|
||||
throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.addDerivedFile.exception.msg"));
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
|
||||
return tskCase.addDerivedFile(fileName, localPath, size,
|
||||
return caseDb.addDerivedFile(fileName, localPath, size,
|
||||
ctime, crtime, atime, mtime,
|
||||
isFile, parentFile, rederiveDetails, toolName, toolVersion, otherDetails);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a carved file to the VirtualDirectory '$CarvedFiles' in the volume
|
||||
* or image given by systemId.
|
||||
* Adds a carved file to the '$CarvedFiles' virtual directory of a data
|
||||
* source, volume or file system.
|
||||
*
|
||||
* @param carvedFileName the name of the carved file (containing appropriate
|
||||
* extension)
|
||||
* @param carvedFileSize size of the carved file to add
|
||||
* @param systemId the ID of the parent volume or file system
|
||||
* @param sectors a list of SectorGroups giving this sectors that
|
||||
* make up this carved file.
|
||||
* @param fileName The name of the file.
|
||||
* @param fileSize The size of the file.
|
||||
* @param parentObjId The object id of the parent data source, volume or
|
||||
* file system.
|
||||
* @param layout A list of the offsets and sizes that gives the layout
|
||||
* of the file within its parent.
|
||||
*
|
||||
* @throws TskCoreException exception thrown when critical tsk error
|
||||
* occurred and carved file could not be added
|
||||
* @return A LayoutFile object representing the carved file.
|
||||
*
|
||||
* @throws TskCoreException if there is a problem adding the file to the
|
||||
* case database.
|
||||
*/
|
||||
public synchronized LayoutFile addCarvedFile(String carvedFileName, long carvedFileSize,
|
||||
long systemId, List<TskFileRange> sectors) throws TskCoreException {
|
||||
|
||||
if (tskCase == null) {
|
||||
throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.addCarvedFile.exception.msg"));
|
||||
public synchronized LayoutFile addCarvedFile(String fileName, long fileSize, long parentObjId, List<TskFileRange> layout) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
|
||||
return tskCase.addCarvedFile(carvedFileName, carvedFileSize, systemId, sectors);
|
||||
return caseDb.addCarvedFile(fileName, fileSize, parentObjId, layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a collection of carved localFiles to the VirtualDirectory
|
||||
* '$CarvedFiles' in the volume or image given by systemId. Creates
|
||||
* $CarvedFiles if it does not exist already.
|
||||
* Adds a collection of carved files to the '$CarvedFiles' virtual directory
|
||||
* of a data source, volume or file system.
|
||||
*
|
||||
* @param filesToAdd a list of CarvedFileContainer localFiles to add as
|
||||
* carved localFiles
|
||||
* @param A collection of CarvedFileContainer objects, one per carved file,
|
||||
* all of which must have the same parent object id.
|
||||
*
|
||||
* @return List<LayoutFile> This is a list of the localFiles added to the
|
||||
* database
|
||||
* @return A collection of LayoutFile object representing the carved files.
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
* @throws TskCoreException if there is a problem adding the files to the
|
||||
* case database.
|
||||
*/
|
||||
public List<LayoutFile> addCarvedFiles(List<CarvedFileContainer> filesToAdd) throws TskCoreException {
|
||||
if (tskCase == null) {
|
||||
throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.addCarvedFile.exception.msg"));
|
||||
} else {
|
||||
return tskCase.addCarvedFiles(filesToAdd);
|
||||
public synchronized List<LayoutFile> addCarvedFiles(List<CarvedFileContainer> filesToAdd) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
return caseDb.addCarvedFiles(filesToAdd);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Interface for receiving notifications on folders being added via a
|
||||
* callback
|
||||
* Interface for receiving a notification for each file or directory added
|
||||
* to the case database by a FileManager add files operation.
|
||||
*/
|
||||
public interface FileAddProgressUpdater {
|
||||
|
||||
/**
|
||||
* Called when new folders has been added
|
||||
* Called after a file or directory is added to the case database.
|
||||
*
|
||||
* @param newFile the file/folder added to the Case
|
||||
* @param An AbstractFile represeting the added file or directory.
|
||||
*/
|
||||
public void fileAdded(AbstractFile newFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a set of local/logical localFiles and dirs.
|
||||
*
|
||||
* @param localAbsPaths list of absolute paths to local localFiles and
|
||||
* dirs
|
||||
* @param addProgressUpdater notifier to receive progress notifications on
|
||||
* folders added, or null if not used
|
||||
*
|
||||
* @return file set root VirtualDirectory contained containing all
|
||||
* AbstractFile objects added
|
||||
*
|
||||
* @throws TskCoreException exception thrown if the object creation failed
|
||||
* due to a critical system error or of the file
|
||||
* manager has already been closed. There is no
|
||||
* "revert" logic if one of the additions fails.
|
||||
* The addition stops with the first error
|
||||
* encountered.
|
||||
*/
|
||||
public synchronized VirtualDirectory addLocalFilesDirs(List<String> localAbsPaths, FileAddProgressUpdater addProgressUpdater) throws TskCoreException {
|
||||
List<java.io.File> rootsToAdd;
|
||||
try {
|
||||
rootsToAdd = getFilesAndDirectories(localAbsPaths);
|
||||
} catch (TskDataException ex) {
|
||||
throw new TskCoreException(ex.getLocalizedMessage(), ex);
|
||||
}
|
||||
|
||||
CaseDbTransaction trans = tskCase.beginTransaction();
|
||||
// make a virtual top-level directory for this set of localFiles/dirs
|
||||
final VirtualDirectory fileSetRootDir = addLocalFileSetRootDir(trans);
|
||||
|
||||
try {
|
||||
// recursively add each item in the set
|
||||
for (java.io.File localRootToAdd : rootsToAdd) {
|
||||
AbstractFile localFileAdded = addLocalDirInt(trans, fileSetRootDir, localRootToAdd, addProgressUpdater);
|
||||
|
||||
if (localFileAdded == null) {
|
||||
String msg = NbBundle
|
||||
.getMessage(this.getClass(), "FileManager.addLocalFilesDirs.exception.cantAdd.msg",
|
||||
localRootToAdd.getAbsolutePath());
|
||||
logger.log(Level.SEVERE, msg);
|
||||
throw new TskCoreException(msg);
|
||||
} else {
|
||||
//added.add(localFileAdded);
|
||||
//send new content event
|
||||
//for now reusing ingest events, in future this will be replaced by datamodel / observer sending out events
|
||||
// @@@ Is this the right place for this? A directory tree refresh will be triggered, so this may be creating a race condition
|
||||
// since the transaction is not yet committed.
|
||||
IngestServices.getInstance().fireModuleContentEvent(new ModuleContentEvent(localFileAdded));
|
||||
}
|
||||
}
|
||||
|
||||
trans.commit();
|
||||
} catch (TskCoreException ex) {
|
||||
trans.rollback();
|
||||
}
|
||||
return fileSetRootDir;
|
||||
void fileAdded(AbstractFile newFile);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -419,20 +416,27 @@ public class FileManager implements Closeable {
|
||||
* directory that does not exist or cannot be read.
|
||||
*/
|
||||
public synchronized LocalFilesDataSource addLocalFilesDataSource(String deviceId, String rootVirtualDirectoryName, String timeZone, List<String> localFilePaths, FileAddProgressUpdater progressUpdater) throws TskCoreException, TskDataException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
List<java.io.File> localFiles = getFilesAndDirectories(localFilePaths);
|
||||
CaseDbTransaction trans = null;
|
||||
try {
|
||||
String rootDirectoryName = rootVirtualDirectoryName;
|
||||
int newLocalFilesSetCount = curNumFileSets + 1;
|
||||
if (rootVirtualDirectoryName.isEmpty()) {
|
||||
rootDirectoryName = VirtualDirectoryNode.LOGICAL_FILE_SET_PREFIX + newLocalFilesSetCount;
|
||||
if (rootDirectoryName.isEmpty()) {
|
||||
rootDirectoryName = generateFilesDataSourceName(caseDb);
|
||||
}
|
||||
trans = tskCase.beginTransaction();
|
||||
LocalFilesDataSource dataSource = tskCase.addLocalFilesDataSource(deviceId, rootDirectoryName, timeZone, trans);
|
||||
|
||||
/*
|
||||
* Add the root virtual directory and its local/logical file
|
||||
* children to the case database.
|
||||
*/
|
||||
trans = caseDb.beginTransaction();
|
||||
LocalFilesDataSource dataSource = caseDb.addLocalFilesDataSource(deviceId, rootDirectoryName, timeZone, trans);
|
||||
VirtualDirectory rootDirectory = dataSource.getRootDirectory();
|
||||
List<AbstractFile> filesAdded = new ArrayList<>();
|
||||
for (java.io.File localFile : localFiles) {
|
||||
AbstractFile fileAdded = addLocalDirInt(trans, rootDirectory, localFile, progressUpdater);
|
||||
AbstractFile fileAdded = addLocalFile(trans, rootDirectory, localFile, progressUpdater);
|
||||
if (null != fileAdded) {
|
||||
filesAdded.add(fileAdded);
|
||||
} else {
|
||||
@ -440,13 +444,16 @@ public class FileManager implements Closeable {
|
||||
}
|
||||
}
|
||||
trans.commit();
|
||||
if (rootVirtualDirectoryName.isEmpty()) {
|
||||
curNumFileSets = newLocalFilesSetCount;
|
||||
}
|
||||
|
||||
/*
|
||||
* Publish content added events for the added files and directories.
|
||||
*/
|
||||
for (AbstractFile fileAdded : filesAdded) {
|
||||
IngestServices.getInstance().fireModuleContentEvent(new ModuleContentEvent(fileAdded));
|
||||
}
|
||||
|
||||
return dataSource;
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
if (null != trans) {
|
||||
trans.rollback();
|
||||
@ -455,6 +462,34 @@ public class FileManager implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a name for the root virtual directory for the data source.
|
||||
*
|
||||
* NOTE: Although this method is guarded by the file manager's monitor,
|
||||
* there is currently a minimal chance of default name duplication for
|
||||
* multi-user cases with multiple FileManagers running on different nodes.
|
||||
*
|
||||
* @return A default name for a local/logical files data source of the form:
|
||||
* LogicalFileSet[N].
|
||||
*
|
||||
* @throws TskCoreException If there is a problem querying the case
|
||||
* database.
|
||||
*/
|
||||
private static synchronized String generateFilesDataSourceName(SleuthkitCase caseDb) throws TskCoreException {
|
||||
int localFileDataSourcesCounter = 0;
|
||||
try {
|
||||
List<VirtualDirectory> localFileDataSources = caseDb.getVirtualDirectoryRoots();
|
||||
for (VirtualDirectory vd : localFileDataSources) {
|
||||
if (vd.getName().startsWith(VirtualDirectoryNode.LOGICAL_FILE_SET_PREFIX)) {
|
||||
++localFileDataSourcesCounter;
|
||||
}
|
||||
}
|
||||
return VirtualDirectoryNode.LOGICAL_FILE_SET_PREFIX + (localFileDataSourcesCounter + 1);
|
||||
} catch (TskCoreException ex) {
|
||||
throw new TskCoreException("Error querying for existing local file data sources with defualt names", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a list of local/logical file and/or directory paths to a list of
|
||||
* file objects.
|
||||
@ -472,7 +507,7 @@ public class FileManager implements Closeable {
|
||||
for (String path : localFilePaths) {
|
||||
java.io.File localFile = new java.io.File(path);
|
||||
if (!localFile.exists() || !localFile.canRead()) {
|
||||
throw new TskDataException(NbBundle.getMessage(this.getClass(), "FileManager.addLocalFilesDirs.exception.notReadable.msg", localFile.getAbsolutePath()));
|
||||
throw new TskDataException(String.format("File at %s does not exist or cannot be read", localFile.getAbsolutePath()));
|
||||
}
|
||||
localFiles.add(localFile);
|
||||
}
|
||||
@ -480,130 +515,89 @@ public class FileManager implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new virtual directory root object with FileSet X name and
|
||||
* consecutive sequence number characteristic to every add operation
|
||||
*
|
||||
* @return the virtual dir root container created
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
private VirtualDirectory addLocalFileSetRootDir(CaseDbTransaction trans) throws TskCoreException {
|
||||
|
||||
VirtualDirectory created = null;
|
||||
|
||||
int newFileSetCount = curNumFileSets + 1;
|
||||
final String fileSetName = VirtualDirectoryNode.LOGICAL_FILE_SET_PREFIX + newFileSetCount;
|
||||
|
||||
try {
|
||||
created = tskCase.addVirtualDirectory(0, fileSetName, trans);
|
||||
curNumFileSets = newFileSetCount;
|
||||
} catch (TskCoreException ex) {
|
||||
String msg = NbBundle
|
||||
.getMessage(this.getClass(), "FileManager.addLocalFileSetRootDir.exception.errCreateDir.msg",
|
||||
fileSetName);
|
||||
logger.log(Level.SEVERE, msg, ex);
|
||||
throw new TskCoreException(msg, ex);
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper (internal) method to recursively add contents of a folder. Node
|
||||
* passed in can be a file or directory. Children of directories are added.
|
||||
* Adds a file or directory of logical/local files data source to the case
|
||||
* database, recursively adding the contents of directories.
|
||||
*
|
||||
* @param trans A case database transaction.
|
||||
* @param parentVd Dir that is the parent of localFile
|
||||
* @param localFile File/Dir that we are adding
|
||||
* @param parentDirectory The root virtual direcotry of the data source.
|
||||
* @param localFile The local/logical file or directory.
|
||||
* @param addProgressUpdater notifier to receive progress notifications on
|
||||
* folders added, or null if not used
|
||||
*
|
||||
* @returns File object of file added or new virtualdirectory for the
|
||||
* directory.
|
||||
* @throws TskCoreException
|
||||
* @param progressUpdater Called after each file/directory is added to
|
||||
* the case database.
|
||||
*
|
||||
* @return An AbstractFile representation of the local/logical file.
|
||||
*
|
||||
* @throws TskCoreException If there is a problem completing a database
|
||||
* operation.
|
||||
*/
|
||||
private AbstractFile addLocalDirInt(CaseDbTransaction trans, VirtualDirectory parentVd,
|
||||
java.io.File localFile, FileAddProgressUpdater addProgressUpdater) throws TskCoreException {
|
||||
|
||||
if (tskCase == null) {
|
||||
throw new TskCoreException(
|
||||
NbBundle.getMessage(this.getClass(), "FileManager.addLocalDirInt.exception.closed.msg"));
|
||||
}
|
||||
|
||||
//final String localName = localDir.getName();
|
||||
if (!localFile.exists()) {
|
||||
throw new TskCoreException(
|
||||
NbBundle.getMessage(this.getClass(), "FileManager.addLocalDirInt.exception.doesntExist.msg",
|
||||
localFile.getAbsolutePath()));
|
||||
}
|
||||
if (!localFile.canRead()) {
|
||||
throw new TskCoreException(
|
||||
NbBundle.getMessage(this.getClass(), "FileManager.addLocalDirInt.exception.notReadable.msg",
|
||||
localFile.getAbsolutePath()));
|
||||
}
|
||||
|
||||
private AbstractFile addLocalFile(CaseDbTransaction trans, VirtualDirectory parentDirectory, java.io.File localFile, FileAddProgressUpdater progressUpdater) throws TskCoreException {
|
||||
if (localFile.isDirectory()) {
|
||||
//create virtual folder (we don't have a notion of a 'local folder')
|
||||
final VirtualDirectory childVd = tskCase.addVirtualDirectory(parentVd.getId(), localFile.getName(), trans);
|
||||
if (childVd != null && addProgressUpdater != null) {
|
||||
addProgressUpdater.fileAdded(childVd);
|
||||
}
|
||||
//add children recursively
|
||||
final java.io.File[] childrenFiles = localFile.listFiles();
|
||||
if (childrenFiles != null) {
|
||||
for (java.io.File childFile : childrenFiles) {
|
||||
addLocalDirInt(trans, childVd, childFile, addProgressUpdater);
|
||||
/*
|
||||
* Add the directory as a virtual directory.
|
||||
*/
|
||||
VirtualDirectory virtualDirectory = caseDb.addVirtualDirectory(parentDirectory.getId(), localFile.getName(), trans);
|
||||
progressUpdater.fileAdded(virtualDirectory);
|
||||
|
||||
/*
|
||||
* Add its children, if any.
|
||||
*/
|
||||
final java.io.File[] childFiles = localFile.listFiles();
|
||||
if (childFiles != null && childFiles.length > 0) {
|
||||
for (java.io.File childFile : childFiles) {
|
||||
addLocalFile(trans, virtualDirectory, childFile, progressUpdater);
|
||||
}
|
||||
}
|
||||
return childVd;
|
||||
|
||||
return virtualDirectory;
|
||||
} else {
|
||||
//add leaf file, base case
|
||||
return this.addLocalFileInt(parentVd, localFile, trans);
|
||||
return caseDb.addLocalFile(localFile.getName(), localFile.getAbsolutePath(), localFile.length(),
|
||||
0, 0, 0, 0,
|
||||
localFile.isFile(), parentDirectory, trans);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a single local/logical file to the case. Adds it to the database.
|
||||
* Does not refresh the views of data. Assumes that the local file exists
|
||||
* and can be read. This checking is done by addLocalDirInt().
|
||||
* Adds a set of local/logical files and/or directories to the case database
|
||||
* as data source.
|
||||
*
|
||||
* @param parentFile parent file object container (such as virtual
|
||||
* directory, another local file, or fscontent File),
|
||||
* @param localFile File that we are adding
|
||||
* @param trans A case database transaction.
|
||||
* @param localFilePaths A list of local/logical file and/or directory
|
||||
* localFilePaths.
|
||||
* @param progressUpdater Called after each file/directory is added to the
|
||||
* case database.
|
||||
*
|
||||
* @return newly created local file object added to the database
|
||||
* @return The root virtual directory for the local/logical files data
|
||||
* source.
|
||||
*
|
||||
* @throws TskCoreException exception thrown if the object creation failed
|
||||
* due to a critical system error or of the file
|
||||
* manager has already been closed
|
||||
* @throws TskCoreException If any of the local file paths is for a file or
|
||||
* directory that does not exist or cannot be read,
|
||||
* or there is a problem completing a database
|
||||
* operation.
|
||||
* @deprecated Use addLocalFilesDataSource instead.
|
||||
*/
|
||||
private synchronized LocalFile addLocalFileInt(AbstractFile parentFile, java.io.File localFile, CaseDbTransaction trans) throws TskCoreException {
|
||||
|
||||
if (tskCase == null) {
|
||||
throw new TskCoreException(
|
||||
NbBundle.getMessage(this.getClass(), "FileManager.addLocalDirInt2.exception.closed.msg"));
|
||||
@Deprecated
|
||||
public synchronized VirtualDirectory addLocalFilesDirs(List<String> localFilePaths, FileAddProgressUpdater progressUpdater) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("File manager has been closed");
|
||||
}
|
||||
try {
|
||||
return addLocalFilesDataSource("", "", "", localFilePaths, progressUpdater).getRootDirectory();
|
||||
} catch (TskDataException ex) {
|
||||
throw new TskCoreException(ex.getLocalizedMessage(), ex);
|
||||
}
|
||||
|
||||
long size = localFile.length();
|
||||
boolean isFile = localFile.isFile();
|
||||
|
||||
long ctime = 0;
|
||||
long crtime = 0;
|
||||
long atime = 0;
|
||||
long mtime = 0;
|
||||
|
||||
String fileName = localFile.getName();
|
||||
|
||||
LocalFile lf = tskCase.addLocalFile(fileName, localFile.getAbsolutePath(), size,
|
||||
ctime, crtime, atime, mtime,
|
||||
isFile, parentFile, trans);
|
||||
|
||||
return lf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the file manager.
|
||||
*
|
||||
* @throws IOException If there is a problem closing the file manager.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
tskCase = null;
|
||||
caseDb = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,19 +1,18 @@
|
||||
/*
|
||||
*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2012-2015 Basis Technology Corp.
|
||||
*
|
||||
*
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
* Copyright 2012 42six Solutions.
|
||||
* Contact: aebadirad <at> 42six <dot> com
|
||||
* Project Contact/Architect: 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.
|
||||
@ -31,7 +30,8 @@ import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
|
||||
/**
|
||||
* A class to manage various services.
|
||||
* A collection of case-level services (e.g., file manager, tags manager,
|
||||
* keyword search, blackboard).
|
||||
*/
|
||||
public class Services implements Closeable {
|
||||
|
||||
@ -41,36 +41,67 @@ public class Services implements Closeable {
|
||||
private final KeywordSearchService keywordSearchService;
|
||||
private final Blackboard blackboard;
|
||||
|
||||
public Services(SleuthkitCase tskCase) {
|
||||
fileManager = new FileManager(tskCase);
|
||||
/**
|
||||
* Constructs a collection of case-level services (e.g., file manager, tags
|
||||
* manager, keyword search, blackboard).
|
||||
*
|
||||
* @param caseDb The case database for the current case.
|
||||
*/
|
||||
public Services(SleuthkitCase caseDb) {
|
||||
fileManager = new FileManager(caseDb);
|
||||
services.add(fileManager);
|
||||
|
||||
tagsManager = new TagsManager(tskCase);
|
||||
tagsManager = new TagsManager(caseDb);
|
||||
services.add(tagsManager);
|
||||
|
||||
keywordSearchService = Lookup.getDefault().lookup(KeywordSearchService.class);
|
||||
services.add(keywordSearchService);
|
||||
|
||||
blackboard = new Blackboard();
|
||||
|
||||
blackboard = new Blackboard(caseDb);
|
||||
services.add(blackboard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file manager service for the current case.
|
||||
*
|
||||
* @return The file manager service for the current case.
|
||||
*/
|
||||
public FileManager getFileManager() {
|
||||
return fileManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tags manager service for the current case.
|
||||
*
|
||||
* @return The tags manager service for the current case.
|
||||
*/
|
||||
public TagsManager getTagsManager() {
|
||||
return tagsManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the keyword search service for the current case.
|
||||
*
|
||||
* @return The keyword search service for the current case.
|
||||
*/
|
||||
public KeywordSearchService getKeywordSearchService() {
|
||||
return keywordSearchService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the blackboard service for the current case.
|
||||
*
|
||||
* @return The blackboard service for the current case.
|
||||
*/
|
||||
public Blackboard getBlackboard() {
|
||||
return blackboard;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the services for the current case.
|
||||
*
|
||||
* @throws IOException if there is a problem closing the services.
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
for (Closeable service : services) {
|
||||
|
@ -45,7 +45,7 @@ public class TagsManager implements Closeable {
|
||||
private static final Logger logger = Logger.getLogger(TagsManager.class.getName());
|
||||
private static final String TAGS_SETTINGS_NAME = "Tags"; //NON-NLS
|
||||
private static final String TAG_NAMES_SETTING_KEY = "TagNames"; //NON-NLS
|
||||
private final SleuthkitCase caseDb;
|
||||
private SleuthkitCase caseDb;
|
||||
private final HashMap<String, TagName> uniqueTagNames = new HashMap<>();
|
||||
private boolean tagNamesLoaded = false;
|
||||
|
||||
@ -53,8 +53,8 @@ public class TagsManager implements Closeable {
|
||||
* Constructs a per case Autopsy service that manages the creation,
|
||||
* updating, and deletion of tags applied to content and blackboard
|
||||
* artifacts by users.
|
||||
*
|
||||
* @param caseDb The case database for the current case.
|
||||
*
|
||||
* @param caseDb The case database.
|
||||
*/
|
||||
TagsManager(SleuthkitCase caseDb) {
|
||||
this.caseDb = caseDb;
|
||||
@ -70,6 +70,9 @@ public class TagsManager implements Closeable {
|
||||
* database.
|
||||
*/
|
||||
public synchronized List<TagName> getAllTagNames() throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
return caseDb.getAllTagNames();
|
||||
}
|
||||
@ -84,6 +87,9 @@ public class TagsManager implements Closeable {
|
||||
* database.
|
||||
*/
|
||||
public synchronized List<TagName> getTagNamesInUse() throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
return caseDb.getTagNamesInUse();
|
||||
}
|
||||
@ -114,6 +120,9 @@ public class TagsManager implements Closeable {
|
||||
* to the case database.
|
||||
*/
|
||||
public TagName addTagName(String displayName) throws TagNameAlreadyExistsException, TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
return addTagName(displayName, "", TagName.HTML_COLOR.NONE);
|
||||
}
|
||||
|
||||
@ -132,6 +141,9 @@ public class TagsManager implements Closeable {
|
||||
* to the case database.
|
||||
*/
|
||||
public TagName addTagName(String displayName, String description) throws TagNameAlreadyExistsException, TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
return addTagName(displayName, description, TagName.HTML_COLOR.NONE);
|
||||
}
|
||||
|
||||
@ -151,6 +163,9 @@ public class TagsManager implements Closeable {
|
||||
* to the case database.
|
||||
*/
|
||||
public synchronized TagName addTagName(String displayName, String description, TagName.HTML_COLOR color) throws TagNameAlreadyExistsException, TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
if (uniqueTagNames.containsKey(displayName)) {
|
||||
throw new TagNameAlreadyExistsException();
|
||||
@ -182,6 +197,9 @@ public class TagsManager implements Closeable {
|
||||
* database.
|
||||
*/
|
||||
public ContentTag addContentTag(Content content, TagName tagName) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
return addContentTag(content, tagName, "", -1, -1);
|
||||
}
|
||||
|
||||
@ -198,6 +216,9 @@ public class TagsManager implements Closeable {
|
||||
* database.
|
||||
*/
|
||||
public ContentTag addContentTag(Content content, TagName tagName, String comment) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
return addContentTag(content, tagName, comment, -1, -1);
|
||||
}
|
||||
|
||||
@ -218,6 +239,9 @@ public class TagsManager implements Closeable {
|
||||
* the case database.
|
||||
*/
|
||||
public ContentTag addContentTag(Content content, TagName tagName, String comment, long beginByteOffset, long endByteOffset) throws IllegalArgumentException, TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
ContentTag tag;
|
||||
synchronized (this) {
|
||||
lazyLoadExistingTagNames();
|
||||
@ -265,6 +289,9 @@ public class TagsManager implements Closeable {
|
||||
* case database.
|
||||
*/
|
||||
public void deleteContentTag(ContentTag tag) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
synchronized (this) {
|
||||
lazyLoadExistingTagNames();
|
||||
caseDb.deleteContentTag(tag);
|
||||
@ -286,6 +313,9 @@ public class TagsManager implements Closeable {
|
||||
* case database.
|
||||
*/
|
||||
public synchronized List<ContentTag> getAllContentTags() throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
return caseDb.getAllContentTags();
|
||||
}
|
||||
@ -301,6 +331,9 @@ public class TagsManager implements Closeable {
|
||||
* the case database.
|
||||
*/
|
||||
public synchronized long getContentTagsCountByTagName(TagName tagName) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
return caseDb.getContentTagsCountByTagName(tagName);
|
||||
}
|
||||
@ -316,6 +349,9 @@ public class TagsManager implements Closeable {
|
||||
* case database.
|
||||
*/
|
||||
public synchronized ContentTag getContentTagByTagID(long tagID) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
return caseDb.getContentTagByID(tagID);
|
||||
}
|
||||
@ -332,6 +368,9 @@ public class TagsManager implements Closeable {
|
||||
* case database.
|
||||
*/
|
||||
public synchronized List<ContentTag> getContentTagsByTagName(TagName tagName) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
return caseDb.getContentTagsByTagName(tagName);
|
||||
}
|
||||
@ -348,6 +387,9 @@ public class TagsManager implements Closeable {
|
||||
* case database.
|
||||
*/
|
||||
public synchronized List<ContentTag> getContentTagsByContent(Content content) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
return caseDb.getContentTagsByContent(content);
|
||||
}
|
||||
@ -365,6 +407,9 @@ public class TagsManager implements Closeable {
|
||||
* database.
|
||||
*/
|
||||
public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
return addBlackboardArtifactTag(artifact, tagName, "");
|
||||
}
|
||||
|
||||
@ -382,12 +427,15 @@ public class TagsManager implements Closeable {
|
||||
* database.
|
||||
*/
|
||||
public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName, String comment) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
BlackboardArtifactTag tag;
|
||||
synchronized (this) {
|
||||
lazyLoadExistingTagNames();
|
||||
if (null == comment) {
|
||||
throw new IllegalArgumentException("Passed null comment argument");
|
||||
}
|
||||
}
|
||||
tag = caseDb.addBlackboardArtifactTag(artifact, tagName, comment);
|
||||
}
|
||||
|
||||
@ -408,6 +456,9 @@ public class TagsManager implements Closeable {
|
||||
* case database.
|
||||
*/
|
||||
public void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
synchronized (this) {
|
||||
lazyLoadExistingTagNames();
|
||||
caseDb.deleteBlackboardArtifactTag(tag);
|
||||
@ -429,6 +480,9 @@ public class TagsManager implements Closeable {
|
||||
* case database.
|
||||
*/
|
||||
public synchronized List<BlackboardArtifactTag> getAllBlackboardArtifactTags() throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
return caseDb.getAllBlackboardArtifactTags();
|
||||
}
|
||||
@ -445,6 +499,9 @@ public class TagsManager implements Closeable {
|
||||
* the case database.
|
||||
*/
|
||||
public synchronized long getBlackboardArtifactTagsCountByTagName(TagName tagName) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
return caseDb.getBlackboardArtifactTagsCountByTagName(tagName);
|
||||
}
|
||||
@ -460,6 +517,9 @@ public class TagsManager implements Closeable {
|
||||
* case database.
|
||||
*/
|
||||
public synchronized BlackboardArtifactTag getBlackboardArtifactTagByTagID(long tagID) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
return caseDb.getBlackboardArtifactTagByID(tagID);
|
||||
}
|
||||
@ -476,6 +536,9 @@ public class TagsManager implements Closeable {
|
||||
* case database.
|
||||
*/
|
||||
public synchronized List<BlackboardArtifactTag> getBlackboardArtifactTagsByTagName(TagName tagName) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
return caseDb.getBlackboardArtifactTagsByTagName(tagName);
|
||||
}
|
||||
@ -492,16 +555,25 @@ public class TagsManager implements Closeable {
|
||||
* case database.
|
||||
*/
|
||||
public synchronized List<BlackboardArtifactTag> getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact) throws TskCoreException {
|
||||
if (null == caseDb) {
|
||||
throw new TskCoreException("Tags manager has been closed");
|
||||
}
|
||||
lazyLoadExistingTagNames();
|
||||
return caseDb.getBlackboardArtifactTagsByArtifact(artifact);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the avaialble tag names to secondary storage.
|
||||
* Closes the tags manager, saving the avaialble tag names to secondary
|
||||
* storage.
|
||||
*
|
||||
* @throws IOException If there is a problem closing the tags manager.
|
||||
* @deprecated Tags manager clients should not close the tags manager.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public synchronized void close() throws IOException {
|
||||
saveTagNamesToTagsSettings();
|
||||
caseDb = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,6 +27,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
@ -127,6 +128,7 @@ public final class ExternalResultsImporter {
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({"ExternalResultsImporter.indexError.message=Failed to index imported artifact for keyword search."})
|
||||
private void importArtifacts(ExternalResults results) {
|
||||
SleuthkitCase caseDb = Case.getCurrentCase().getSleuthkitCase();
|
||||
for (ExternalResults.Artifact artifactData : results.getArtifacts()) {
|
||||
@ -200,9 +202,9 @@ public final class ExternalResultsImporter {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(artifact);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", artifact.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), artifact.getDisplayName());
|
||||
Bundle.ExternalResultsImporter_indexError_message(), artifact.getDisplayName());
|
||||
}
|
||||
|
||||
if (standardArtifactTypeIds.contains(artifactTypeId)) {
|
||||
|
@ -53,6 +53,6 @@ SearchNode.getName.text=Search Result
|
||||
SizeSearchPanel.sizeCompareComboBox.equalTo=equal to
|
||||
SizeSearchPanel.sizeCompareComboBox.greaterThan=greater than
|
||||
SizeSearchPanel.sizeCompareComboBox.lessThan=less than
|
||||
MimeTypePanel.jCheckBox1.text=MIME Type:
|
||||
MimeTypePanel.jLabel1.text=*Note: Multiple MIME types can be selected
|
||||
FileSearchPanel.searchButton.text=Search
|
||||
MimeTypePanel.mimeTypeCheckBox.text=MIME Type:
|
||||
|
@ -180,6 +180,9 @@
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="DateSearchPanel.dateCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="dateCheckBoxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="timeZoneComboBox">
|
||||
<Properties>
|
||||
|
@ -74,6 +74,7 @@ class DateSearchPanel extends javax.swing.JPanel {
|
||||
copyMenuItem.addActionListener(actList);
|
||||
pasteMenuItem.addActionListener(actList);
|
||||
selectAllMenuItem.addActionListener(actList);
|
||||
this.setComponentsEnabled();
|
||||
}
|
||||
|
||||
JCheckBox getAccessedCheckBox() {
|
||||
@ -116,6 +117,23 @@ class DateSearchPanel extends javax.swing.JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
private void setComponentsEnabled() {
|
||||
boolean enable = this.dateCheckBox.isSelected();
|
||||
this.dateFromTextField.setEnabled(enable);
|
||||
this.dateFromButtonCalendar.setEnabled(enable);
|
||||
this.jLabel1.setEnabled(enable);
|
||||
this.dateToTextField.setEnabled(enable);
|
||||
this.dateToButtonCalendar.setEnabled(enable);
|
||||
this.jLabel2.setEnabled(enable);
|
||||
this.jLabel3.setEnabled(enable);
|
||||
this.jLabel4.setEnabled(enable);
|
||||
this.timeZoneComboBox.setEnabled(enable);
|
||||
this.modifiedCheckBox.setEnabled(enable);
|
||||
this.accessedCheckBox.setEnabled(enable);
|
||||
this.changedCheckBox.setEnabled(enable);
|
||||
this.createdCheckBox.setEnabled(enable);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
@ -170,6 +188,11 @@ class DateSearchPanel extends javax.swing.JPanel {
|
||||
jLabel4.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.jLabel4.text")); // NOI18N
|
||||
|
||||
dateCheckBox.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.dateCheckBox.text")); // NOI18N
|
||||
dateCheckBox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
dateCheckBoxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
jLabel3.setFont(new java.awt.Font("Tahoma", 0, 10)); // NOI18N
|
||||
jLabel3.setText(org.openide.util.NbBundle.getMessage(DateSearchPanel.class, "DateSearchPanel.jLabel3.text")); // NOI18N
|
||||
@ -324,6 +347,10 @@ class DateSearchPanel extends javax.swing.JPanel {
|
||||
}
|
||||
}//GEN-LAST:event_dateToPopupChanged
|
||||
|
||||
private void dateCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dateCheckBoxActionPerformed
|
||||
this.setComponentsEnabled();
|
||||
}//GEN-LAST:event_dateCheckBoxActionPerformed
|
||||
|
||||
/**
|
||||
* Validate and set the datetime field on the screen given a datetime
|
||||
* string.
|
||||
|
@ -53,6 +53,9 @@
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="KnownStatusSearchPanel.knownCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="knownCheckBoxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="unknownOptionCheckBox">
|
||||
<Properties>
|
||||
|
@ -37,6 +37,7 @@ class KnownStatusSearchPanel extends javax.swing.JPanel {
|
||||
*/
|
||||
KnownStatusSearchPanel() {
|
||||
initComponents();
|
||||
setComponentsEnabled();
|
||||
}
|
||||
|
||||
JCheckBox getKnownCheckBox() {
|
||||
@ -54,6 +55,13 @@ class KnownStatusSearchPanel extends javax.swing.JPanel {
|
||||
JCheckBox getUnknownOptionCheckBox() {
|
||||
return unknownOptionCheckBox;
|
||||
}
|
||||
|
||||
private void setComponentsEnabled() {
|
||||
boolean enabled = this.knownCheckBox.isSelected();
|
||||
this.unknownOptionCheckBox.setEnabled(enabled);
|
||||
this.knownOptionCheckBox.setEnabled(enabled);
|
||||
this.knownBadOptionCheckBox.setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
@ -70,6 +78,11 @@ class KnownStatusSearchPanel extends javax.swing.JPanel {
|
||||
knownBadOptionCheckBox = new javax.swing.JCheckBox();
|
||||
|
||||
knownCheckBox.setText(org.openide.util.NbBundle.getMessage(KnownStatusSearchPanel.class, "KnownStatusSearchPanel.knownCheckBox.text")); // NOI18N
|
||||
knownCheckBox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
knownCheckBoxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
unknownOptionCheckBox.setSelected(true);
|
||||
unknownOptionCheckBox.setText(org.openide.util.NbBundle.getMessage(KnownStatusSearchPanel.class, "KnownStatusSearchPanel.unknownOptionCheckBox.text")); // NOI18N
|
||||
@ -117,6 +130,10 @@ class KnownStatusSearchPanel extends javax.swing.JPanel {
|
||||
// TODO add your handling code here:
|
||||
}//GEN-LAST:event_knownOptionCheckBoxActionPerformed
|
||||
|
||||
private void knownCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_knownCheckBoxActionPerformed
|
||||
setComponentsEnabled();
|
||||
}//GEN-LAST:event_knownCheckBoxActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JCheckBox knownBadOptionCheckBox;
|
||||
private javax.swing.JCheckBox knownCheckBox;
|
||||
|
@ -25,13 +25,13 @@
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="jCheckBox1" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="mimeTypeCheckBox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jScrollPane1" pref="0" max="32767" attributes="0"/>
|
||||
<Component id="jScrollPane1" pref="298" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="jLabel1" min="-2" pref="246" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
@ -44,12 +44,12 @@
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="jCheckBox1" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="mimeTypeCheckBox" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane1" pref="106" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
@ -62,7 +62,7 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JList" name="jList1">
|
||||
<Component class="javax.swing.JList" name="mimeTypeList">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ListModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="new javax.swing.AbstractListModel<String>() {
 String[] strings = getMimeTypeArray();
 public int getSize() { return strings.length; }
 public String getElementAt(int i) { return strings[i]; }
}" type="code"/>
|
||||
@ -77,12 +77,15 @@
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JCheckBox" name="jCheckBox1">
|
||||
<Component class="javax.swing.JCheckBox" name="mimeTypeCheckBox">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="MimeTypePanel.jCheckBox1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="MimeTypePanel.mimeTypeCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="mimeTypeCheckBoxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="jLabel1">
|
||||
<Properties>
|
||||
|
@ -5,17 +5,16 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.filesearch;
|
||||
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.logging.Level;
|
||||
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
|
||||
import org.apache.tika.mime.MediaType;
|
||||
import org.apache.tika.mime.MimeTypes;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -32,6 +31,7 @@ public class MimeTypePanel extends javax.swing.JPanel {
|
||||
*/
|
||||
public MimeTypePanel() {
|
||||
initComponents();
|
||||
setComponentsEnabled();
|
||||
}
|
||||
|
||||
private String[] getMimeTypeArray() {
|
||||
@ -63,11 +63,17 @@ public class MimeTypePanel extends javax.swing.JPanel {
|
||||
}
|
||||
|
||||
List<String> getMimeTypesSelected() {
|
||||
return this.jList1.getSelectedValuesList();
|
||||
return this.mimeTypeList.getSelectedValuesList();
|
||||
}
|
||||
|
||||
boolean isSelected() {
|
||||
return this.jCheckBox1.isSelected();
|
||||
return this.mimeTypeCheckBox.isSelected();
|
||||
}
|
||||
|
||||
void setComponentsEnabled() {
|
||||
boolean enabled = this.isSelected();
|
||||
this.mimeTypeList.setEnabled(enabled);
|
||||
this.jLabel1.setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,22 +86,27 @@ public class MimeTypePanel extends javax.swing.JPanel {
|
||||
private void initComponents() {
|
||||
|
||||
jScrollPane1 = new javax.swing.JScrollPane();
|
||||
jList1 = new javax.swing.JList<String>();
|
||||
jCheckBox1 = new javax.swing.JCheckBox();
|
||||
mimeTypeList = new javax.swing.JList<>();
|
||||
mimeTypeCheckBox = new javax.swing.JCheckBox();
|
||||
jLabel1 = new javax.swing.JLabel();
|
||||
|
||||
setMinimumSize(new java.awt.Dimension(150, 150));
|
||||
setPreferredSize(new java.awt.Dimension(100, 100));
|
||||
|
||||
jList1.setModel(new javax.swing.AbstractListModel<String>() {
|
||||
mimeTypeList.setModel(new javax.swing.AbstractListModel<String>() {
|
||||
String[] strings = getMimeTypeArray();
|
||||
public int getSize() { return strings.length; }
|
||||
public String getElementAt(int i) { return strings[i]; }
|
||||
});
|
||||
jList1.setMinimumSize(new java.awt.Dimension(0, 200));
|
||||
jScrollPane1.setViewportView(jList1);
|
||||
mimeTypeList.setMinimumSize(new java.awt.Dimension(0, 200));
|
||||
jScrollPane1.setViewportView(mimeTypeList);
|
||||
|
||||
org.openide.awt.Mnemonics.setLocalizedText(jCheckBox1, org.openide.util.NbBundle.getMessage(MimeTypePanel.class, "MimeTypePanel.jCheckBox1.text")); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(mimeTypeCheckBox, org.openide.util.NbBundle.getMessage(MimeTypePanel.class, "MimeTypePanel.mimeTypeCheckBox.text")); // NOI18N
|
||||
mimeTypeCheckBox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
mimeTypeCheckBoxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
jLabel1.setFont(new java.awt.Font("Tahoma", 0, 10)); // NOI18N
|
||||
org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(MimeTypePanel.class, "MimeTypePanel.jLabel1.text")); // NOI18N
|
||||
@ -105,12 +116,12 @@ public class MimeTypePanel extends javax.swing.JPanel {
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jCheckBox1)
|
||||
.addComponent(mimeTypeCheckBox)
|
||||
.addGap(0, 0, Short.MAX_VALUE))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 298, Short.MAX_VALUE)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 246, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addGap(0, 0, Short.MAX_VALUE)))
|
||||
@ -119,20 +130,24 @@ public class MimeTypePanel extends javax.swing.JPanel {
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addComponent(jCheckBox1)
|
||||
.addComponent(mimeTypeCheckBox)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 106, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jLabel1)
|
||||
.addGap(0, 0, 0))
|
||||
.addContainerGap())
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void mimeTypeCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mimeTypeCheckBoxActionPerformed
|
||||
setComponentsEnabled();
|
||||
}//GEN-LAST:event_mimeTypeCheckBoxActionPerformed
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JCheckBox jCheckBox1;
|
||||
private javax.swing.JLabel jLabel1;
|
||||
private javax.swing.JList<String> jList1;
|
||||
private javax.swing.JScrollPane jScrollPane1;
|
||||
private javax.swing.JCheckBox mimeTypeCheckBox;
|
||||
private javax.swing.JList<String> mimeTypeList;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
|
@ -94,6 +94,9 @@
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="NameSearchPanel.nameCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="nameCheckBoxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="searchTextField">
|
||||
<Properties>
|
||||
|
@ -24,7 +24,6 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.filesearch;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import javax.swing.JCheckBox;
|
||||
@ -43,6 +42,7 @@ class NameSearchPanel extends javax.swing.JPanel {
|
||||
NameSearchPanel() {
|
||||
initComponents();
|
||||
customizeComponents();
|
||||
setComponentsEnabled();
|
||||
}
|
||||
|
||||
private void customizeComponents() {
|
||||
@ -77,6 +77,12 @@ class NameSearchPanel extends javax.swing.JPanel {
|
||||
JTextField getSearchTextField() {
|
||||
return searchTextField;
|
||||
}
|
||||
|
||||
void setComponentsEnabled() {
|
||||
boolean enabled = nameCheckBox.isSelected();
|
||||
this.searchTextField.setEnabled(enabled);
|
||||
this.noteNameLabel.setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
@ -110,6 +116,11 @@ class NameSearchPanel extends javax.swing.JPanel {
|
||||
|
||||
nameCheckBox.setFont(nameCheckBox.getFont().deriveFont(nameCheckBox.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
|
||||
nameCheckBox.setText(org.openide.util.NbBundle.getMessage(NameSearchPanel.class, "NameSearchPanel.nameCheckBox.text")); // NOI18N
|
||||
nameCheckBox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
nameCheckBoxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
searchTextField.setFont(searchTextField.getFont().deriveFont(searchTextField.getFont().getStyle() & ~java.awt.Font.BOLD, 11));
|
||||
searchTextField.setText(org.openide.util.NbBundle.getMessage(NameSearchPanel.class, "NameSearchPanel.searchTextField.text")); // NOI18N
|
||||
@ -154,6 +165,11 @@ class NameSearchPanel extends javax.swing.JPanel {
|
||||
private void searchTextFieldMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_searchTextFieldMouseClicked
|
||||
|
||||
this.nameCheckBox.setSelected(true); }//GEN-LAST:event_searchTextFieldMouseClicked
|
||||
|
||||
private void nameCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nameCheckBoxActionPerformed
|
||||
setComponentsEnabled();
|
||||
}//GEN-LAST:event_nameCheckBoxActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JMenuItem copyMenuItem;
|
||||
private javax.swing.JMenuItem cutMenuItem;
|
||||
|
@ -116,6 +116,9 @@
|
||||
<ResourceString bundle="org/sleuthkit/autopsy/filesearch/Bundle.properties" key="SizeSearchPanel.sizeCheckBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="sizeCheckBoxActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
@ -18,8 +18,6 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.filesearch;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.text.NumberFormat;
|
||||
@ -40,6 +38,7 @@ class SizeSearchPanel extends javax.swing.JPanel {
|
||||
SizeSearchPanel() {
|
||||
initComponents();
|
||||
customizeComponents();
|
||||
setComponentsEnabled();
|
||||
}
|
||||
|
||||
private void customizeComponents() {
|
||||
@ -82,6 +81,13 @@ class SizeSearchPanel extends javax.swing.JPanel {
|
||||
JComboBox<String> getSizeUnitComboBox() {
|
||||
return sizeUnitComboBox;
|
||||
}
|
||||
|
||||
void setComponentsEnabled() {
|
||||
boolean enabled = this.sizeCheckBox.isSelected();
|
||||
this.sizeCompareComboBox.setEnabled(enabled);
|
||||
this.sizeUnitComboBox.setEnabled(enabled);
|
||||
this.sizeTextField.setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
@ -97,9 +103,9 @@ class SizeSearchPanel extends javax.swing.JPanel {
|
||||
copyMenuItem = new javax.swing.JMenuItem();
|
||||
pasteMenuItem = new javax.swing.JMenuItem();
|
||||
selectAllMenuItem = new javax.swing.JMenuItem();
|
||||
sizeUnitComboBox = new javax.swing.JComboBox<String>();
|
||||
sizeUnitComboBox = new javax.swing.JComboBox<>();
|
||||
sizeTextField = new JFormattedTextField(NumberFormat.getIntegerInstance());
|
||||
sizeCompareComboBox = new javax.swing.JComboBox<String>();
|
||||
sizeCompareComboBox = new javax.swing.JComboBox<>();
|
||||
sizeCheckBox = new javax.swing.JCheckBox();
|
||||
|
||||
cutMenuItem.setText(org.openide.util.NbBundle.getMessage(SizeSearchPanel.class, "SizeSearchPanel.cutMenuItem.text")); // NOI18N
|
||||
@ -114,7 +120,7 @@ class SizeSearchPanel extends javax.swing.JPanel {
|
||||
selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(SizeSearchPanel.class, "SizeSearchPanel.selectAllMenuItem.text")); // NOI18N
|
||||
rightClickMenu.add(selectAllMenuItem);
|
||||
|
||||
sizeUnitComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { "Byte(s)", "KB", "MB", "GB", "TB" })); //NON-NLS
|
||||
sizeUnitComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { "Byte(s)", "KB", "MB", "GB", "TB" }));
|
||||
|
||||
sizeTextField.setValue(0);
|
||||
sizeTextField.addMouseListener(new java.awt.event.MouseAdapter() {
|
||||
@ -123,12 +129,14 @@ class SizeSearchPanel extends javax.swing.JPanel {
|
||||
}
|
||||
});
|
||||
|
||||
sizeCompareComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] {
|
||||
NbBundle.getMessage(this.getClass(), "SizeSearchPanel.sizeCompareComboBox.equalTo"),
|
||||
NbBundle.getMessage(this.getClass(), "SizeSearchPanel.sizeCompareComboBox.greaterThan"),
|
||||
NbBundle.getMessage(this.getClass(), "SizeSearchPanel.sizeCompareComboBox.lessThan") }));
|
||||
sizeCompareComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { "equal to", "greater than", "less than" }));
|
||||
|
||||
sizeCheckBox.setText(org.openide.util.NbBundle.getMessage(SizeSearchPanel.class, "SizeSearchPanel.sizeCheckBox.text")); // NOI18N
|
||||
sizeCheckBox.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
sizeCheckBoxActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
@ -157,6 +165,11 @@ class SizeSearchPanel extends javax.swing.JPanel {
|
||||
this.sizeCheckBox.setSelected(true);
|
||||
this.sizeTextField.selectAll(); // select all so user can change it easily
|
||||
}//GEN-LAST:event_sizeTextFieldMouseClicked
|
||||
|
||||
private void sizeCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sizeCheckBoxActionPerformed
|
||||
setComponentsEnabled();
|
||||
}//GEN-LAST:event_sizeCheckBoxActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JMenuItem copyMenuItem;
|
||||
private javax.swing.JMenuItem cutMenuItem;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -23,7 +23,9 @@ import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
*
|
||||
* An implementation of a keyword search service.
|
||||
*
|
||||
* TODO (AUT-2158: This interface should not extend Closeable.
|
||||
*/
|
||||
public interface KeywordSearchService extends Closeable {
|
||||
|
||||
@ -49,4 +51,4 @@ public interface KeywordSearchService extends Closeable {
|
||||
*/
|
||||
public void tryConnect(String host, int port) throws KeywordSearchServiceException;
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
@ -72,6 +71,7 @@ class BrowserLocationAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
@NbBundle.Messages({"BrowserLocationAnalyzer.indexError.message=Failed to index GPS trackpoint artifact for keyword search."})
|
||||
private static void findGeoLocationsInDB(String DatabasePath, AbstractFile f) {
|
||||
Connection connection = null;
|
||||
ResultSet resultSet = null;
|
||||
@ -110,9 +110,9 @@ class BrowserLocationAnalyzer {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactTypeName(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
Bundle.BrowserLocationAnalyzer_indexError_message(), bba.getDisplayName());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -25,8 +25,8 @@ import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
@ -78,6 +78,7 @@ class CacheLocationAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({"CacheLocationAnalyzer.indexError.message=Failed to index GPS trackpoint artifact for keyword search."})
|
||||
private static void findGeoLocationsInFile(File file, AbstractFile f) {
|
||||
byte[] bytes; // will temporarily hold bytes to be converted into the correct data types
|
||||
|
||||
@ -140,14 +141,13 @@ class CacheLocationAnalyzer {
|
||||
//Not storing these for now.
|
||||
// bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_VALUE.getTypeID(),moduleName, accuracy));
|
||||
// bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT.getTypeID(),moduleName, confidence));
|
||||
|
||||
try {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
Bundle.CacheLocationAnalyzer_indexError_message(), bba.getDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ import java.sql.Statement;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
@ -58,7 +58,7 @@ class CallLogAnalyzer {
|
||||
private static final Iterable<String> tableNames = Arrays.asList("calls", "logs"); //NON-NLS
|
||||
|
||||
public static void findCallLogs(Content dataSource, FileManager fileManager) {
|
||||
blackboard = Case.getCurrentCase().getServices().getBlackboard();
|
||||
blackboard = Case.getCurrentCase().getServices().getBlackboard();
|
||||
try {
|
||||
List<AbstractFile> absFiles = fileManager.findFiles(dataSource, "logs.db"); //NON-NLS
|
||||
absFiles.addAll(fileManager.findFiles(dataSource, "contacts.db")); //NON-NLS
|
||||
@ -77,6 +77,7 @@ class CallLogAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({"CallLogAnalyzer.indexError.message=Failed to index call log artifact for keyword search."})
|
||||
private static void findCallLogsInDB(String DatabasePath, AbstractFile f) {
|
||||
|
||||
if (DatabasePath == null || DatabasePath.isEmpty()) {
|
||||
@ -113,16 +114,16 @@ class CallLogAnalyzer {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
Bundle.CallLogAnalyzer_indexError_message(), bba.getDisplayName());
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error posting call log record to the Blackboard", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.log(Level.WARNING, "Could not read table {0} in db {1}", new Object[]{tableName, DatabasePath}); //NON-NLS
|
||||
logger.log(Level.WARNING, String.format("Could not read table %s in db %s", tableName, DatabasePath), e); //NON-NLS
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
|
@ -27,7 +27,7 @@ import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
@ -79,6 +79,7 @@ class ContactAnalyzer {
|
||||
* path The fileId will be the Abstract file associated
|
||||
* with the artifacts
|
||||
*/
|
||||
@Messages({"ContactAnalyzer.indexError.message=Failed to index contact artifact for keyword search."})
|
||||
private static void findContactsInDB(String databasePath, AbstractFile f) {
|
||||
Connection connection = null;
|
||||
ResultSet resultSet = null;
|
||||
@ -154,9 +155,9 @@ class ContactAnalyzer {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
Bundle.ContactAnalyzer_indexError_message(), bba.getDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,8 +26,8 @@ import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
@ -71,6 +71,7 @@ class GoogleMapLocationAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({"GoogleMapLocationAnalyzer.indexError.message=Failed to index GPS route artifact for keyword search."})
|
||||
private static void findGeoLocationsInDB(String DatabasePath, AbstractFile f) {
|
||||
Connection connection = null;
|
||||
ResultSet resultSet = null;
|
||||
@ -136,9 +137,9 @@ class GoogleMapLocationAnalyzer {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
Bundle.GoogleMapLocationAnalyzer_indexError_message(), bba.getDisplayName());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -28,6 +28,7 @@ import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
@ -68,6 +69,7 @@ class TangoMessageAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({"TangoMessageAnalyzer.indexError.message=Failed to index Tango message artifact for keyword search."})
|
||||
private static void findTangoMessagesInDB(String DatabasePath, AbstractFile f) {
|
||||
Connection connection = null;
|
||||
ResultSet resultSet = null;
|
||||
@ -110,14 +112,14 @@ class TangoMessageAnalyzer {
|
||||
bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, moduleName,
|
||||
NbBundle.getMessage(TangoMessageAnalyzer.class,
|
||||
"TangoMessageAnalyzer.bbAttribute.tangoMessage")));
|
||||
|
||||
|
||||
try {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
Bundle.TangoMessageAnalyzer_indexError_message(), bba.getDisplayName());
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -26,8 +26,8 @@ import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
@ -68,6 +68,7 @@ class TextMessageAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({"TextMessageAnalyzer.indexError.message=Failed to index text message artifact for keyword search."})
|
||||
private static void findTextsInDB(String DatabasePath, AbstractFile f) {
|
||||
Connection connection = null;
|
||||
ResultSet resultSet = null;
|
||||
@ -123,14 +124,14 @@ class TextMessageAnalyzer {
|
||||
bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, moduleName,
|
||||
NbBundle.getMessage(TextMessageAnalyzer.class,
|
||||
"TextMessageAnalyzer.bbAttribute.smsMessage")));
|
||||
|
||||
|
||||
try {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
Bundle.TextMessageAnalyzer_indexError_message(), bba.getDisplayName());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -26,8 +26,8 @@ import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
@ -71,6 +71,7 @@ class WWFMessageAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({"WWFMessageAnalyzer.indexError.message=Failed to index WWF message artifact for keyword search."})
|
||||
private static void findWWFMessagesInDB(String DatabasePath, AbstractFile f) {
|
||||
Connection connection = null;
|
||||
ResultSet resultSet = null;
|
||||
@ -110,14 +111,14 @@ class WWFMessageAnalyzer {
|
||||
bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, moduleName,
|
||||
NbBundle.getMessage(WWFMessageAnalyzer.class,
|
||||
"WWFMessageAnalyzer.bbAttribute.wordsWithFriendsMsg")));
|
||||
|
||||
|
||||
try {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
Bundle.WWFMessageAnalyzer_indexError_message(), bba.getDisplayName());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -42,6 +42,7 @@ import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
|
||||
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||
@ -50,7 +51,6 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||
import org.sleuthkit.autopsy.ingest.IngestMessage;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException;
|
||||
import org.sleuthkit.autopsy.ingest.IngestMonitor;
|
||||
import org.sleuthkit.autopsy.ingest.IngestServices;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
|
||||
@ -267,6 +267,7 @@ class SevenZipExtractor {
|
||||
*
|
||||
* @return list of unpacked derived files
|
||||
*/
|
||||
@Messages({"SevenZipExtractor.indexError.message=Failed to index encryption detected artifact for keyword search."})
|
||||
void unpack(AbstractFile archiveFile) {
|
||||
blackboard = Case.getCurrentCase().getServices().getBlackboard();
|
||||
String archiveFilePath;
|
||||
@ -586,9 +587,9 @@ class SevenZipExtractor {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(artifact);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", artifact.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), artifact.getDisplayName());
|
||||
Bundle.SevenZipExtractor_indexError_message(), artifact.getDisplayName());
|
||||
}
|
||||
|
||||
services.fireModuleDataEvent(new ModuleDataEvent(EmbeddedFileExtractorModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED));
|
||||
|
@ -38,15 +38,16 @@ import java.util.TimeZone;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.ingest.FileIngestModule;
|
||||
import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
|
||||
import org.sleuthkit.autopsy.ingest.IngestServices;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
|
||||
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
@ -133,6 +134,7 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
|
||||
return processFile(content);
|
||||
}
|
||||
|
||||
@Messages({"ExifParserFileIngestModule.indexError.message=Failed to index EXIF Metadata artifact for keyword search."})
|
||||
ProcessResult processFile(AbstractFile f) {
|
||||
InputStream in = null;
|
||||
BufferedInputStream bin = null;
|
||||
@ -206,9 +208,9 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
Bundle.ExifParserFileIngestModule_indexError_message(), bba.getDisplayName());
|
||||
}
|
||||
filesToFire = true;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
@ -105,6 +106,7 @@ public class FileExtMismatchIngestModule implements FileIngestModule {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Messages({"FileExtMismatchIngestModule.indexError.message=Failed to index file extension mismatch artifact for keyword search."})
|
||||
public ProcessResult process(AbstractFile abstractFile) {
|
||||
blackboard = Case.getCurrentCase().getServices().getBlackboard();
|
||||
if (this.settings.skipKnownFiles() && (abstractFile.getKnown() == FileKnown.KNOWN)) {
|
||||
@ -139,9 +141,9 @@ public class FileExtMismatchIngestModule implements FileIngestModule {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bart);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bart.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bart.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bart.getDisplayName());
|
||||
Bundle.FileExtMismatchIngestModule_indexError_message(), bart.getDisplayName());
|
||||
}
|
||||
|
||||
services.fireModuleDataEvent(new ModuleDataEvent(FileExtMismatchDetectorModuleFactory.getModuleName(), ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED, Collections.singletonList(bart)));
|
||||
|
@ -22,6 +22,7 @@ import java.awt.Dimension;
|
||||
import java.awt.Toolkit;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.logging.Level;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JFrame;
|
||||
@ -31,6 +32,7 @@ import org.apache.commons.io.FilenameUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
|
||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||
import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb;
|
||||
import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb.KnownFilesType;
|
||||
import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDbManagerException;
|
||||
@ -249,9 +251,16 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog {
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void openButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openButtonActionPerformed
|
||||
String lastBaseDirectory = Paths.get(PlatformUtil.getUserConfigDirectory(), "HashDatabases").toString();
|
||||
if (ModuleSettings.settingExists(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY)) {
|
||||
fileChooser.setCurrentDirectory(new File(ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY)));
|
||||
lastBaseDirectory = ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY);
|
||||
}
|
||||
File hashDbFolder = new File(lastBaseDirectory);
|
||||
// create the folder if it doesn't exist
|
||||
if (!hashDbFolder.exists()) {
|
||||
hashDbFolder.mkdir();
|
||||
}
|
||||
fileChooser.setCurrentDirectory(hashDbFolder);
|
||||
if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||
File databaseFile = fileChooser.getSelectedFile();
|
||||
try {
|
||||
|
@ -26,27 +26,28 @@ import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.ingest.FileIngestModule;
|
||||
import org.sleuthkit.autopsy.ingest.IngestMessage;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
|
||||
import org.sleuthkit.autopsy.ingest.IngestServices;
|
||||
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
|
||||
import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||
import org.sleuthkit.datamodel.HashHitInfo;
|
||||
import org.sleuthkit.datamodel.HashUtility;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.TskException;
|
||||
import org.sleuthkit.autopsy.modules.hashdatabase.HashDbManager.HashDb;
|
||||
import org.sleuthkit.autopsy.ingest.FileIngestModule;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
|
||||
import org.sleuthkit.datamodel.HashHitInfo;
|
||||
|
||||
@NbBundle.Messages({
|
||||
"HashDbIngestModule.noKnownBadHashDbSetMsg=No known bad hash database set.",
|
||||
@ -285,6 +286,7 @@ public class HashDbIngestModule implements FileIngestModule {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Messages({"HashDbIngestModule.indexError.message=Failed to index hashset hit artifact for keyword search."})
|
||||
private void postHashSetHitToBlackboard(AbstractFile abstractFile, String md5Hash, String hashSetName, String comment, boolean showInboxMessage) {
|
||||
try {
|
||||
String MODULE_NAME = NbBundle.getMessage(HashDbIngestModule.class, "HashDbIngestModule.moduleName");
|
||||
@ -303,9 +305,9 @@ public class HashDbIngestModule implements FileIngestModule {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(badFile);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", badFile.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + badFile.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), badFile.getDisplayName());
|
||||
Bundle.HashDbIngestModule_indexError_message(), badFile.getDisplayName());
|
||||
}
|
||||
|
||||
if (showInboxMessage) {
|
||||
|
@ -25,7 +25,7 @@ import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
@ -74,6 +74,7 @@ class CallLogAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({"CallLogAnalyzer.indexError.message=Failed to index call log artifact for keyword search."})
|
||||
private void findCallLogsInDB(String DatabasePath, long fId) {
|
||||
if (DatabasePath == null || DatabasePath.isEmpty()) {
|
||||
return;
|
||||
@ -128,9 +129,9 @@ class CallLogAnalyzer {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
Bundle.CallLogAnalyzer_indexError_message(), bba.getDisplayName());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -30,18 +30,18 @@ import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.autopsy.datamodel.ContentUtils;
|
||||
import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||
|
||||
class ContactAnalyzer {
|
||||
|
||||
@ -91,6 +91,7 @@ class ContactAnalyzer {
|
||||
* path The fileId will be the Abstract file associated
|
||||
* with the artifacts
|
||||
*/
|
||||
@Messages({"ContactAnalyzer.indexError.message=Failed to index contact artifact for keyword search."})
|
||||
private void findContactsInDB(String DatabasePath, long fId) {
|
||||
if (DatabasePath == null || DatabasePath.isEmpty()) {
|
||||
return;
|
||||
@ -149,9 +150,9 @@ class ContactAnalyzer {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
Bundle.ContactAnalyzer_indexError_message(), bba.getDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ import java.sql.Statement;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
@ -74,6 +75,7 @@ class TextMessageAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
@Messages({"TextMessageAnalyzer.indexError.message=Failed to index text message artifact for keyword search."})
|
||||
private void findTextsInDB(String DatabasePath, long fId) {
|
||||
if (DatabasePath == null || DatabasePath.isEmpty()) {
|
||||
return;
|
||||
@ -127,14 +129,14 @@ class TextMessageAnalyzer {
|
||||
bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT, moduleName, subject));
|
||||
bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, moduleName, body));
|
||||
bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, moduleName, NbBundle.getMessage(this.getClass(), "TextMessageAnalyzer.bbAttribute.smsMessage")));
|
||||
|
||||
|
||||
try {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
Bundle.TextMessageAnalyzer_indexError_message(), bba.getDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.Exceptions;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
@ -100,6 +99,7 @@ final class FilesIdentifierIngestModule implements FileIngestModule {
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
@Messages({"FilesIdentifierIngestModule.indexError.message=Failed to index interesting file hit artifact for keyword search."})
|
||||
public ProcessResult process(AbstractFile file) {
|
||||
blackboard = Case.getCurrentCase().getServices().getBlackboard();
|
||||
|
||||
@ -131,9 +131,8 @@ final class FilesIdentifierIngestModule implements FileIngestModule {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(artifact);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", artifact.getDisplayName()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), artifact.getDisplayName());
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(Bundle.FilesIdentifierIngestModule_indexError_message(), artifact.getDisplayName());
|
||||
}
|
||||
|
||||
IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(moduleName, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, Collections.singletonList(artifact)));
|
||||
|
@ -543,7 +543,9 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
this.mimeCheck.setSelected(false);
|
||||
|
||||
} else {
|
||||
this.extensionRadioButton.setEnabled(true);
|
||||
if (this.nameCheck.isSelected()) {
|
||||
this.extensionRadioButton.setEnabled(true);
|
||||
}
|
||||
this.fileSizeCheck.setEnabled(true);
|
||||
this.mimeCheck.setEnabled(true);
|
||||
}
|
||||
@ -814,7 +816,9 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
||||
} else {
|
||||
this.nameTextField.setEnabled(true);
|
||||
this.fullNameRadioButton.setEnabled(true);
|
||||
this.extensionRadioButton.setEnabled(true);
|
||||
if (this.filesRadioButton.isSelected()) {
|
||||
this.extensionRadioButton.setEnabled(true);
|
||||
}
|
||||
this.nameRegexCheckbox.setEnabled(true);
|
||||
}
|
||||
this.setOkButton();
|
||||
|
@ -19,7 +19,7 @@
|
||||
package org.sleuthkit.autopsy.modules.stix;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
@ -58,6 +58,7 @@ class StixArtifactData {
|
||||
objType = a_objType;
|
||||
}
|
||||
|
||||
@Messages({"StixArtifactData.indexError.message=Failed to index STIX interesting file hit artifact for keyword search."})
|
||||
public void createArtifact(String a_title) throws TskCoreException {
|
||||
Blackboard blackboard = Case.getCurrentCase().getServices().getBlackboard();
|
||||
|
||||
@ -72,14 +73,13 @@ class StixArtifactData {
|
||||
bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, "Stix", setName)); //NON-NLS
|
||||
bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TITLE, "Stix", observableId)); //NON-NLS
|
||||
bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, "Stix", objType)); //NON-NLS
|
||||
|
||||
|
||||
try {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bba);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bba.getDisplayName()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bba.getDisplayName());
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(Bundle.StixArtifactData_indexError_message(), bba.getDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,7 +225,8 @@ ReportHTML.writeSum.noCaseNum=<i>No case number</i>
|
||||
ReportBodyFile.generateReport.srcModuleName.text=TSK Body File
|
||||
ReportExcel.endReport.srcModuleName.text=Excel Report
|
||||
ReportHTML.writeIndex.srcModuleName.text=HTML Report
|
||||
ReportKML.genReport.srcModuleName.text=KML Report
|
||||
ReportKML.genReport.srcModuleName.text=Geospatial Data
|
||||
ReportKML.genReport.reportName=KML Report
|
||||
ReportGenerator.artTableColHdr.extension.text=Extension
|
||||
ReportGenerator.artTableColHdr.mimeType.text=MIME Type
|
||||
ReportGenerator.artTableColHdr.processorArchitecture.text=Processor Architecture
|
||||
|
@ -188,7 +188,7 @@ ReportExcel.endReport.srcModuleName.text=Excel\u30ec\u30dd\u30fc\u30c8
|
||||
ReportGenerator.artTableColHdr.extension.text=\u62e1\u5f35\u5b50
|
||||
ReportGenerator.artTableColHdr.mimeType.text=MIME\u30bf\u30a4\u30d7
|
||||
ReportHTML.writeIndex.srcModuleName.text=HTML\u30ec\u30dd\u30fc\u30c8
|
||||
ReportKML.genReport.srcModuleName.text=KML\u30ec\u30dd\u30fc\u30c8
|
||||
ReportKML.genReport.reportName=KML\u30ec\u30dd\u30fc\u30c8
|
||||
ReportGenerator.artTableColHdr.associatedArtifact=\u95a2\u4fc2\u3059\u308b\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8
|
||||
ReportGenerator.artTableColHdr.count=\u30ab\u30a6\u30f3\u30c8
|
||||
ReportGenerator.artTableColHdr.devMake=\u6a5f\u5668\u578b\u540d
|
||||
|
@ -839,10 +839,16 @@ class ReportHTML implements TableReportModule {
|
||||
indexOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFilePath), "UTF-8")); //NON-NLS
|
||||
StringBuilder index = new StringBuilder();
|
||||
final String reportTitle = reportBranding.getReportTitle();
|
||||
String iconPath = reportBranding.getAgencyLogoPath();
|
||||
if (iconPath == null){
|
||||
// use default Autopsy icon if custom icon is not set
|
||||
iconPath = "favicon.ico";
|
||||
}
|
||||
index.append("<head>\n<title>").append(reportTitle).append(" ").append(
|
||||
NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getName())).append(
|
||||
"</title>\n"); //NON-NLS
|
||||
index.append("<link rel=\"icon\" type=\"image/ico\" href=\"favicon.ico\" />\n"); //NON-NLS
|
||||
index.append("<link rel=\"icon\" type=\"image/ico\" href=\"")
|
||||
.append(iconPath).append("\" />\n"); //NON-NLS
|
||||
index.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
|
||||
index.append("</head>\n"); //NON-NLS
|
||||
index.append("<frameset cols=\"350px,*\">\n"); //NON-NLS
|
||||
|
File diff suppressed because it is too large
Load Diff
299
Core/src/org/sleuthkit/autopsy/report/stylesheets/style.kml
Executable file
299
Core/src/org/sleuthkit/autopsy/report/stylesheets/style.kml
Executable file
@ -0,0 +1,299 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2">
|
||||
<Document>
|
||||
<StyleMap id="yellowFeature">
|
||||
<Pair>
|
||||
<key>normal</key>
|
||||
<styleUrl>#n_YellowPushpin</styleUrl>
|
||||
</Pair>
|
||||
<Pair>
|
||||
<key>highlight</key>
|
||||
<styleUrl>#h_YellowPushpin</styleUrl>
|
||||
</Pair>
|
||||
</StyleMap>
|
||||
<Style id="n_YellowPushpin">
|
||||
<IconStyle>
|
||||
<scale>1.0</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
<LabelStyle>
|
||||
<scale>0</scale>
|
||||
</LabelStyle>
|
||||
<LineStyle>
|
||||
<color>FF00FFFF</color>
|
||||
<width>5.0</width>
|
||||
</LineStyle>
|
||||
<BalloonStyle>
|
||||
<text>$[description]</text>
|
||||
</BalloonStyle>
|
||||
</Style>
|
||||
<Style id="h_YellowPushpin">
|
||||
<IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/ylw-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
<LabelStyle>
|
||||
<scale>1</scale>
|
||||
</LabelStyle>
|
||||
<LineStyle>
|
||||
<color>FF00FFFF</color>
|
||||
<width>10.0</width>
|
||||
</LineStyle>
|
||||
<BalloonStyle>
|
||||
<text>$[description]</text>
|
||||
</BalloonStyle>
|
||||
</Style>
|
||||
<StyleMap id="blueFeature">
|
||||
<Pair>
|
||||
<key>normal</key>
|
||||
<styleUrl>#n_bluePushpin</styleUrl>
|
||||
</Pair>
|
||||
<Pair>
|
||||
<key>highlight</key>
|
||||
<styleUrl>#h_bluePushpin</styleUrl>
|
||||
</Pair>
|
||||
</StyleMap>
|
||||
<Style id="n_bluePushpin">
|
||||
<IconStyle>
|
||||
<scale>1.0</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
<LabelStyle>
|
||||
<scale>0</scale>
|
||||
</LabelStyle>
|
||||
<LineStyle>
|
||||
<color>FFE63900</color>
|
||||
<width>5.0</width>
|
||||
</LineStyle>
|
||||
<BalloonStyle>
|
||||
<text>$[description]</text>
|
||||
</BalloonStyle>
|
||||
</Style>
|
||||
<Style id="h_bluePushpin">
|
||||
<IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
<LabelStyle>
|
||||
<scale>1</scale>
|
||||
</LabelStyle>
|
||||
<LineStyle>
|
||||
<color>FFE63900</color>
|
||||
<width>10.0</width>
|
||||
</LineStyle>
|
||||
<BalloonStyle>
|
||||
<text>$[description]</text>
|
||||
</BalloonStyle>
|
||||
</Style>
|
||||
<StyleMap id="redFeature">
|
||||
<Pair>
|
||||
<key>normal</key>
|
||||
<styleUrl>#n_redPushpin</styleUrl>
|
||||
</Pair>
|
||||
<Pair>
|
||||
<key>highlight</key>
|
||||
<styleUrl>#h_redPushpin</styleUrl>
|
||||
</Pair>
|
||||
</StyleMap>
|
||||
<Style id="n_redPushpin">
|
||||
<IconStyle>
|
||||
<scale>1.0</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/red-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
<LabelStyle>
|
||||
<scale>0</scale>
|
||||
</LabelStyle>
|
||||
<LineStyle>
|
||||
<color>FF0000FF</color>
|
||||
<width>5.0</width>
|
||||
</LineStyle>
|
||||
<BalloonStyle>
|
||||
<text>$[description]</text>
|
||||
</BalloonStyle>
|
||||
</Style>
|
||||
<Style id="h_redPushpin">
|
||||
<IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/red-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
<LabelStyle>
|
||||
<scale>1</scale>
|
||||
</LabelStyle>
|
||||
<LineStyle>
|
||||
<color>FF0000FF</color>
|
||||
<width>10.0</width>
|
||||
</LineStyle>
|
||||
<BalloonStyle>
|
||||
<text>$[description]</text>
|
||||
</BalloonStyle>
|
||||
</Style>
|
||||
<StyleMap id="greenFeature">
|
||||
<Pair>
|
||||
<key>normal</key>
|
||||
<styleUrl>#n_greenPushpin</styleUrl>
|
||||
</Pair>
|
||||
<Pair>
|
||||
<key>highlight</key>
|
||||
<styleUrl>#h_greenPushpin</styleUrl>
|
||||
</Pair>
|
||||
</StyleMap>
|
||||
<Style id="n_greenPushpin">
|
||||
<IconStyle>
|
||||
<scale>1.0</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/grn-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
<LabelStyle>
|
||||
<scale>1</scale>
|
||||
</LabelStyle>
|
||||
<LineStyle>
|
||||
<color>FF00CC00</color>
|
||||
<width>5.0</width>
|
||||
</LineStyle>
|
||||
<BalloonStyle>
|
||||
<text>
|
||||
A route was planned between these two points. The green line connecting the points is not the actual route, but it indicates which Start and End points are associated with each other.
|
||||
$[description]
|
||||
</text>
|
||||
</BalloonStyle>
|
||||
</Style>
|
||||
<Style id="h_greenPushpin">
|
||||
<IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/grn-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
<LabelStyle>
|
||||
<scale>1</scale>
|
||||
</LabelStyle>
|
||||
<LineStyle>
|
||||
<color>FF00CC00</color>
|
||||
<width>10.0</width>
|
||||
</LineStyle>
|
||||
<BalloonStyle>
|
||||
<text>
|
||||
A route was planned between these two points. The green line connecting the points is not the actual route, but it indicates which Start and End points are associated with each other.
|
||||
$[description]
|
||||
</text>
|
||||
</BalloonStyle>
|
||||
</Style>
|
||||
<StyleMap id="purpleFeature">
|
||||
<Pair>
|
||||
<key>normal</key>
|
||||
<styleUrl>#n_purplePushpin</styleUrl>
|
||||
</Pair>
|
||||
<Pair>
|
||||
<key>highlight</key>
|
||||
<styleUrl>#h_purplePushpin</styleUrl>
|
||||
</Pair>
|
||||
</StyleMap>
|
||||
<Style id="n_purplePushpin">
|
||||
<IconStyle>
|
||||
<scale>1.0</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/purple-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
<LabelStyle>
|
||||
<scale>1</scale>
|
||||
</LabelStyle>
|
||||
<LineStyle>
|
||||
<color>FFCC0066</color>
|
||||
<width>5.0</width>
|
||||
</LineStyle>
|
||||
<BalloonStyle>
|
||||
<text>$[description]</text>
|
||||
</BalloonStyle>
|
||||
</Style>
|
||||
<Style id="h_purplePushpin">
|
||||
<IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/purple-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
<LabelStyle>
|
||||
<scale>1</scale>
|
||||
</LabelStyle>
|
||||
<LineStyle>
|
||||
<color>FFCC0066</color>
|
||||
<width>10.0</width>
|
||||
</LineStyle>
|
||||
<BalloonStyle>
|
||||
<text>$[description]</text>
|
||||
</BalloonStyle>
|
||||
</Style>
|
||||
<StyleMap id="whiteFeature">
|
||||
<Pair>
|
||||
<key>normal</key>
|
||||
<styleUrl>#n_whitePushpin</styleUrl>
|
||||
</Pair>
|
||||
<Pair>
|
||||
<key>highlight</key>
|
||||
<styleUrl>#h_whitePushpin</styleUrl>
|
||||
</Pair>
|
||||
</StyleMap>
|
||||
<Style id="n_whitePushpin">
|
||||
<IconStyle>
|
||||
<scale>1.0</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/wht-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
<LabelStyle>
|
||||
<scale>0</scale>
|
||||
</LabelStyle>
|
||||
<BalloonStyle>
|
||||
<text>$[description]</text>
|
||||
</BalloonStyle>
|
||||
<LineStyle>
|
||||
<color>FFFFFFFF</color>
|
||||
<width>5.0</width>
|
||||
</LineStyle>
|
||||
</Style>
|
||||
<Style id="h_whitePushpin">
|
||||
<IconStyle>
|
||||
<scale>1.3</scale>
|
||||
<Icon>
|
||||
<href>http://maps.google.com/mapfiles/kml/pushpin/wht-pushpin.png</href>
|
||||
</Icon>
|
||||
<hotSpot x="20" y="2" xunits="pixels" yunits="pixels" />
|
||||
</IconStyle>
|
||||
<LabelStyle>
|
||||
<scale>1</scale>
|
||||
</LabelStyle>
|
||||
<LineStyle>
|
||||
<color>FFFFFFFF</color>
|
||||
<width>10.0</width>
|
||||
</LineStyle>
|
||||
<BalloonStyle>
|
||||
<text>$[description]</text>
|
||||
</BalloonStyle>
|
||||
</Style>
|
||||
</Document>
|
||||
</kml>
|
@ -9,7 +9,7 @@ Timeline.zoomOutButton.text=Zoom Out
|
||||
Timeline.goToButton.text=Go To\:
|
||||
Timeline.yearBarChart.x.years=Years
|
||||
Timeline.resultPanel.loading=Loading...
|
||||
Timeline.node.root=Root
|
||||
|
||||
TimelineFrame.title=Timeline
|
||||
TimelinePanel.jButton1.text=6m
|
||||
TimelinePanel.jButton13.text=all
|
||||
@ -24,4 +24,4 @@ TimelinePanel.jButton7.text=3d
|
||||
TimelinePanel.jButton2.text=1m
|
||||
TimelinePanel.jButton3.text=3m
|
||||
TimelinePanel.jButton4.text=2w
|
||||
ProgressWindow.progressHeader.text=\
|
||||
ProgressWindow.progressHeader.text=\
|
||||
|
@ -1,48 +1,48 @@
|
||||
CTL_MakeTimeline=\u300C\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u300D
|
||||
CTL_TimeLineTopComponent=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A6\u30A3\u30F3\u30C9\u30A6
|
||||
CTL_TimeLineTopComponentAction=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C8\u30C3\u30D7\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8
|
||||
HINT_TimeLineTopComponent=\u3053\u308C\u306F\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A6\u30A3\u30F3\u30C9\u30A6\u3067\u3059
|
||||
OpenTimelineAction.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3
|
||||
Timeline.frameName.text={0} - Autopsy\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3
|
||||
Timeline.goToButton.text=\u4E0B\u8A18\u3078\u79FB\u52D5\uFF1A
|
||||
Timeline.node.root=\u30EB\u30FC\u30C8
|
||||
Timeline.pushDescrLOD.confdlg.msg={0}\u30A4\u30D9\u30F3\u30C8\u306E\u8A73\u7D30\u304C\u8868\u793A\u53EF\u80FD\u3067\u3059\u3002\u3053\u306E\u51E6\u7406\u306F\u9577\u6642\u9593\u304B\u304B\u308B\u3082\u3057\u304F\u306FAutopsy\u3092\u30AF\u30E9\u30C3\u30B7\u30E5\u3059\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\n\n\u5B9F\u884C\u3057\u307E\u3059\u304B\uFF1F
|
||||
Timeline.resultPanel.loading=\u30ED\u30FC\u30C9\u4E2D\u30FB\u30FB\u30FB
|
||||
Timeline.resultsPanel.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u7D50\u679C
|
||||
Timeline.runJavaFxThread.progress.creating=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u4F5C\u6210\u4E2D\u30FB\u30FB\u30FB
|
||||
Timeline.zoomOutButton.text=\u30BA\u30FC\u30E0\u30A2\u30A6\u30C8
|
||||
TimelineFrame.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3
|
||||
TimeLineTopComponent.eventsTab.name=\u30A4\u30D9\u30F3\u30C8
|
||||
TimeLineTopComponent.filterTab.name=\u30D5\u30A3\u30EB\u30BF\u30FC
|
||||
OpenTimeLineAction.msgdlg.text=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u4F5C\u6210\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u304C\u3042\u308A\u307E\u305B\u3093\u3002
|
||||
PrompDialogManager.buttonType.continueNoUpdate=\u66F4\u65B0\u305B\u305A\u6B21\u3078
|
||||
PrompDialogManager.buttonType.showTimeline=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u8868\u793A
|
||||
PrompDialogManager.buttonType.update=\u66F4\u65B0
|
||||
PromptDialogManager.confirmDuringIngest.contentText=\u6B21\u3078\u9032\u307F\u307E\u3059\u304B\uFF1F
|
||||
PromptDialogManager.confirmDuringIngest.headerText=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u304C\u5B8C\u4E86\u3059\u308B\u524D\u306B\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3092\u8868\u793A\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u307E\u3059\u3002\n\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u304C\u5B8C\u6210\u3057\u3066\u3044\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002
|
||||
PromptDialogManager.progressDialog.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u3092\u5165\u529B\u4E2D
|
||||
PromptDialogManager.rebuildPrompt.details=\u8A73\u7D30\uFF1A
|
||||
PromptDialogManager.rebuildPrompt.headerText=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u304C\u4E0D\u5B8C\u5168\u307E\u305F\u306F\u6700\u65B0\u3067\u306F\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\n \u6B20\u843D\u3057\u3066\u3044\u308B\u307E\u305F\u306F\u4E0D\u6B63\u78BA\u306A\u30A4\u30D9\u30F3\u30C8\u304C\u4E00\u90E8\u3042\u308B\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002\u4E00\u90E8\u306E\u6A5F\u80FD\u304C\u5229\u7528\u3067\u304D\u306A\u3044\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002
|
||||
Timeline.confirmation.dialogs.title=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u66F4\u65B0\u3057\u307E\u3059\u304B\uFF1F
|
||||
Timeline.pushDescrLOD.confdlg.title=\u8AAC\u660E\u306E\u8A18\u8FF0\u30EC\u30D9\u30EB\u3092\u5909\u66F4\u3057\u307E\u3059\u304B\uFF1F
|
||||
TimeLineController.errorTitle=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A8\u30E9\u30FC
|
||||
TimeLineController.outOfDate.errorMessage=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u304C\u6700\u65B0\u304B\u78BA\u8A8D\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002\u66F4\u65B0\u304C\u5FC5\u8981\u3060\u3068\u60F3\u5B9A\u3057\u307E\u3059\u3002
|
||||
TimeLineController.rebuildReasons.incompleteOldSchema=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A4\u30D9\u30F3\u30C8\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306B\u4E0D\u5B8C\u5168\u306A\u60C5\u5831\u304C\u4EE5\u524D\u5165\u529B\u3055\u308C\u3066\u3044\u307E\u3057\u305F\uFF1A\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u66F4\u65B0\u3057\u306A\u3044\u3068\u3001\u4E00\u90E8\u306E\u6A5F\u80FD\u304C\u5229\u7528\u3067\u304D\u306A\u3044\u3001\u307E\u305F\u306F\u6A5F\u80FD\u3057\u306A\u3044\u304B\u3082\u3057\u308C\u306A\u3044\u3067\u3059\u3002
|
||||
TimeLineController.rebuildReasons.ingestWasRunning=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u304C\u5B9F\u884C\u4E2D\u306B\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30A4\u30D9\u30F3\u30C8\u306E\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306B\u60C5\u5831\u304C\u5165\u529B\u3055\u308C\u3066\u3044\u307E\u3057\u305F\uFF1A\u30A4\u30D9\u30F3\u30C8\u304C\u6B20\u3051\u3066\u3044\u308B\u3001\u4E0D\u5B8C\u5168\u3001\u307E\u305F\u306F\u4E0D\u6B63\u78BA\u304B\u3082\u3057\u308C\u307E\u305B\u3093\u3002
|
||||
TimeLineController.rebuildReasons.outOfDate=\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u304C\u6700\u65B0\u3067\u306F\u3042\u308A\u307E\u305B\u3093\uFF1A\u898B\u308C\u306A\u3044\u30A4\u30D9\u30F3\u30C8\u304C\u3042\u308A\u307E\u3059\u3002
|
||||
TimeLineController.rebuildReasons.outOfDateError=\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u30C7\u30FC\u30BF\u304C\u6700\u65B0\u304B\u78BA\u8A8D\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002
|
||||
TimeLinecontroller.updateNowQuestion=\u30A4\u30D9\u30F3\u30C8\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u4ECA\u66F4\u65B0\u3057\u307E\u3059\u304B\uFF1F
|
||||
CTL_MakeTimeline=\u300c\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u300d
|
||||
CTL_TimeLineTopComponent=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a6\u30a3\u30f3\u30c9\u30a6
|
||||
CTL_TimeLineTopComponentAction=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c8\u30c3\u30d7\u30b3\u30f3\u30dd\u30fc\u30cd\u30f3\u30c8
|
||||
HINT_TimeLineTopComponent=\u3053\u308c\u306f\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a6\u30a3\u30f3\u30c9\u30a6\u3067\u3059
|
||||
OpenTimelineAction.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3
|
||||
Timeline.frameName.text={0} - Autopsy\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3
|
||||
Timeline.goToButton.text=\u4e0b\u8a18\u3078\u79fb\u52d5\uff1a
|
||||
Timeline.pushDescrLOD.confdlg.msg={0}\u30a4\u30d9\u30f3\u30c8\u306e\u8a73\u7d30\u304c\u8868\u793a\u53ef\u80fd\u3067\u3059\u3002\u3053\u306e\u51e6\u7406\u306f\u9577\u6642\u9593\u304b\u304b\u308b\u3082\u3057\u304f\u306fAutopsy\u3092\u30af\u30e9\u30c3\u30b7\u30e5\u3059\u308b\u53ef\u80fd\u6027\u304c\u3042\u308a\u307e\u3059\u3002\n\n\u5b9f\u884c\u3057\u307e\u3059\u304b\uff1f
|
||||
Timeline.resultPanel.loading=\u30ed\u30fc\u30c9\u4e2d\u30fb\u30fb\u30fb
|
||||
Timeline.resultsPanel.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u7d50\u679c
|
||||
Timeline.runJavaFxThread.progress.creating=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u4f5c\u6210\u4e2d\u30fb\u30fb\u30fb
|
||||
Timeline.zoomOutButton.text=\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8
|
||||
TimelineFrame.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3
|
||||
TimeLineTopComponent.eventsTab.name=\u30a4\u30d9\u30f3\u30c8
|
||||
TimeLineTopComponent.filterTab.name=\u30d5\u30a3\u30eb\u30bf\u30fc
|
||||
OpenTimeLineAction.msgdlg.text=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u30c7\u30fc\u30bf\u30bd\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002
|
||||
PrompDialogManager.buttonType.continueNoUpdate=\u66f4\u65b0\u305b\u305a\u6b21\u3078
|
||||
PrompDialogManager.buttonType.showTimeline=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u8868\u793a
|
||||
PrompDialogManager.buttonType.update=\u66f4\u65b0
|
||||
PromptDialogManager.confirmDuringIngest.contentText=\u6b21\u3078\u9032\u307f\u307e\u3059\u304b\uff1f
|
||||
PromptDialogManager.confirmDuringIngest.headerText=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b8c\u4e86\u3059\u308b\u524d\u306b\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u3092\u8868\u793a\u3057\u3088\u3046\u3068\u3057\u3066\u3044\u307e\u3059\u3002\n\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u304c\u5b8c\u6210\u3057\u3066\u3044\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
|
||||
PromptDialogManager.progressDialog.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c7\u30fc\u30bf\u3092\u5165\u529b\u4e2d
|
||||
PromptDialogManager.rebuildPrompt.details=\u8a73\u7d30\uff1a
|
||||
PromptDialogManager.rebuildPrompt.headerText=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304c\u4e0d\u5b8c\u5168\u307e\u305f\u306f\u6700\u65b0\u3067\u306f\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002\n \u6b20\u843d\u3057\u3066\u3044\u308b\u307e\u305f\u306f\u4e0d\u6b63\u78ba\u306a\u30a4\u30d9\u30f3\u30c8\u304c\u4e00\u90e8\u3042\u308b\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002\u4e00\u90e8\u306e\u6a5f\u80fd\u304c\u5229\u7528\u3067\u304d\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
|
||||
Timeline.confirmation.dialogs.title=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u66f4\u65b0\u3057\u307e\u3059\u304b\uff1f
|
||||
Timeline.pushDescrLOD.confdlg.title=\u8aac\u660e\u306e\u8a18\u8ff0\u30ec\u30d9\u30eb\u3092\u5909\u66f4\u3057\u307e\u3059\u304b\uff1f
|
||||
TimeLineController.errorTitle=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a8\u30e9\u30fc
|
||||
TimeLineController.outOfDate.errorMessage=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u304c\u6700\u65b0\u304b\u78ba\u8a8d\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002\u66f4\u65b0\u304c\u5fc5\u8981\u3060\u3068\u60f3\u5b9a\u3057\u307e\u3059\u3002
|
||||
TimeLineController.rebuildReasons.incompleteOldSchema=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a4\u30d9\u30f3\u30c8\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u4e0d\u5b8c\u5168\u306a\u60c5\u5831\u304c\u4ee5\u524d\u5165\u529b\u3055\u308c\u3066\u3044\u307e\u3057\u305f\uff1a\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u66f4\u65b0\u3057\u306a\u3044\u3068\u3001\u4e00\u90e8\u306e\u6a5f\u80fd\u304c\u5229\u7528\u3067\u304d\u306a\u3044\u3001\u307e\u305f\u306f\u6a5f\u80fd\u3057\u306a\u3044\u304b\u3082\u3057\u308c\u306a\u3044\u3067\u3059\u3002
|
||||
TimeLineController.rebuildReasons.ingestWasRunning=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u304c\u5b9f\u884c\u4e2d\u306b\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30a4\u30d9\u30f3\u30c8\u306e\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u60c5\u5831\u304c\u5165\u529b\u3055\u308c\u3066\u3044\u307e\u3057\u305f\uff1a\u30a4\u30d9\u30f3\u30c8\u304c\u6b20\u3051\u3066\u3044\u308b\u3001\u4e0d\u5b8c\u5168\u3001\u307e\u305f\u306f\u4e0d\u6b63\u78ba\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
|
||||
TimeLineController.rebuildReasons.outOfDate=\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u304c\u6700\u65b0\u3067\u306f\u3042\u308a\u307e\u305b\u3093\uff1a\u898b\u308c\u306a\u3044\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u3059\u3002
|
||||
TimeLineController.rebuildReasons.outOfDateError=\u30bf\u30a4\u30e0\u30e9\u30a4\u30f3\u30c7\u30fc\u30bf\u304c\u6700\u65b0\u304b\u78ba\u8a8d\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
|
||||
TimeLinecontroller.updateNowQuestion=\u30a4\u30d9\u30f3\u30c8\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u3092\u4eca\u66f4\u65b0\u3057\u307e\u3059\u304b\uff1f
|
||||
TimelinePanel.jButton13.text=\u5168\u3066
|
||||
Timeline.yearBarChart.x.years=\u5E74
|
||||
TimelinePanel.jButton1.text=6\u30F6\u6708
|
||||
Timeline.yearBarChart.x.years=\u5e74
|
||||
TimelinePanel.jButton1.text=6\u30f6\u6708
|
||||
TimelinePanel.jButton10.text=1\u6642\u9593
|
||||
TimelinePanel.jButton9.text=12\u6642\u9593
|
||||
TimelinePanel.jButton11.text=5\u5E74
|
||||
TimelinePanel.jButton12.text=10\u5E74
|
||||
TimelinePanel.jButton11.text=5\u5e74
|
||||
TimelinePanel.jButton12.text=10\u5e74
|
||||
TimelinePanel.jButton6.text=1\u9031\u9593
|
||||
TimelinePanel.jButton5.text=1\u5E74
|
||||
TimelinePanel.jButton8.text=1\u65E5
|
||||
TimelinePanel.jButton7.text=3\u65E5
|
||||
TimelinePanel.jButton2.text=1\u30F6\u6708
|
||||
TimelinePanel.jButton3.text=3\u30F6\u6708
|
||||
TimelinePanel.jButton4.text=2\u9031\u9593
|
||||
TimelinePanel.jButton5.text=1\u5e74
|
||||
TimelinePanel.jButton8.text=1\u65e5
|
||||
TimelinePanel.jButton7.text=3\u65e5
|
||||
TimelinePanel.jButton2.text=1\u30f6\u6708
|
||||
TimelinePanel.jButton3.text=3\u30f6\u6708
|
||||
TimelinePanel.jButton4.text=2\u9031\u9593
|
||||
TimeLineResultView.startDateToEndDate.text={0}\u304b\u3089{1}
|
@ -24,6 +24,7 @@ import java.io.IOException;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@ -188,7 +189,7 @@ public class TimeLineController {
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
private TimeLineTopComponent mainFrame;
|
||||
private TimeLineTopComponent topComponent;
|
||||
|
||||
//are the listeners currently attached
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
@ -199,11 +200,7 @@ public class TimeLineController {
|
||||
private final PropertyChangeListener ingestModuleListener = new AutopsyIngestModuleListener();
|
||||
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper<VisualizationMode> visualizationMode = new ReadOnlyObjectWrapper<>(VisualizationMode.COUNTS);
|
||||
|
||||
synchronized public ReadOnlyObjectProperty<VisualizationMode> visualizationModeProperty() {
|
||||
return visualizationMode.getReadOnlyProperty();
|
||||
}
|
||||
private final ReadOnlyObjectWrapper<ViewMode> viewMode = new ReadOnlyObjectWrapper<>(ViewMode.COUNTS);
|
||||
|
||||
@GuardedBy("filteredEvents")
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
@ -223,21 +220,38 @@ public class TimeLineController {
|
||||
@GuardedBy("this")
|
||||
private final ObservableList<Long> selectedEventIDs = FXCollections.<Long>synchronizedObservableList(FXCollections.<Long>observableArrayList());
|
||||
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>();
|
||||
|
||||
private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(true);
|
||||
|
||||
private final PromptDialogManager promptDialogManager = new PromptDialogManager(this);
|
||||
|
||||
/**
|
||||
* @return A list of the selected event ids
|
||||
* Get an ObservableList of selected event IDs
|
||||
*
|
||||
* @return A list of the selected event IDs
|
||||
*/
|
||||
synchronized public ObservableList<Long> getSelectedEventIDs() {
|
||||
return selectedEventIDs;
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
private final ReadOnlyObjectWrapper<Interval> selectedTimeRange = new ReadOnlyObjectWrapper<>();
|
||||
/**
|
||||
* Get a read only observable view of the selected time range.
|
||||
*
|
||||
* @return A read only view of the selected time range.
|
||||
*/
|
||||
synchronized public ReadOnlyObjectProperty<Interval> selectedTimeRangeProperty() {
|
||||
return selectedTimeRange.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a read only view of the selected interval.
|
||||
* Get the selected time range.
|
||||
*
|
||||
* @return The selected time range.
|
||||
*/
|
||||
synchronized public ReadOnlyObjectProperty<Interval> getSelectedTimeRange() {
|
||||
return selectedTimeRange.getReadOnlyProperty();
|
||||
synchronized public Interval getSelectedTimeRange() {
|
||||
return selectedTimeRange.get();
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty eventsDBStaleProperty() {
|
||||
@ -282,9 +296,30 @@ public class TimeLineController {
|
||||
synchronized public ReadOnlyBooleanProperty canRetreatProperty() {
|
||||
return historyManager.getCanRetreat();
|
||||
}
|
||||
private final ReadOnlyBooleanWrapper eventsDBStale = new ReadOnlyBooleanWrapper(true);
|
||||
|
||||
private final PromptDialogManager promptDialogManager = new PromptDialogManager(this);
|
||||
synchronized public ReadOnlyObjectProperty<ViewMode> viewModeProperty() {
|
||||
return viewMode.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new ViewMode as the active one.
|
||||
*
|
||||
* @param viewMode The new ViewMode to set.
|
||||
*/
|
||||
synchronized public void setViewMode(ViewMode viewMode) {
|
||||
if (this.viewMode.get() != viewMode) {
|
||||
this.viewMode.set(viewMode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently active ViewMode.
|
||||
*
|
||||
* @return The currently active ViewMode.
|
||||
*/
|
||||
synchronized public ViewMode getViewMode() {
|
||||
return viewMode.get();
|
||||
}
|
||||
|
||||
public TimeLineController(Case autoCase) throws IOException {
|
||||
this.autoCase = autoCase;
|
||||
@ -310,6 +345,9 @@ public class TimeLineController {
|
||||
filteredEvents.filterProperty().get(),
|
||||
DescriptionLoD.SHORT);
|
||||
historyManager.advance(InitialZoomState);
|
||||
|
||||
//clear the selected events when the view mode changes
|
||||
viewMode.addListener(observable -> selectEventIDs(Collections.emptySet()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -469,9 +507,9 @@ public class TimeLineController {
|
||||
IngestManager.getInstance().removeIngestModuleEventListener(ingestModuleListener);
|
||||
IngestManager.getInstance().removeIngestJobEventListener(ingestJobListener);
|
||||
Case.removePropertyChangeListener(caseListener);
|
||||
if (mainFrame != null) {
|
||||
mainFrame.close();
|
||||
mainFrame = null;
|
||||
if (topComponent != null) {
|
||||
topComponent.close();
|
||||
topComponent = null;
|
||||
}
|
||||
OpenTimelineAction.invalidateController();
|
||||
}
|
||||
@ -601,17 +639,6 @@ public class TimeLineController {
|
||||
pushTimeRange(new Interval(start, end));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new Visualization mode as the active one.
|
||||
*
|
||||
* @param visualizationMode The new VisaualizationMode to set.
|
||||
*/
|
||||
synchronized public void setVisualizationMode(VisualizationMode visualizationMode) {
|
||||
if (this.visualizationMode.get() != visualizationMode) {
|
||||
this.visualizationMode.set(visualizationMode);
|
||||
}
|
||||
}
|
||||
|
||||
public void selectEventIDs(Collection<Long> events) {
|
||||
final LoggedTask<Interval> selectEventIDsTask = new LoggedTask<Interval>("Select Event IDs", true) { //NON-NLS
|
||||
@Override
|
||||
@ -643,16 +670,16 @@ public class TimeLineController {
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
synchronized private void showWindow() {
|
||||
if (mainFrame == null) {
|
||||
mainFrame = new TimeLineTopComponent(this);
|
||||
if (topComponent == null) {
|
||||
topComponent = new TimeLineTopComponent(this);
|
||||
}
|
||||
mainFrame.open();
|
||||
mainFrame.toFront();
|
||||
topComponent.open();
|
||||
topComponent.toFront();
|
||||
/*
|
||||
* Make this top component active so its ExplorerManager's lookup gets
|
||||
* proxied in Utilities.actionsGlobalContext()
|
||||
*/
|
||||
mainFrame.requestActive();
|
||||
topComponent.requestActive();
|
||||
}
|
||||
|
||||
synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) {
|
||||
|
@ -54,7 +54,7 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javafx.embed.swing.JFXPanel" name="jFXVizPanel">
|
||||
<Container class="javafx.embed.swing.JFXPanel" name="jFXViewPanel">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
|
||||
<JSplitPaneConstraints position="left"/>
|
||||
@ -65,7 +65,7 @@
|
||||
<Property name="useNullLayout" type="boolean" value="true"/>
|
||||
</Layout>
|
||||
</Container>
|
||||
<Container class="javax.swing.JSplitPane" name="lowerSplitXPane">
|
||||
<Container class="javax.swing.JSplitPane" name="horizontalSplitPane">
|
||||
<Properties>
|
||||
<Property name="dividerLocation" type="int" value="600"/>
|
||||
<Property name="resizeWeight" type="double" value="0.5"/>
|
||||
@ -82,33 +82,45 @@
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="resultContainerPanel">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[700, 300]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Container class="javax.swing.JPanel" name="leftFillerPanel">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
|
||||
<JSplitPaneConstraints position="left"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="599" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="54" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="contentViewerContainerPanel">
|
||||
<Properties>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[500, 300]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Container class="javax.swing.JPanel" name="rightfillerPanel">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
|
||||
<JSplitPaneConstraints position="right"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="364" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="54" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,12 +18,14 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.beans.PropertyVetoException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.control.Tab;
|
||||
@ -34,8 +36,15 @@ import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.controlsfx.control.Notifications;
|
||||
import org.joda.time.Interval;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.openide.explorer.ExplorerManager;
|
||||
import org.openide.explorer.ExplorerUtils;
|
||||
import org.openide.nodes.AbstractNode;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.windows.Mode;
|
||||
import org.openide.windows.TopComponent;
|
||||
@ -44,19 +53,22 @@ import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataContentPanel;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventNode;
|
||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
|
||||
import org.sleuthkit.autopsy.timeline.ui.HistoryToolBar;
|
||||
import org.sleuthkit.autopsy.timeline.ui.StatusBar;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineResultView;
|
||||
import org.sleuthkit.autopsy.timeline.ui.TimeZonePanel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.VisualizationPanel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.ViewFrame;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
|
||||
import org.sleuthkit.autopsy.timeline.ui.filtering.FilterSetPanel;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomSettingsPane;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* TopComponent for the timeline feature.
|
||||
* TopComponent for the Timeline feature.
|
||||
*/
|
||||
@TopComponent.Description(
|
||||
preferredID = "TimeLineTopComponent",
|
||||
@ -67,91 +79,209 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(TimeLineTopComponent.class.getName());
|
||||
|
||||
private final DataContentPanel dataContentPanel;
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
private final DataContentPanel contentViewerPanel;
|
||||
|
||||
private final TimeLineResultView tlrv;
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
private DataResultPanel dataResultPanel;
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
private final ExplorerManager em = new ExplorerManager();
|
||||
|
||||
private final TimeLineController controller;
|
||||
|
||||
/**
|
||||
* Listener that drives the result viewer or content viewer (depending on
|
||||
* view mode) according to the controller's selected event IDs
|
||||
*/
|
||||
@NbBundle.Messages({"TimelineTopComponent.selectedEventListener.errorMsg=There was a problem getting the content for the selected event."})
|
||||
private final InvalidationListener selectedEventsListener = new InvalidationListener() {
|
||||
@Override
|
||||
public void invalidated(Observable observable) {
|
||||
ObservableList<Long> selectedEventIDs = controller.getSelectedEventIDs();
|
||||
|
||||
//depending on the active view mode, we either update the dataResultPanel, or update the contentViewerPanel directly.
|
||||
switch (controller.getViewMode()) {
|
||||
case LIST:
|
||||
|
||||
//make an array of EventNodes for the selected events
|
||||
EventNode[] childArray = new EventNode[selectedEventIDs.size()];
|
||||
try {
|
||||
for (int i = 0; i < selectedEventIDs.size(); i++) {
|
||||
childArray[i] = EventNode.createEventNode(selectedEventIDs.get(i), controller.getEventsModel());
|
||||
}
|
||||
Children children = new Children.Array();
|
||||
children.add(childArray);
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
//set generic container node as root context
|
||||
em.setRootContext(new AbstractNode(children));
|
||||
try {
|
||||
//set selected nodes for actions
|
||||
em.setSelectedNodes(childArray);
|
||||
} catch (PropertyVetoException ex) {
|
||||
//I don't know why this would ever happen.
|
||||
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) {
|
||||
contentViewerPanel.setNode(childArray[0]);
|
||||
} else {
|
||||
contentViewerPanel.setNode(null);
|
||||
}
|
||||
});
|
||||
} catch (IllegalStateException ex) {
|
||||
//Since the case is closed, the user probably doesn't care about this, just log it as a precaution.
|
||||
LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
Notifications.create()
|
||||
.owner(jFXViewPanel.getScene().getWindow())
|
||||
.text(Bundle.TimelineTopComponent_selectedEventListener_errorMsg())
|
||||
.showError();
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case COUNTS:
|
||||
case DETAIL:
|
||||
//make a root node with nodes for the selected events as children and push it to the result viewer.
|
||||
EventRootNode rootNode = new EventRootNode(selectedEventIDs, controller.getEventsModel());
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dataResultPanel.setPath(getResultViewerSummaryString());
|
||||
dataResultPanel.setNode(rootNode);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown view mode: " + controller.getViewMode());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The TimeLineController for this topcomponent.
|
||||
*/
|
||||
public TimeLineTopComponent(TimeLineController controller) {
|
||||
initComponents();
|
||||
this.controller = controller;
|
||||
associateLookup(ExplorerUtils.createLookup(em, getActionMap()));
|
||||
|
||||
setName(NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponent"));
|
||||
setToolTipText(NbBundle.getMessage(TimeLineTopComponent.class, "HINT_TimeLineTopComponent"));
|
||||
setIcon(WindowManager.getDefault().getMainWindow().getIconImage()); //use the same icon as main application
|
||||
|
||||
dataContentPanel = DataContentPanel.createInstance();
|
||||
this.contentViewerContainerPanel.add(dataContentPanel, BorderLayout.CENTER);
|
||||
tlrv = new TimeLineResultView(controller, dataContentPanel);
|
||||
DataResultPanel dataResultPanel = tlrv.getDataResultPanel();
|
||||
this.resultContainerPanel.add(dataResultPanel, BorderLayout.CENTER);
|
||||
dataResultPanel.open();
|
||||
customizeFXComponents();
|
||||
}
|
||||
this.controller = controller;
|
||||
|
||||
@NbBundle.Messages({"TimeLineTopComponent.eventsTab.name=Events",
|
||||
"TimeLineTopComponent.filterTab.name=Filters"})
|
||||
void customizeFXComponents() {
|
||||
Platform.runLater(() -> {
|
||||
//create linked result and content views
|
||||
contentViewerPanel = DataContentPanel.createInstance();
|
||||
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, contentViewerPanel);
|
||||
|
||||
//create and wire up jfx componenets that make up the interface
|
||||
final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller));
|
||||
filterTab.setClosable(false);
|
||||
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
|
||||
//add them to bottom splitpane
|
||||
horizontalSplitPane.setLeftComponent(dataResultPanel);
|
||||
horizontalSplitPane.setRightComponent(contentViewerPanel);
|
||||
|
||||
final EventsTree eventsTree = new EventsTree(controller);
|
||||
final VisualizationPanel visualizationPanel = new VisualizationPanel(controller, eventsTree);
|
||||
final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree);
|
||||
eventsTreeTab.setClosable(false);
|
||||
eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
|
||||
eventsTreeTab.disableProperty().bind(controller.visualizationModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||
dataResultPanel.open(); //get the explorermanager
|
||||
|
||||
final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab);
|
||||
VBox.setVgrow(leftTabPane, Priority.ALWAYS);
|
||||
controller.visualizationModeProperty().addListener((Observable observable) -> {
|
||||
if (controller.visualizationModeProperty().get().equals(VisualizationMode.COUNTS)) {
|
||||
//if view mode is counts, make sure events tabd is not active
|
||||
leftTabPane.getSelectionModel().select(filterTab);
|
||||
}
|
||||
});
|
||||
Platform.runLater(this::initFXComponents);
|
||||
|
||||
HistoryToolBar historyToolBar = new HistoryToolBar(controller);
|
||||
final TimeZonePanel timeZonePanel = new TimeZonePanel();
|
||||
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
|
||||
//set up listeners
|
||||
TimeLineController.getTimeZone().addListener(timeZone -> dataResultPanel.setPath(getResultViewerSummaryString()));
|
||||
controller.getSelectedEventIDs().addListener(selectedEventsListener);
|
||||
|
||||
final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller);
|
||||
|
||||
final VBox leftVBox = new VBox(5, timeZonePanel,historyToolBar, zoomSettingsPane, leftTabPane);
|
||||
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
|
||||
|
||||
final SplitPane mainSplitPane = new SplitPane(leftVBox, visualizationPanel);
|
||||
mainSplitPane.setDividerPositions(0);
|
||||
|
||||
final Scene scene = new Scene(mainSplitPane);
|
||||
scene.addEventFilter(KeyEvent.KEY_PRESSED,
|
||||
(KeyEvent event) -> {
|
||||
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||
new Back(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(event)) {
|
||||
new Back(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(event)) {
|
||||
new Forward(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(event)) {
|
||||
new Forward(controller).handle(null);
|
||||
//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);
|
||||
}
|
||||
});
|
||||
|
||||
//add ui componenets to JFXPanels
|
||||
jFXVizPanel.setScene(scene);
|
||||
jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
|
||||
|
||||
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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and wire up JavaFX components of the interface
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"TimeLineTopComponent.eventsTab.name=Events",
|
||||
"TimeLineTopComponent.filterTab.name=Filters"})
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
void initFXComponents() {
|
||||
/////init componenets of left most column from top to bottom
|
||||
final TimeZonePanel timeZonePanel = new TimeZonePanel();
|
||||
VBox.setVgrow(timeZonePanel, Priority.SOMETIMES);
|
||||
HistoryToolBar historyToolBar = new HistoryToolBar(controller);
|
||||
final ZoomSettingsPane zoomSettingsPane = new ZoomSettingsPane(controller);
|
||||
|
||||
//set up filter tab
|
||||
final Tab filterTab = new Tab(Bundle.TimeLineTopComponent_filterTab_name(), new FilterSetPanel(controller));
|
||||
filterTab.setClosable(false);
|
||||
filterTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/funnel.png")); // NON-NLS
|
||||
|
||||
//set up events tab
|
||||
final EventsTree eventsTree = new EventsTree(controller);
|
||||
final Tab eventsTreeTab = new Tab(Bundle.TimeLineTopComponent_eventsTab_name(), eventsTree);
|
||||
eventsTreeTab.setClosable(false);
|
||||
eventsTreeTab.setGraphic(new ImageView("org/sleuthkit/autopsy/timeline/images/timeline_marker.png")); // NON-NLS
|
||||
eventsTreeTab.disableProperty().bind(controller.viewModeProperty().isNotEqualTo(ViewMode.DETAIL));
|
||||
|
||||
final TabPane leftTabPane = new TabPane(filterTab, eventsTreeTab);
|
||||
VBox.setVgrow(leftTabPane, Priority.ALWAYS);
|
||||
controller.viewModeProperty().addListener(viewMode -> {
|
||||
if (controller.getViewMode().equals(ViewMode.DETAIL) == false) {
|
||||
//if view mode is not details, switch back to the filter tab
|
||||
leftTabPane.getSelectionModel().select(filterTab);
|
||||
}
|
||||
});
|
||||
|
||||
//assemble left column
|
||||
final VBox leftVBox = new VBox(5, timeZonePanel, historyToolBar, zoomSettingsPane, leftTabPane);
|
||||
SplitPane.setResizableWithParent(leftVBox, Boolean.FALSE);
|
||||
|
||||
final ViewFrame viewFrame = new ViewFrame(controller, eventsTree);
|
||||
final SplitPane mainSplitPane = new SplitPane(leftVBox, viewFrame);
|
||||
mainSplitPane.setDividerPositions(0);
|
||||
|
||||
final Scene scene = new Scene(mainSplitPane);
|
||||
scene.addEventFilter(KeyEvent.KEY_PRESSED, keyEvent -> {
|
||||
if (new KeyCodeCombination(KeyCode.LEFT, KeyCodeCombination.ALT_DOWN).match(keyEvent)) {
|
||||
new Back(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE).match(keyEvent)) {
|
||||
new Back(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.RIGHT, KeyCodeCombination.ALT_DOWN).match(keyEvent)) {
|
||||
new Forward(controller).handle(null);
|
||||
} else if (new KeyCodeCombination(KeyCode.BACK_SPACE, KeyCodeCombination.SHIFT_DOWN).match(keyEvent)) {
|
||||
new Forward(controller).handle(null);
|
||||
}
|
||||
});
|
||||
|
||||
//add ui componenets to JFXPanels
|
||||
jFXViewPanel.setScene(scene);
|
||||
jFXstatusPanel.setScene(new Scene(new StatusBar(controller)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Mode> availableModes(List<Mode> modes) {
|
||||
return Collections.emptyList();
|
||||
@ -165,12 +295,12 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
jFXstatusPanel = new JFXPanel();
|
||||
jFXstatusPanel = new javafx.embed.swing.JFXPanel();
|
||||
splitYPane = new javax.swing.JSplitPane();
|
||||
jFXVizPanel = new JFXPanel();
|
||||
lowerSplitXPane = new javax.swing.JSplitPane();
|
||||
resultContainerPanel = new javax.swing.JPanel();
|
||||
contentViewerContainerPanel = new javax.swing.JPanel();
|
||||
jFXViewPanel = new javafx.embed.swing.JFXPanel();
|
||||
horizontalSplitPane = new javax.swing.JSplitPane();
|
||||
leftFillerPanel = new javax.swing.JPanel();
|
||||
rightfillerPanel = new javax.swing.JPanel();
|
||||
|
||||
jFXstatusPanel.setPreferredSize(new java.awt.Dimension(100, 16));
|
||||
|
||||
@ -178,32 +308,47 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
splitYPane.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
|
||||
splitYPane.setResizeWeight(0.9);
|
||||
splitYPane.setPreferredSize(new java.awt.Dimension(1024, 400));
|
||||
splitYPane.setLeftComponent(jFXVizPanel);
|
||||
splitYPane.setLeftComponent(jFXViewPanel);
|
||||
|
||||
lowerSplitXPane.setDividerLocation(600);
|
||||
lowerSplitXPane.setResizeWeight(0.5);
|
||||
lowerSplitXPane.setPreferredSize(new java.awt.Dimension(1200, 300));
|
||||
lowerSplitXPane.setRequestFocusEnabled(false);
|
||||
horizontalSplitPane.setDividerLocation(600);
|
||||
horizontalSplitPane.setResizeWeight(0.5);
|
||||
horizontalSplitPane.setPreferredSize(new java.awt.Dimension(1200, 300));
|
||||
horizontalSplitPane.setRequestFocusEnabled(false);
|
||||
|
||||
resultContainerPanel.setPreferredSize(new java.awt.Dimension(700, 300));
|
||||
resultContainerPanel.setLayout(new java.awt.BorderLayout());
|
||||
lowerSplitXPane.setLeftComponent(resultContainerPanel);
|
||||
javax.swing.GroupLayout leftFillerPanelLayout = new javax.swing.GroupLayout(leftFillerPanel);
|
||||
leftFillerPanel.setLayout(leftFillerPanelLayout);
|
||||
leftFillerPanelLayout.setHorizontalGroup(
|
||||
leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 599, Short.MAX_VALUE)
|
||||
);
|
||||
leftFillerPanelLayout.setVerticalGroup(
|
||||
leftFillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 54, Short.MAX_VALUE)
|
||||
);
|
||||
|
||||
contentViewerContainerPanel.setPreferredSize(new java.awt.Dimension(500, 300));
|
||||
contentViewerContainerPanel.setLayout(new java.awt.BorderLayout());
|
||||
lowerSplitXPane.setRightComponent(contentViewerContainerPanel);
|
||||
horizontalSplitPane.setLeftComponent(leftFillerPanel);
|
||||
|
||||
splitYPane.setRightComponent(lowerSplitXPane);
|
||||
javax.swing.GroupLayout rightfillerPanelLayout = new javax.swing.GroupLayout(rightfillerPanel);
|
||||
rightfillerPanel.setLayout(rightfillerPanelLayout);
|
||||
rightfillerPanelLayout.setHorizontalGroup(
|
||||
rightfillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 364, Short.MAX_VALUE)
|
||||
);
|
||||
rightfillerPanelLayout.setVerticalGroup(
|
||||
rightfillerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 54, Short.MAX_VALUE)
|
||||
);
|
||||
|
||||
horizontalSplitPane.setRightComponent(rightfillerPanel);
|
||||
|
||||
splitYPane.setRightComponent(horizontalSplitPane);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(splitYPane, javax.swing.GroupLayout.DEFAULT_SIZE, 972, Short.MAX_VALUE)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(0, 0, 0)
|
||||
.addComponent(jFXstatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGap(0, 0, 0))
|
||||
.addComponent(jFXstatusPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
@ -215,11 +360,11 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JPanel contentViewerContainerPanel;
|
||||
private javafx.embed.swing.JFXPanel jFXVizPanel;
|
||||
private javax.swing.JSplitPane horizontalSplitPane;
|
||||
private javafx.embed.swing.JFXPanel jFXViewPanel;
|
||||
private javafx.embed.swing.JFXPanel jFXstatusPanel;
|
||||
private javax.swing.JSplitPane lowerSplitXPane;
|
||||
private javax.swing.JPanel resultContainerPanel;
|
||||
private javax.swing.JPanel leftFillerPanel;
|
||||
private javax.swing.JPanel rightfillerPanel;
|
||||
private javax.swing.JSplitPane splitYPane;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
@ -229,25 +374,34 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer
|
||||
putClientProperty(PROP_UNDOCKING_DISABLED, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentClosed() {
|
||||
// TODO add custom code on component closing
|
||||
}
|
||||
|
||||
void writeProperties(java.util.Properties p) {
|
||||
// better to version settings since initial version as advocated at
|
||||
// http://wiki.apidesign.org/wiki/PropertyFiles
|
||||
p.setProperty("version", "1.0");
|
||||
// TODO store your settings
|
||||
}
|
||||
|
||||
void readProperties(java.util.Properties p) {
|
||||
String version = p.getProperty("version");
|
||||
// TODO read your settings according to their version
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExplorerManager getExplorerManager() {
|
||||
return em;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string that should be used as the label above the result table.
|
||||
* It displays the time range spanned by the selected events.
|
||||
*
|
||||
* @return A String representation of all the events displayed.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"# {0} - start of date range",
|
||||
"# {1} - end of date range",
|
||||
"TimeLineResultView.startDateToEndDate.text={0} to {1}"})
|
||||
private String getResultViewerSummaryString() {
|
||||
Interval selectedTimeRange = controller.getSelectedTimeRange();
|
||||
if (selectedTimeRange == null) {
|
||||
return "";
|
||||
} else {
|
||||
final DateTimeFormatter zonedFormatter = TimeLineController.getZonedFormatter();
|
||||
String start = selectedTimeRange.getStart()
|
||||
.withZone(TimeLineController.getJodaTimeZone())
|
||||
.toString(zonedFormatter);
|
||||
String end = selectedTimeRange.getEnd()
|
||||
.withZone(TimeLineController.getJodaTimeZone())
|
||||
.toString(zonedFormatter);
|
||||
return Bundle.TimeLineResultView_startDateToEndDate_text(start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,9 @@ package org.sleuthkit.autopsy.timeline;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public enum VisualizationMode {
|
||||
public enum ViewMode {
|
||||
|
||||
COUNTS, DETAIL;
|
||||
COUNTS,
|
||||
DETAIL,
|
||||
LIST;
|
||||
}
|
@ -71,7 +71,7 @@ public class SaveSnapshotAsReport extends Action {
|
||||
"Timeline.ModuleName=Timeline",
|
||||
"SaveSnapShotAsReport.action.dialogs.title=Timeline",
|
||||
"SaveSnapShotAsReport.action.name.text=Snapshot Report",
|
||||
"SaveSnapShotAsReport.action.longText=Save a screen capture of the visualization as a report.",
|
||||
"SaveSnapShotAsReport.action.longText=Save a screen capture of the current view of the timeline as a report.",
|
||||
"# {0} - report file path",
|
||||
"SaveSnapShotAsReport.ReportSavedAt=Report saved at [{0}]",
|
||||
"SaveSnapShotAsReport.Success=Success",
|
||||
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.datamodel;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
|
||||
/**
|
||||
* A container for several events that have the same timestamp and description
|
||||
* and are backed by the same file. Used in the ListView to coalesce the file
|
||||
* system events for a file when they have the same timestamp.
|
||||
*/
|
||||
public class CombinedEvent {
|
||||
|
||||
private final long fileID;
|
||||
private final long epochMillis;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* A map from EventType to event ID.
|
||||
*/
|
||||
private final Map<EventType, Long> eventTypeMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param epochMillis The timestamp for this event, in millis from the Unix
|
||||
* epoch.
|
||||
* @param description The full description shared by all the combined events
|
||||
* @param fileID The ID of the file shared by all the combined events.
|
||||
* @param eventMap A map from EventType to event ID.
|
||||
*/
|
||||
public CombinedEvent(long epochMillis, String description, long fileID, Map<EventType, Long> eventMap) {
|
||||
this.epochMillis = epochMillis;
|
||||
this.description = description;
|
||||
eventTypeMap.putAll(eventMap);
|
||||
this.fileID = fileID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp of this event as millis from the Unix epoch.
|
||||
*
|
||||
* @return The timestamp of this event as millis from the Unix epoch.
|
||||
*/
|
||||
public long getStartMillis() {
|
||||
return epochMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full description shared by all the combined events.
|
||||
*
|
||||
* @return The full description shared by all the combined events.
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the obj ID of the file shared by the combined events.
|
||||
*
|
||||
* @return The obj ID of the file shared by the combined events.
|
||||
*/
|
||||
public long getFileID() {
|
||||
return fileID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the types of the combined events.
|
||||
*
|
||||
* @return The types of the combined events.
|
||||
*/
|
||||
public Set<EventType> getEventTypes() {
|
||||
return eventTypeMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event IDs of the combined events.
|
||||
*
|
||||
* @return The event IDs of the combined events.
|
||||
*/
|
||||
public Collection<Long> getEventIDs() {
|
||||
return eventTypeMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event ID of one event that is representative of all the combined
|
||||
* events. It can be used to look up a SingleEvent with more details, for
|
||||
* example.
|
||||
*
|
||||
* @return An arbitrary representative event ID for the combined events.
|
||||
*/
|
||||
public Long getRepresentativeEventID() {
|
||||
return eventTypeMap.values().stream().findFirst().get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 53 * hash + (int) (this.fileID ^ (this.fileID >>> 32));
|
||||
hash = 53 * hash + (int) (this.epochMillis ^ (this.epochMillis >>> 32));
|
||||
hash = 53 * hash + Objects.hashCode(this.description);
|
||||
hash = 53 * hash + Objects.hashCode(this.eventTypeMap);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final CombinedEvent other = (CombinedEvent) obj;
|
||||
if (this.fileID != other.fileID) {
|
||||
return false;
|
||||
}
|
||||
if (this.epochMillis != other.epochMillis) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.description, other.description)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.eventTypeMap, other.eventTypeMap)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -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");
|
||||
@ -21,10 +21,10 @@ package org.sleuthkit.autopsy.timeline.datamodel;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import org.joda.time.Interval;
|
||||
@ -108,8 +108,8 @@ public class EventCluster implements MultiEvent<EventStripe> {
|
||||
*/
|
||||
private final ImmutableSet<Long> hashHits;
|
||||
|
||||
private EventCluster(Interval spanningInterval, EventType type, Set<Long> eventIDs,
|
||||
Set<Long> hashHits, Set<Long> tagged, String description, DescriptionLoD lod,
|
||||
private EventCluster(Interval spanningInterval, EventType type, Collection<Long> eventIDs,
|
||||
Collection<Long> hashHits, Collection<Long> tagged, String description, DescriptionLoD lod,
|
||||
EventStripe parent) {
|
||||
|
||||
this.span = spanningInterval;
|
||||
@ -122,8 +122,8 @@ public class EventCluster implements MultiEvent<EventStripe> {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public EventCluster(Interval spanningInterval, EventType type, Set<Long> eventIDs,
|
||||
Set<Long> hashHits, Set<Long> tagged, String description, DescriptionLoD lod) {
|
||||
public EventCluster(Interval spanningInterval, EventType type, Collection<Long> eventIDs,
|
||||
Collection<Long> hashHits, Collection<Long> tagged, String description, DescriptionLoD lod) {
|
||||
this(spanningInterval, type, eventIDs, hashHits, tagged, description, lod, null);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014-15 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -70,15 +70,15 @@ import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* This class acts as the model for a {@link TimeLineView}
|
||||
* This class acts as the model for a TimelineView
|
||||
*
|
||||
* Views can register listeners on properties returned by methods.
|
||||
*
|
||||
* This class is implemented as a filtered view into an underlying
|
||||
* {@link EventsRepository}.
|
||||
* EventsRepository.
|
||||
*
|
||||
* TODO: as many methods as possible should cache their results so as to avoid
|
||||
* unnecessary db calls through the {@link EventsRepository} -jm
|
||||
* unnecessary db calls through the EventsRepository -jm
|
||||
*
|
||||
* Concurrency Policy: repo is internally synchronized, so methods that only
|
||||
* access the repo atomically do not need further synchronization
|
||||
@ -279,7 +279,7 @@ public final class FilteredEventsModel {
|
||||
return repo.getTagCountsByTagName(eventIDsWithTags);
|
||||
}
|
||||
|
||||
public Set<Long> getEventIDs(Interval timeRange, Filter filter) {
|
||||
public List<Long> getEventIDs(Interval timeRange, Filter filter) {
|
||||
final Interval overlap;
|
||||
final RootFilter intersect;
|
||||
synchronized (this) {
|
||||
@ -290,6 +290,21 @@ public final class FilteredEventsModel {
|
||||
return repo.getEventIDs(overlap, intersect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a representation of all the events, within the given time range, that
|
||||
* pass the given filter, grouped by time and description such that file
|
||||
* system events for the same file, with the same timestamp, are combined
|
||||
* together.
|
||||
*
|
||||
* @param timeRange The Interval that all returned events must be within.
|
||||
* @param filter The Filter that all returned events must pass.
|
||||
*
|
||||
* @return A List of combined events, sorted by timestamp.
|
||||
*/
|
||||
public List<CombinedEvent> getCombinedEvents() {
|
||||
return repo.getCombinedEvents(requestedTimeRange.get(), requestedFilter.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* return the number of events that pass the requested filter and are within
|
||||
* the given time range.
|
||||
|
@ -56,6 +56,7 @@ import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.Version;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventCluster;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
@ -343,18 +344,29 @@ public class EventDB {
|
||||
return result;
|
||||
}
|
||||
|
||||
Set<Long> getEventIDs(Interval timeRange, RootFilter filter) {
|
||||
return getEventIDs(timeRange.getStartMillis() / 1000, timeRange.getEndMillis() / 1000, filter);
|
||||
}
|
||||
/**
|
||||
* Get the IDs of all the events within the given time range that pass the
|
||||
* given filter.
|
||||
*
|
||||
* @param timeRange The Interval that all returned events must be within.
|
||||
* @param filter The Filter that all returned events must pass.
|
||||
*
|
||||
* @return A List of event ids, sorted by timestamp of the corresponding
|
||||
* event..
|
||||
*/
|
||||
List<Long> getEventIDs(Interval timeRange, RootFilter filter) {
|
||||
Long startTime = timeRange.getStartMillis() / 1000;
|
||||
Long endTime = timeRange.getEndMillis() / 1000;
|
||||
|
||||
Set<Long> getEventIDs(Long startTime, Long endTime, RootFilter filter) {
|
||||
if (Objects.equals(startTime, endTime)) {
|
||||
endTime++;
|
||||
endTime++; //make sure end is at least 1 millisecond after start
|
||||
}
|
||||
Set<Long> resultIDs = new HashSet<>();
|
||||
|
||||
ArrayList<Long> resultIDs = new ArrayList<>();
|
||||
|
||||
DBLock.lock();
|
||||
final String query = "SELECT events.event_id AS event_id FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + SQLHelper.getSQLWhere(filter); // NON-NLS
|
||||
final String query = "SELECT events.event_id AS event_id FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter)
|
||||
+ " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + SQLHelper.getSQLWhere(filter) + " ORDER BY time ASC"; // NON-NLS
|
||||
try (Statement stmt = con.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(query)) {
|
||||
while (rs.next()) {
|
||||
@ -370,6 +382,55 @@ public class EventDB {
|
||||
return resultIDs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a representation of all the events, within the given time range, that
|
||||
* pass the given filter, grouped by time and description such that file
|
||||
* system events for the same file, with the same timestamp, are combined
|
||||
* together.
|
||||
*
|
||||
* @param timeRange The Interval that all returned events must be within.
|
||||
* @param filter The Filter that all returned events must pass.
|
||||
*
|
||||
* @return A List of combined events, sorted by timestamp.
|
||||
*/
|
||||
List<CombinedEvent> getCombinedEvents(Interval timeRange, RootFilter filter) {
|
||||
Long startTime = timeRange.getStartMillis() / 1000;
|
||||
Long endTime = timeRange.getEndMillis() / 1000;
|
||||
|
||||
if (Objects.equals(startTime, endTime)) {
|
||||
endTime++; //make sure end is at least 1 millisecond after start
|
||||
}
|
||||
|
||||
ArrayList<CombinedEvent> results = new ArrayList<>();
|
||||
|
||||
DBLock.lock();
|
||||
final String query = "SELECT full_description, time, file_id, GROUP_CONCAT(events.event_id), GROUP_CONCAT(sub_type)"
|
||||
+ " FROM events " + useHashHitTablesHelper(filter) + useTagTablesHelper(filter)
|
||||
+ " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + SQLHelper.getSQLWhere(filter)
|
||||
+ " GROUP BY time,full_description, file_id ORDER BY time ASC, full_description";
|
||||
try (Statement stmt = con.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(query)) {
|
||||
while (rs.next()) {
|
||||
|
||||
//make a map from event type to event ID
|
||||
List<Long> eventIDs = SQLHelper.unGroupConcat(rs.getString("GROUP_CONCAT(events.event_id)"), Long::valueOf);
|
||||
List<EventType> eventTypes = SQLHelper.unGroupConcat(rs.getString("GROUP_CONCAT(sub_type)"), s -> RootEventType.allTypes.get(Integer.valueOf(s)));
|
||||
Map<EventType, Long> eventMap = new HashMap<>();
|
||||
for (int i = 0; i < eventIDs.size(); i++) {
|
||||
eventMap.put(eventTypes.get(i), eventIDs.get(i));
|
||||
}
|
||||
results.add(new CombinedEvent(rs.getLong("time") * 1000, rs.getString("full_description"), rs.getLong("file_id"), eventMap));
|
||||
}
|
||||
|
||||
} catch (SQLException sqlEx) {
|
||||
LOGGER.log(Level.SEVERE, "failed to execute query for combined events", sqlEx); // NON-NLS
|
||||
} finally {
|
||||
DBLock.unlock();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* this relies on the fact that no tskObj has ID 0 but 0 is the default
|
||||
* value for the datasource_id column in the events table.
|
||||
@ -583,7 +644,14 @@ public class EventDB {
|
||||
insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, event_id) values (?,?)"); //NON-NLS
|
||||
insertTagStmt = prepareStatement("INSERT OR IGNORE INTO tags (tag_id, tag_name_id,tag_name_display_name, event_id) values (?,?,?,?)"); //NON-NLS
|
||||
deleteTagStmt = prepareStatement("DELETE FROM tags WHERE tag_id = ?"); //NON-NLS
|
||||
countAllEventsStmt = prepareStatement("SELECT count(*) AS count FROM events"); //NON-NLS
|
||||
|
||||
/*
|
||||
* This SQL query is really just a select count(*), but that has
|
||||
* performance problems on very large tables unless you include
|
||||
* a where clause see http://stackoverflow.com/a/9338276/4004683
|
||||
* for more.
|
||||
*/
|
||||
countAllEventsStmt = prepareStatement("SELECT count(event_id) AS count FROM events WHERE event_id IS NOT null"); //NON-NLS
|
||||
dropEventsTableStmt = prepareStatement("DROP TABLE IF EXISTS events"); //NON-NLS
|
||||
dropHashSetHitsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_set_hits"); //NON-NLS
|
||||
dropHashSetsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_sets"); //NON-NLS
|
||||
@ -1090,12 +1158,12 @@ public class EventDB {
|
||||
private EventCluster eventClusterHelper(ResultSet rs, boolean useSubTypes, DescriptionLoD descriptionLOD, TagsFilter filter) throws SQLException {
|
||||
Interval interval = new Interval(rs.getLong("min(time)") * 1000, rs.getLong("max(time)") * 1000, TimeLineController.getJodaTimeZone());// NON-NLS
|
||||
String eventIDsString = rs.getString("event_ids");// NON-NLS
|
||||
Set<Long> eventIDs = SQLHelper.unGroupConcat(eventIDsString, Long::valueOf);
|
||||
List<Long> eventIDs = SQLHelper.unGroupConcat(eventIDsString, Long::valueOf);
|
||||
String description = rs.getString(SQLHelper.getDescriptionColumn(descriptionLOD));
|
||||
EventType type = useSubTypes ? RootEventType.allTypes.get(rs.getInt("sub_type")) : BaseTypes.values()[rs.getInt("base_type")];// NON-NLS
|
||||
|
||||
Set<Long> hashHits = SQLHelper.unGroupConcat(rs.getString("hash_hits"), Long::valueOf); //NON-NLS
|
||||
Set<Long> tagged = SQLHelper.unGroupConcat(rs.getString("taggeds"), Long::valueOf); //NON-NLS
|
||||
List<Long> hashHits = SQLHelper.unGroupConcat(rs.getString("hash_hits"), Long::valueOf); //NON-NLS
|
||||
List<Long> tagged = SQLHelper.unGroupConcat(rs.getString("taggeds"), Long::valueOf); //NON-NLS
|
||||
|
||||
return new EventCluster(interval, type, eventIDs, hashHits, tagged, description, descriptionLOD);
|
||||
}
|
||||
@ -1168,7 +1236,6 @@ public class EventDB {
|
||||
return useSubTypes ? "sub_type" : "base_type"; //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
private PreparedStatement prepareStatement(String queryString) throws SQLException {
|
||||
PreparedStatement prepareStatement = con.prepareStatement(queryString);
|
||||
preparedStatements.add(prepareStatement);
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -56,6 +56,7 @@ import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.timeline.CancellationProgressTask;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
@ -214,10 +215,25 @@ public class EventsRepository {
|
||||
idToEventCache.invalidateAll();
|
||||
}
|
||||
|
||||
public Set<Long> getEventIDs(Interval timeRange, RootFilter filter) {
|
||||
public List<Long> getEventIDs(Interval timeRange, RootFilter filter) {
|
||||
return eventDB.getEventIDs(timeRange, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a representation of all the events, within the given time range, that
|
||||
* pass the given filter, grouped by time and description such that file
|
||||
* system events for the same file, with the same timestamp, are combined
|
||||
* together.
|
||||
*
|
||||
* @param timeRange The Interval that all returned events must be within.
|
||||
* @param filter The Filter that all returned events must pass.
|
||||
*
|
||||
* @return A List of combined events, sorted by timestamp.
|
||||
*/
|
||||
public List<CombinedEvent> getCombinedEvents(Interval timeRange, RootFilter filter) {
|
||||
return eventDB.getCombinedEvents(timeRange, filter);
|
||||
}
|
||||
|
||||
public Interval getSpanningInterval(Collection<Long> eventIDs) {
|
||||
return eventDB.getSpanningInterval(eventIDs);
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.timeline.db;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@ -82,11 +81,11 @@ class SQLHelper {
|
||||
* @return a Set of X, each element mapped from one element of the original
|
||||
* comma delimited string
|
||||
*/
|
||||
static <X> Set<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
|
||||
return StringUtils.isBlank(groupConcat) ? Collections.emptySet()
|
||||
static <X> List<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
|
||||
return StringUtils.isBlank(groupConcat) ? Collections.emptyList()
|
||||
: Stream.of(groupConcat.split(","))
|
||||
.map(mapper::apply)
|
||||
.collect(Collectors.toSet());
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.timeline.events;
|
||||
|
||||
/**
|
||||
* A "local" event published by filteredEventsModel to indicate that the user
|
||||
* requested that the current visualization be refreshed with out changing any
|
||||
* requested that the current view be refreshed with out changing any
|
||||
* of the parameters ( to include more up to date tag data for example.)
|
||||
*
|
||||
* This event is not intended for use out side of the Timeline module.
|
||||
|
@ -1 +1 @@
|
||||
EventRoodNode.tooManyNode.displayName=\u8868\u793A\u3059\u308B\u30A4\u30D9\u30F3\u30C8\u6570\u304C\u591A\u3059\u304E\u307E\u3059\u3002\u6700\u5927 \= {0}\u3002\u8868\u793A\u3059\u308B\u30A4\u30D9\u30F3\u30C8\u306F{1}\u3042\u308A\u307E\u3059\u3002
|
||||
EventRoodNode.tooManyNode.displayName=\u8868\u793a\u3059\u308b\u30a4\u30d9\u30f3\u30c8\u6570\u304c\u591a\u3059\u304e\u307e\u3059\u3002\u6700\u5927 \= {0}\u3002\u8868\u793a\u3059\u308b\u30a4\u30d9\u30f3\u30c8\u306f{1}\u3042\u308a\u307e\u3059\u3002
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2014 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -23,47 +23,60 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javafx.beans.Observable;
|
||||
import javax.swing.Action;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.openide.nodes.Children;
|
||||
import org.openide.nodes.PropertySupport;
|
||||
import org.openide.nodes.Sheet;
|
||||
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.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.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.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* * Explorer Node for {@link SingleEvent}s.
|
||||
* * Explorer Node for a SingleEvent.
|
||||
*/
|
||||
class EventNode extends DisplayableItemNode {
|
||||
public class EventNode extends DisplayableItemNode {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(EventNode.class.getName());
|
||||
|
||||
private final SingleEvent e;
|
||||
private final SingleEvent event;
|
||||
|
||||
EventNode(SingleEvent eventById, AbstractFile file, BlackboardArtifact artifact) {
|
||||
super(Children.LEAF, Lookups.fixed(eventById, file, artifact));
|
||||
this.e = eventById;
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + e.getEventType().getIconBase()); // NON-NLS
|
||||
EventNode(SingleEvent event, AbstractFile file, BlackboardArtifact artifact) {
|
||||
super(Children.LEAF, Lookups.fixed(event, file, artifact));
|
||||
this.event = event;
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + event.getEventType().getIconBase()); // NON-NLS
|
||||
}
|
||||
|
||||
EventNode(SingleEvent eventById, AbstractFile file) {
|
||||
super(Children.LEAF, Lookups.fixed(eventById, file));
|
||||
this.e = eventById;
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + e.getEventType().getIconBase()); // NON-NLS
|
||||
EventNode(SingleEvent event, AbstractFile file) {
|
||||
super(Children.LEAF, Lookups.fixed(event, file));
|
||||
this.event = event;
|
||||
this.setIconBaseWithExtension("org/sleuthkit/autopsy/timeline/images/" + event.getEventType().getIconBase()); // NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
@NbBundle.Messages({
|
||||
"NodeProperty.displayName.icon=Icon",
|
||||
"NodeProperty.displayName.description=Description",
|
||||
"NodeProperty.displayName.baseType=Base Type",
|
||||
"NodeProperty.displayName.subType=Sub Type",
|
||||
"NodeProperty.displayName.known=Known",
|
||||
"NodeProperty.displayName.dateTime=Date/Time"})
|
||||
protected Sheet createSheet() {
|
||||
Sheet s = super.createSheet();
|
||||
Sheet.Set properties = s.get(Sheet.PROPERTIES);
|
||||
@ -72,28 +85,25 @@ class EventNode extends DisplayableItemNode {
|
||||
s.put(properties);
|
||||
}
|
||||
|
||||
final TimeProperty timePropery = new TimeProperty("time", "Date/Time", "time ", getDateTimeString()); // NON-NLS
|
||||
|
||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
||||
try {
|
||||
timePropery.setValue(getDateTimeString());
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
LOGGER.log(Level.SEVERE, "unexpected error setting date/time property on EventNode explorer node", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
|
||||
properties.put(new NodeProperty<>("icon", "Icon", "icon", true)); // NON-NLS //gets overridden with icon
|
||||
properties.put(timePropery);
|
||||
properties.put(new NodeProperty<>("description", "Description", "description", e.getFullDescription())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("eventBaseType", "Base Type", "base type", e.getEventType().getSuperType().getDisplayName())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("eventSubType", "Sub Type", "sub type", e.getEventType().getDisplayName())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("Known", "Known", "known", e.getKnown().toString())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("icon", Bundle.NodeProperty_displayName_icon(), "icon", true)); // NON-NLS //gets overridden with icon
|
||||
properties.put(new TimeProperty("time", Bundle.NodeProperty_displayName_dateTime(), "time ", getDateTimeString()));// NON-NLS
|
||||
properties.put(new NodeProperty<>("description", Bundle.NodeProperty_displayName_description(), "description", event.getFullDescription())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("eventBaseType", Bundle.NodeProperty_displayName_baseType(), "base type", event.getEventType().getSuperType().getDisplayName())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("eventSubType", Bundle.NodeProperty_displayName_subType(), "sub type", event.getEventType().getDisplayName())); // NON-NLS
|
||||
properties.put(new NodeProperty<>("Known", Bundle.NodeProperty_displayName_known(), "known", event.getKnown().toString())); // NON-NLS
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time of this event as a String formated according to the
|
||||
* controller's time zone setting.
|
||||
*
|
||||
* @return The time of this event as a String formated according to the
|
||||
* controller's time zone setting.
|
||||
*/
|
||||
private String getDateTimeString() {
|
||||
return new DateTime(e.getStartMillis(), DateTimeZone.UTC).toString(TimeLineController.getZonedFormatter());
|
||||
return new DateTime(event.getStartMillis(), DateTimeZone.UTC).toString(TimeLineController.getZonedFormatter());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,7 +128,7 @@ class EventNode extends DisplayableItemNode {
|
||||
|
||||
@Override
|
||||
public <T> T accept(DisplayableItemNodeVisitor<T> dinv) {
|
||||
throw new UnsupportedOperationException("Not supported yet."); // NON-NLS //To change body of generated methods, choose Tools | Templates.
|
||||
throw new UnsupportedOperationException("Not supported yet."); // NON-NLS
|
||||
}
|
||||
|
||||
/*
|
||||
@ -134,7 +144,7 @@ class EventNode extends DisplayableItemNode {
|
||||
* We use TimeProperty instead of a normal NodeProperty to correctly display
|
||||
* the date/time when the user changes the timezone setting.
|
||||
*/
|
||||
private class TimeProperty extends PropertySupport.ReadWrite<String> {
|
||||
final private class TimeProperty extends PropertySupport.ReadWrite<String> {
|
||||
|
||||
private String value;
|
||||
|
||||
@ -147,6 +157,14 @@ class EventNode extends DisplayableItemNode {
|
||||
super(name, String.class, displayName, shortDescription);
|
||||
setValue("suppressCustomEditor", Boolean.TRUE); // remove the "..." (editing) button NON-NLS
|
||||
this.value = value;
|
||||
TimeLineController.getTimeZone().addListener(timeZone -> {
|
||||
try {
|
||||
setValue(getDateTimeString());
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unexpected error setting date/time property on EventNode explorer node", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -161,4 +179,32 @@ class EventNode extends DisplayableItemNode {
|
||||
firePropertyChange("time", oldValue, t); // NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create an EventNode from the event ID and the events
|
||||
* model.
|
||||
*
|
||||
* @param eventID The ID of the event this node is for.
|
||||
* @param eventsModel The model that provides access to the events DB.
|
||||
*
|
||||
* @return An EventNode with the file (and artifact) backing this event in
|
||||
* its lookup.
|
||||
*/
|
||||
public static EventNode createEventNode(final Long eventID, FilteredEventsModel eventsModel) throws TskCoreException, IllegalStateException {
|
||||
/*
|
||||
* Look up the event by id and creata an EventNode with the appropriate
|
||||
* data in the lookup.
|
||||
*/
|
||||
final SingleEvent eventById = eventsModel.getEventById(eventID);
|
||||
|
||||
SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
|
||||
AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID());
|
||||
|
||||
if (eventById.getArtifactID().isPresent()) {
|
||||
BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get());
|
||||
return new EventNode(eventById, file, blackboardArtifact);
|
||||
} else {
|
||||
return new EventNode(eventById, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,22 +27,19 @@ import org.openide.nodes.Children;
|
||||
import org.openide.nodes.Node;
|
||||
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.datamodel.DisplayableItemNode;
|
||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
|
||||
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.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Root Explorer node to represent events.
|
||||
* Root Explorer Node to represent events.
|
||||
*/
|
||||
public class EventRootNode extends DisplayableItemNode {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Since the lazy loading seems to be broken if there are more than this
|
||||
* many child events, we don't show them and just show a message showing the
|
||||
@ -50,18 +47,8 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
*/
|
||||
public static final int MAX_EVENTS_TO_DISPLAY = 5000;
|
||||
|
||||
/**
|
||||
* the number of child events
|
||||
*/
|
||||
private final int childCount;
|
||||
|
||||
public EventRootNode(String NAME, Collection<Long> fileIds, FilteredEventsModel filteredEvents) {
|
||||
public EventRootNode(Collection<Long> fileIds, FilteredEventsModel filteredEvents) {
|
||||
super(Children.create(new EventNodeChildFactory(fileIds, filteredEvents), true), Lookups.singleton(fileIds));
|
||||
|
||||
super.setName(NAME);
|
||||
super.setDisplayName(NAME);
|
||||
|
||||
childCount = fileIds.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -74,9 +61,6 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getChildCount() {
|
||||
return childCount;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO (AUT-1849): Correct or remove peristent column reordering code
|
||||
@ -95,12 +79,12 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
private static final Logger LOGGER = Logger.getLogger(EventNodeChildFactory.class.getName());
|
||||
|
||||
/**
|
||||
* list of event ids that act as keys for the child nodes.
|
||||
* List of event IDs that act as keys for the child nodes.
|
||||
*/
|
||||
private final Collection<Long> eventIDs;
|
||||
|
||||
/**
|
||||
* filteredEvents is used to lookup the events from their ids
|
||||
* filteredEvents is used to lookup the events from their IDs
|
||||
*/
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
@ -112,8 +96,8 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
@Override
|
||||
protected boolean createKeys(List<Long> toPopulate) {
|
||||
/**
|
||||
* if there are too many events, just add one id (-1) to indicate
|
||||
* this.
|
||||
* If there are too many events, just add one dummy ID (-1) to
|
||||
* indicate this.
|
||||
*/
|
||||
if (eventIDs.size() < MAX_EVENTS_TO_DISPLAY) {
|
||||
toPopulate.addAll(eventIDs);
|
||||
@ -127,34 +111,24 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
protected Node createNodeForKey(Long eventID) {
|
||||
if (eventID < 0) {
|
||||
/*
|
||||
* if the eventId is a the special value, return a node with a
|
||||
* warning that their are too many evens
|
||||
* If the eventId is a the special value ( -1 ), return a node
|
||||
* with a warning that their are too many evens
|
||||
*/
|
||||
return new TooManyNode(eventIDs.size());
|
||||
} else {
|
||||
/*
|
||||
* look up the event by id and creata an EventNode with the
|
||||
* appropriate data in the lookup.
|
||||
*/
|
||||
final SingleEvent eventById = filteredEvents.getEventById(eventID);
|
||||
try {
|
||||
SleuthkitCase sleuthkitCase = Case.getCurrentCase().getSleuthkitCase();
|
||||
AbstractFile file = sleuthkitCase.getAbstractFileById(eventById.getFileID());
|
||||
if (file != null) {
|
||||
if (eventById.getArtifactID().isPresent()) {
|
||||
BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(eventById.getArtifactID().get());
|
||||
return new EventNode(eventById, file, blackboardArtifact);
|
||||
} else {
|
||||
return new EventNode(eventById, file);
|
||||
}
|
||||
} else {
|
||||
//This should never happen in normal operations
|
||||
LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent."); // NON-NLS
|
||||
return null;
|
||||
}
|
||||
} catch (IllegalStateException | TskCoreException ex) {
|
||||
//if some how the case was closed or ther is another unspecified exception, just bail out with a warning.
|
||||
LOGGER.log(Level.WARNING, "Failed to lookup sleuthkit object backing TimeLineEvent.", ex); // NON-NLS
|
||||
return EventNode.createEventNode(eventID, filteredEvents);
|
||||
} catch (IllegalStateException ex) {
|
||||
//Since the case is closed, the user probably doesn't care about this, just log it as a precaution.
|
||||
LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
return null;
|
||||
} catch (TskCoreException ex) {
|
||||
/*
|
||||
* Just log it: There might be lots of these errors, and we
|
||||
* don't want to flood the user with notifications. It will
|
||||
* be obvious the UI is broken anyways
|
||||
*/
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); // NON-NLS
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -162,8 +136,7 @@ public class EventRootNode extends DisplayableItemNode {
|
||||
}
|
||||
|
||||
/**
|
||||
* A Node that just shows a warning message that their are too many events
|
||||
* to show
|
||||
* A Node with a warning message that their are too many events to show.
|
||||
*/
|
||||
private static class TooManyNode extends AbstractNode {
|
||||
|
||||
|
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/table.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/table.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 920 B |
@ -70,7 +70,7 @@ public class SnapShotReportWriter {
|
||||
* @param zoomParams The ZoomParams in effect when the snapshot was
|
||||
* taken.
|
||||
* @param generationDate The generation Date of the report.
|
||||
* @param snapshot A snapshot of the visualization to include in the
|
||||
* @param snapshot A snapshot of the view to include in the
|
||||
* report.
|
||||
*/
|
||||
public SnapShotReportWriter(Case currentCase, Path reportFolderPath, String reportName, ZoomParams zoomParams, Date generationDate, BufferedImage snapshot) {
|
||||
|
@ -0,0 +1,372 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.ui;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.controlsfx.control.MaskerPane;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||
|
||||
public abstract class AbstractTimeLineView extends BorderPane {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractTimeLineView.class.getName());
|
||||
|
||||
/**
|
||||
* Boolean property that holds true if the view does not show any events
|
||||
* with the current zoom and filter settings.
|
||||
*/
|
||||
private final ReadOnlyBooleanWrapper hasVisibleEvents = new ReadOnlyBooleanWrapper(true);
|
||||
|
||||
/**
|
||||
* Boolean property that holds true if the view may not represent the
|
||||
* current state of the DB, because, for example, tags have been updated but
|
||||
* the view. was not refreshed.
|
||||
*/
|
||||
private final ReadOnlyBooleanWrapper outOfDate = new ReadOnlyBooleanWrapper(false);
|
||||
|
||||
/**
|
||||
* List of Nodes to insert into the toolbar. This should be set in an
|
||||
* implementation's constructor.
|
||||
*/
|
||||
private List<Node> settingsNodes;
|
||||
|
||||
/**
|
||||
* Listener that is attached to various properties that should trigger a
|
||||
* view update when they change.
|
||||
*/
|
||||
private InvalidationListener updateListener = (Observable any) -> refresh();
|
||||
|
||||
/**
|
||||
* Task used to reload the content of this view
|
||||
*/
|
||||
private Task<Boolean> updateTask;
|
||||
|
||||
private final TimeLineController controller;
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller
|
||||
*/
|
||||
public AbstractTimeLineView(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
this.filteredEvents.registerForEvents(this);
|
||||
this.filteredEvents.zoomParametersProperty().addListener(updateListener);
|
||||
TimeLineController.getTimeZone().addListener(updateListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a RefreshRequestedEvent from the events model by updating the
|
||||
* view.
|
||||
*
|
||||
* @param event The RefreshRequestedEvent to handle.
|
||||
*/
|
||||
@Subscribe
|
||||
public void handleRefreshRequested(RefreshRequestedEvent event) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the view represent an out-of-date state of the DB. It might if, for
|
||||
* example, tags have been updated but the view was not refreshed.
|
||||
*
|
||||
* @return True if the view does not represent the current state of the DB.
|
||||
*/
|
||||
public boolean isOutOfDate() {
|
||||
return outOfDate.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a ReadOnlyBooleanProperty that holds true if this view does not
|
||||
* represent the current state of the DB>
|
||||
*
|
||||
* @return A ReadOnlyBooleanProperty that holds the out-of-date state for
|
||||
* this view.
|
||||
*/
|
||||
public ReadOnlyBooleanProperty outOfDateProperty() {
|
||||
return outOfDate.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TimelineController for this view.
|
||||
*
|
||||
* @return The TimelineController for this view.
|
||||
*/
|
||||
protected TimeLineController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh this view based on current state of zoom / filters. Primarily
|
||||
* this invokes the background ViewRefreshTask returned by
|
||||
* getUpdateTask(), which derived classes must implement.
|
||||
*
|
||||
* TODO: replace this logic with a javafx Service ? -jm
|
||||
*/
|
||||
protected final synchronized void refresh() {
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel(true);
|
||||
updateTask = null;
|
||||
}
|
||||
updateTask = getNewUpdateTask();
|
||||
updateTask.stateProperty().addListener((Observable observable) -> {
|
||||
switch (updateTask.getState()) {
|
||||
case CANCELLED:
|
||||
case FAILED:
|
||||
case READY:
|
||||
case RUNNING:
|
||||
case SCHEDULED:
|
||||
break;
|
||||
case SUCCEEDED:
|
||||
try {
|
||||
this.hasVisibleEvents.set(updateTask.get());
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unexpected exception updating view", ex); //NON-NLS
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
getController().monitorTask(updateTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the FilteredEventsModel for this view.
|
||||
*
|
||||
* @return The FilteredEventsModel for this view.
|
||||
*/
|
||||
protected FilteredEventsModel getEventsModel() {
|
||||
return filteredEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new background Task that fetches the appropriate data and loads it
|
||||
* into this view.
|
||||
*
|
||||
* @return A new task to execute on a background thread to reload this view
|
||||
* with different data.
|
||||
*/
|
||||
protected abstract Task<Boolean> getNewUpdateTask();
|
||||
|
||||
/**
|
||||
* Get a List of Nodes containing settings widgets to insert into this
|
||||
* view's header.
|
||||
*
|
||||
* @return The List of settings Nodes.
|
||||
*/
|
||||
protected List<Node> getSettingsNodes() {
|
||||
return Collections.unmodifiableList(settingsNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the List of Nodes containing settings widgets to insert into this
|
||||
* view's header.
|
||||
*
|
||||
*
|
||||
* @param settingsNodes The List of Nodes containing settings widgets to
|
||||
* insert into this view's header.
|
||||
*/
|
||||
final protected void setSettingsNodes(List<Node> settingsNodes) {
|
||||
this.settingsNodes = new ArrayList<>(settingsNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of this view and any resources it holds onto.
|
||||
*/
|
||||
final synchronized void dispose() {
|
||||
//cancel and gc updateTask
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel(true);
|
||||
updateTask = null;
|
||||
}
|
||||
//remvoe and gc updateListener
|
||||
this.filteredEvents.zoomParametersProperty().removeListener(updateListener);
|
||||
TimeLineController.getTimeZone().removeListener(updateListener);
|
||||
updateListener = null;
|
||||
filteredEvents.unRegisterForEvents(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Are there are any events visible in this view with the current view
|
||||
* parameters?
|
||||
*
|
||||
* @return True if there are events visible in this view with the current
|
||||
* view parameters.
|
||||
*/
|
||||
boolean hasVisibleEvents() {
|
||||
return hasVisibleEventsProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* A property that indicates whether there are any events visible in this
|
||||
* view with the current view parameters.
|
||||
*
|
||||
* @return A property that indicates whether there are any events visible in
|
||||
* this view with the current view parameters.
|
||||
*/
|
||||
ReadOnlyBooleanProperty hasVisibleEventsProperty() {
|
||||
return hasVisibleEvents.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this view out of date because, for example, tags have been updated
|
||||
* but the view was not refreshed.
|
||||
*/
|
||||
void setOutOfDate() {
|
||||
outOfDate.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all data items from this chart.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
abstract protected void clearData();
|
||||
|
||||
/**
|
||||
* Base class for Tasks that refreshes a view when the view settings change.
|
||||
*
|
||||
* @param <AxisValuesType> The type of a single object that can represent
|
||||
* the range of data displayed along the X-Axis.
|
||||
*/
|
||||
protected abstract class ViewRefreshTask<AxisValuesType> extends LoggedTask<Boolean> {
|
||||
|
||||
private final Node center;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param taskName The name of this task.
|
||||
* @param logStateChanges Whether or not task state changes should be
|
||||
* logged.
|
||||
*/
|
||||
protected ViewRefreshTask(String taskName, boolean logStateChanges) {
|
||||
super(taskName, logStateChanges);
|
||||
this.center = getCenter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets initial progress value and message and shows blocking progress
|
||||
* indicator over the view. Derived Tasks should be sure to call this as
|
||||
* part of their call() implementation.
|
||||
*
|
||||
* @return True
|
||||
*
|
||||
* @throws Exception If there is an unhandled exception during the
|
||||
* background operation
|
||||
*/
|
||||
@NbBundle.Messages(value = {"ViewRefreshTask.preparing=Analyzing zoom and filter settings"})
|
||||
@Override
|
||||
protected Boolean call() throws Exception {
|
||||
updateProgress(-1, 1);
|
||||
updateMessage(Bundle.ViewRefreshTask_preparing());
|
||||
Platform.runLater(() -> {
|
||||
MaskerPane maskerPane = new MaskerPane();
|
||||
maskerPane.textProperty().bind(messageProperty());
|
||||
maskerPane.progressProperty().bind(progressProperty());
|
||||
setCenter(new StackPane(center, maskerPane));
|
||||
setCursor(Cursor.WAIT);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the horizontal axis and removes the blocking progress
|
||||
* indicator. Derived Tasks should be sure to call this as part of their
|
||||
* succeeded() implementation.
|
||||
*/
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
super.succeeded();
|
||||
outOfDate.set(false);
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the blocking progress indicator. Derived Tasks should be sure
|
||||
* to call this as part of their cancelled() implementation.
|
||||
*/
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
super.cancelled();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the blocking progress indicator. Derived Tasks should be sure
|
||||
* to call this as part of their failed() implementation.
|
||||
*/
|
||||
@Override
|
||||
protected void failed() {
|
||||
super.failed();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the blocking progress indicator and reset the cursor to the
|
||||
* default.
|
||||
*/
|
||||
private void cleanup() {
|
||||
setCenter(center); //clear masker pane installed in call()
|
||||
setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the horizontal range that this chart will show.
|
||||
*
|
||||
* @param values A single object representing the range that this chart
|
||||
* will show.
|
||||
*/
|
||||
protected abstract void setDateValues(AxisValuesType values);
|
||||
|
||||
/**
|
||||
* Clears the chart data and sets the horizontal axis range. For use
|
||||
* within the derived implementation of the call() method.
|
||||
*
|
||||
* @param axisValues
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.NOT_UI)
|
||||
protected void resetView(AxisValuesType axisValues) {
|
||||
Platform.runLater(() -> {
|
||||
clearData();
|
||||
setDateValues(axisValues);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -18,28 +18,16 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.ui;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.chart.Axis;
|
||||
import javafx.scene.chart.XYChart;
|
||||
@ -47,7 +35,6 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.OverrunStyle;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.Border;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.BorderStroke;
|
||||
import javafx.scene.layout.BorderStrokeStyle;
|
||||
import javafx.scene.layout.BorderWidths;
|
||||
@ -55,7 +42,6 @@ import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
@ -64,18 +50,14 @@ import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.controlsfx.control.MaskerPane;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||
|
||||
/**
|
||||
* Abstract base class for TimeLineChart based visualizations.
|
||||
* Abstract base class for TimeLineChart based views.
|
||||
*
|
||||
* @param <X> The type of data plotted along the x axis
|
||||
* @param <Y> The type of data plotted along the y axis
|
||||
@ -88,36 +70,33 @@ import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||
*
|
||||
* TODO: pull up common history context menu items out of derived classes? -jm
|
||||
*/
|
||||
public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, ChartType extends Region & TimeLineChart<X>> extends BorderPane {
|
||||
public abstract class AbstractTimelineChart<X, Y, NodeType extends Node, ChartType extends Region & TimeLineChart<X>> extends AbstractTimeLineView {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractVisualizationPane.class.getName());
|
||||
private static final Logger LOGGER = Logger.getLogger(AbstractTimelineChart.class.getName());
|
||||
|
||||
@NbBundle.Messages("AbstractVisualization.Default_Tooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.")
|
||||
private static final Tooltip DEFAULT_TOOLTIP = new Tooltip(Bundle.AbstractVisualization_Default_Tooltip_text());
|
||||
@NbBundle.Messages("AbstractTimelineChart.defaultTooltip.text=Drag the mouse to select a time interval to zoom into.\nRight-click for more actions.")
|
||||
private static final Tooltip DEFAULT_TOOLTIP = new Tooltip(Bundle.AbstractTimelineChart_defaultTooltip_text());
|
||||
private static final Border ONLY_LEFT_BORDER = new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(0, 0, 0, 1)));
|
||||
|
||||
/**
|
||||
* Get the tool tip to use for this visualization when no more specific
|
||||
* Tooltip is needed.
|
||||
* Get the tool tip to use for this view when no more specific Tooltip is
|
||||
* needed.
|
||||
*
|
||||
* @return The default Tooltip.
|
||||
*/
|
||||
public static Tooltip getDefaultTooltip() {
|
||||
static public Tooltip getDefaultTooltip() {
|
||||
return DEFAULT_TOOLTIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Boolean property that holds true if the visualization may not represent
|
||||
* the current state of the DB, because, for example, tags have been updated
|
||||
* but the vis. was not refreshed.
|
||||
* The nodes that are selected.
|
||||
*
|
||||
* @return An ObservableList<NodeType> of the nodes that are selected in
|
||||
* this view.
|
||||
*/
|
||||
private final ReadOnlyBooleanWrapper outOfDate = new ReadOnlyBooleanWrapper(false);
|
||||
|
||||
/**
|
||||
* Boolean property that holds true if the visualization does not show any
|
||||
* events with the current zoom and filter settings.
|
||||
*/
|
||||
private final ReadOnlyBooleanWrapper hasVisibleEvents = new ReadOnlyBooleanWrapper(true);
|
||||
protected ObservableList<NodeType> getSelectedNodes() {
|
||||
return selectedNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access to chart data via series
|
||||
@ -130,123 +109,36 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
//// replacement axis label componenets
|
||||
private final Pane specificLabelPane = new Pane(); // container for the specfic labels in the decluttered axis
|
||||
private final Pane contextLabelPane = new Pane();// container for the contextual labels in the decluttered axis
|
||||
// container for the contextual labels in the decluttered axis
|
||||
private final Region spacer = new Region();
|
||||
|
||||
/**
|
||||
* task used to reload the content of this visualization
|
||||
*/
|
||||
private Task<Boolean> updateTask;
|
||||
|
||||
final private TimeLineController controller;
|
||||
final private FilteredEventsModel filteredEvents;
|
||||
|
||||
final private ObservableList<NodeType> selectedNodes = FXCollections.observableArrayList();
|
||||
|
||||
/**
|
||||
* Listener that is attached to various properties that should trigger a vis
|
||||
* update when they change.
|
||||
*/
|
||||
private InvalidationListener updateListener = any -> refresh();
|
||||
public Pane getSpecificLabelPane() {
|
||||
return specificLabelPane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the visualization represent an out-of-date state of the DB. It might
|
||||
* if, for example, tags have been updated but the vis. was not refreshed.
|
||||
*
|
||||
* @return True if the visualization does not represent the curent state of
|
||||
* the DB.
|
||||
*/
|
||||
public boolean isOutOfDate() {
|
||||
return outOfDate.get();
|
||||
public Pane getContextLabelPane() {
|
||||
return contextLabelPane;
|
||||
}
|
||||
|
||||
public Region getSpacer() {
|
||||
return spacer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this visualization out of date because, for example, tags have been
|
||||
* updated but the vis. was not refreshed.
|
||||
*/
|
||||
void setOutOfDate() {
|
||||
outOfDate.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a ReadOnlyBooleanProperty that holds true if this visualization does
|
||||
* not represent the current state of the DB>
|
||||
* Get the CharType that implements this view.
|
||||
*
|
||||
* @return A ReadOnlyBooleanProperty that holds the out-of-date state for
|
||||
* this visualization.
|
||||
*/
|
||||
public ReadOnlyBooleanProperty outOfDateProperty() {
|
||||
return outOfDate.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* The visualization nodes that are selected.
|
||||
*
|
||||
* @return An ObservableList<NodeType> of the nodes that are selected in
|
||||
* this visualization.
|
||||
*/
|
||||
protected ObservableList<NodeType> getSelectedNodes() {
|
||||
return selectedNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of Nodes to insert into the toolbar. This should be set in an
|
||||
* implementations constructor.
|
||||
*/
|
||||
private List<Node> settingsNodes;
|
||||
|
||||
/**
|
||||
* Get a List of nodes containing settings widgets to insert into this
|
||||
* visualization's header.
|
||||
*
|
||||
* @return The List of settings Nodes.
|
||||
*/
|
||||
protected List<Node> getSettingsNodes() {
|
||||
return Collections.unmodifiableList(settingsNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the List of nodes containing settings widgets to insert into this
|
||||
* visualization's header.
|
||||
*
|
||||
*
|
||||
* @param settingsNodes The List of nodes containing settings widgets to
|
||||
* insert into this visualization's header.
|
||||
*/
|
||||
protected void setSettingsNodes(List<Node> settingsNodes) {
|
||||
this.settingsNodes = new ArrayList<>(settingsNodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TimelineController for this visualization.
|
||||
*
|
||||
* @return The TimelineController for this visualization.
|
||||
*/
|
||||
protected TimeLineController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CharType that implements this visualization.
|
||||
*
|
||||
* @return The CharType that implements this visualization.
|
||||
* @return The CharType that implements this view.
|
||||
*/
|
||||
protected ChartType getChart() {
|
||||
return chart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the FilteredEventsModel for this visualization.
|
||||
* Set the ChartType that implements this view.
|
||||
*
|
||||
* @return The FilteredEventsModel for this visualization.
|
||||
*/
|
||||
protected FilteredEventsModel getEventsModel() {
|
||||
return filteredEvents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ChartType that implements this visualization.
|
||||
*
|
||||
* @param chart The ChartType that implements this visualization.
|
||||
* @param chart The ChartType that implements this view.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
protected void setChart(ChartType chart) {
|
||||
@ -255,29 +147,7 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
}
|
||||
|
||||
/**
|
||||
* A property that indicates whether there are any events visible in this
|
||||
* visualization with the current view parameters.
|
||||
*
|
||||
* @return A property that indicates whether there are any events visible in
|
||||
* this visualization with the current view parameters.
|
||||
*/
|
||||
ReadOnlyBooleanProperty hasVisibleEventsProperty() {
|
||||
return hasVisibleEvents.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Are there are any events visible in this visualization with the current
|
||||
* view parameters?
|
||||
*
|
||||
* @return True if there are events visible in this visualization with the
|
||||
* current view parameters.
|
||||
*/
|
||||
boolean hasVisibleEvents() {
|
||||
return hasVisibleEventsProperty().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply this visualization's 'selection effect' to the given node.
|
||||
* Apply this view's 'selection effect' to the given node.
|
||||
*
|
||||
* @param node The node to apply the 'effect' to.
|
||||
*/
|
||||
@ -286,7 +156,7 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this visualization's 'selection effect' from the given node.
|
||||
* Remove this view's 'selection effect' from the given node.
|
||||
*
|
||||
* @param node The node to remvoe the 'effect' from.
|
||||
*/
|
||||
@ -298,7 +168,7 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
* Should the tick mark at the given value be bold, because it has
|
||||
* interesting data associated with it?
|
||||
*
|
||||
* @param value A value along this visualization's x axis
|
||||
* @param value A value along this view's x axis
|
||||
*
|
||||
* @return True if the tick label for the given value should be bold ( has
|
||||
* relevant data), false otherwise
|
||||
@ -306,8 +176,8 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
abstract protected Boolean isTickBold(X value);
|
||||
|
||||
/**
|
||||
* Apply this visualization's 'selection effect' to the given node, if
|
||||
* applied is true. If applied is false, remove the affect
|
||||
* Apply this view's 'selection effect' to the given node, if applied is
|
||||
* true. If applied is false, remove the affect
|
||||
*
|
||||
* @param node The node to apply the 'effect' to
|
||||
* @param applied True if the effect should be applied, false if the effect
|
||||
@ -315,15 +185,6 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
*/
|
||||
abstract protected void applySelectionEffect(NodeType node, Boolean applied);
|
||||
|
||||
/**
|
||||
* Get a new background Task that fetches the appropriate data and loads it
|
||||
* into this visualization.
|
||||
*
|
||||
* @return A new task to execute on a background thread to reload this
|
||||
* visualization with different data.
|
||||
*/
|
||||
abstract protected Task<Boolean> getNewUpdateTask();
|
||||
|
||||
/**
|
||||
* Get the label that should be used for a tick mark at the given value.
|
||||
*
|
||||
@ -342,16 +203,16 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
abstract protected double getTickSpacing();
|
||||
|
||||
/**
|
||||
* Get the X-Axis of this Visualization's chart
|
||||
* Get the X-Axis of this view's chart
|
||||
*
|
||||
* @return The horizontal axis used by this Visualization's chart
|
||||
* @return The horizontal axis used by this view's chart
|
||||
*/
|
||||
abstract protected Axis<X> getXAxis();
|
||||
|
||||
/**
|
||||
* Get the Y-Axis of this Visualization's chart
|
||||
* Get the Y-Axis of this view's chart
|
||||
*
|
||||
* @return The vertical axis used by this Visualization's chart
|
||||
* @return The vertical axis used by this view's chart
|
||||
*/
|
||||
abstract protected Axis<Y> getYAxis();
|
||||
|
||||
@ -364,74 +225,6 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
*/
|
||||
abstract protected double getAxisMargin();
|
||||
|
||||
/**
|
||||
* Clear all data items from this chart.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
abstract protected void clearChartData();
|
||||
|
||||
/**
|
||||
* Refresh this visualization based on current state of zoom / filters.
|
||||
* Primarily this invokes the background VisualizationUpdateTask returned by
|
||||
* getUpdateTask(), which derived classes must implement.
|
||||
*
|
||||
* TODO: replace this logic with a javafx Service ? -jm
|
||||
*/
|
||||
protected final synchronized void refresh() {
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel(true);
|
||||
updateTask = null;
|
||||
}
|
||||
updateTask = getNewUpdateTask();
|
||||
updateTask.stateProperty().addListener((Observable observable) -> {
|
||||
switch (updateTask.getState()) {
|
||||
case CANCELLED:
|
||||
case FAILED:
|
||||
case READY:
|
||||
case RUNNING:
|
||||
case SCHEDULED:
|
||||
break;
|
||||
case SUCCEEDED:
|
||||
try {
|
||||
this.hasVisibleEvents.set(updateTask.get());
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Unexpected exception updating visualization", ex); //NON-NLS
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
controller.monitorTask(updateTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a RefreshRequestedEvent from the events model by updating the
|
||||
* visualization.
|
||||
*
|
||||
* @param event The RefreshRequestedEvent to handle.
|
||||
*/
|
||||
@Subscribe
|
||||
public void handleRefreshRequested(RefreshRequestedEvent event) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of this visualization and any resources it holds onto.
|
||||
*/
|
||||
final synchronized void dispose() {
|
||||
|
||||
//cancel and gc updateTask
|
||||
if (updateTask != null) {
|
||||
updateTask.cancel(true);
|
||||
updateTask = null;
|
||||
}
|
||||
//remvoe and gc updateListener
|
||||
this.filteredEvents.zoomParametersProperty().removeListener(updateListener);
|
||||
TimeLineController.getTimeZone().removeListener(updateListener);
|
||||
updateListener = null;
|
||||
|
||||
filteredEvents.unRegisterForEvents(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a series for each event type in a consistent order.
|
||||
*/
|
||||
@ -459,23 +252,20 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The TimelineController for this visualization.
|
||||
* @param controller The TimelineController for this view.
|
||||
*/
|
||||
protected AbstractVisualizationPane(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
this.filteredEvents.registerForEvents(this);
|
||||
this.filteredEvents.zoomParametersProperty().addListener(updateListener);
|
||||
protected AbstractTimelineChart(TimeLineController controller) {
|
||||
super(controller);
|
||||
Platform.runLater(() -> {
|
||||
VBox vBox = new VBox(specificLabelPane, contextLabelPane);
|
||||
VBox vBox = new VBox(getSpecificLabelPane(), getContextLabelPane());
|
||||
vBox.setFillWidth(false);
|
||||
HBox hBox = new HBox(spacer, vBox);
|
||||
HBox hBox = new HBox(getSpacer(), vBox);
|
||||
hBox.setFillHeight(false);
|
||||
setBottom(hBox);
|
||||
DoubleBinding spacerSize = getYAxis().widthProperty().add(getYAxis().tickLengthProperty()).add(getAxisMargin());
|
||||
spacer.minWidthProperty().bind(spacerSize);
|
||||
spacer.prefWidthProperty().bind(spacerSize);
|
||||
spacer.maxWidthProperty().bind(spacerSize);
|
||||
getSpacer().minWidthProperty().bind(spacerSize);
|
||||
getSpacer().prefWidthProperty().bind(spacerSize);
|
||||
getSpacer().maxWidthProperty().bind(spacerSize);
|
||||
});
|
||||
|
||||
createSeries();
|
||||
@ -487,10 +277,8 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
}
|
||||
});
|
||||
|
||||
TimeLineController.getTimeZone().addListener(updateListener);
|
||||
|
||||
//show tooltip text in status bar
|
||||
hoverProperty().addListener(hoverProp -> controller.setStatusMessage(isHover() ? DEFAULT_TOOLTIP.getText() : ""));
|
||||
hoverProperty().addListener(hoverProp -> controller.setStatusMessage(isHover() ? getDefaultTooltip().getText() : ""));
|
||||
|
||||
}
|
||||
|
||||
@ -681,117 +469,4 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for Tasks that refresh a visualization when the view settings
|
||||
* change.
|
||||
*
|
||||
* @param <AxisValuesType> The type of a single object that can represent
|
||||
* the range of data displayed along the X-Axis.
|
||||
*/
|
||||
abstract protected class VisualizationRefreshTask<AxisValuesType> extends LoggedTask<Boolean> {
|
||||
|
||||
private final Node center;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param taskName The name of this task.
|
||||
* @param logStateChanges Whether or not task state changes should be
|
||||
* logged.
|
||||
*/
|
||||
protected VisualizationRefreshTask(String taskName, boolean logStateChanges) {
|
||||
super(taskName, logStateChanges);
|
||||
this.center = getCenter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets initial progress value and message and shows blocking progress
|
||||
* indicator over the visualization. Derived Tasks should be sure to
|
||||
* call this as part of their call() implementation.
|
||||
*
|
||||
* @return True
|
||||
*
|
||||
* @throws Exception If there is an unhandled exception during the
|
||||
* background operation
|
||||
*/
|
||||
@NbBundle.Messages({"VisualizationUpdateTask.preparing=Analyzing zoom and filter settings"})
|
||||
@Override
|
||||
protected Boolean call() throws Exception {
|
||||
updateProgress(-1, 1);
|
||||
updateMessage(Bundle.VisualizationUpdateTask_preparing());
|
||||
Platform.runLater(() -> {
|
||||
MaskerPane maskerPane = new MaskerPane();
|
||||
maskerPane.textProperty().bind(messageProperty());
|
||||
maskerPane.progressProperty().bind(progressProperty());
|
||||
setCenter(new StackPane(center, maskerPane));
|
||||
setCursor(Cursor.WAIT);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the horizontal axis and removes the blocking progress
|
||||
* indicator. Derived Tasks should be sure to call this as part of their
|
||||
* succeeded() implementation.
|
||||
*/
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
super.succeeded();
|
||||
outOfDate.set(false);
|
||||
layoutDateLabels();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the blocking progress indicator. Derived Tasks should be sure
|
||||
* to call this as part of their cancelled() implementation.
|
||||
*/
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
super.cancelled();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the blocking progress indicator. Derived Tasks should be sure
|
||||
* to call this as part of their failed() implementation.
|
||||
*/
|
||||
@Override
|
||||
protected void failed() {
|
||||
super.failed();
|
||||
cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the blocking progress indicator and reset the cursor to the
|
||||
* default.
|
||||
*/
|
||||
private void cleanup() {
|
||||
setCenter(center); //clear masker pane installed in call()
|
||||
setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the chart data and sets the horizontal axis range. For use
|
||||
* within the derived implementation of the call() method.
|
||||
*
|
||||
* @param axisValues
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.NOT_UI)
|
||||
protected void resetChart(AxisValuesType axisValues) {
|
||||
Platform.runLater(() -> {
|
||||
clearChartData();
|
||||
setDateAxisValues(axisValues);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the horizontal range that this chart will show.
|
||||
*
|
||||
* @param values A single object representing the range that this chart
|
||||
* will show.
|
||||
*/
|
||||
abstract protected void setDateAxisValues(AxisValuesType values);
|
||||
}
|
||||
}
|
@ -35,14 +35,13 @@ Timeline.ui.ZoomRanges.threeyears.text=Three Years
|
||||
Timeline.ui.ZoomRanges.fiveyears.text=Five Years
|
||||
Timeline.ui.ZoomRanges.tenyears.text=Ten Years
|
||||
Timeline.ui.ZoomRanges.all.text=All
|
||||
TimeLineResultView.startDateToEndDate.text={0} to {1}
|
||||
VisualizationPanel.histogramTask.title=Rebuild Histogram
|
||||
VisualizationPanel.histogramTask.preparing=preparing
|
||||
VisualizationPanel.histogramTask.resetUI=resetting ui
|
||||
VisualizationPanel.histogramTask.queryDb=querying db
|
||||
VisualizationPanel.histogramTask.updateUI2=updating ui
|
||||
VisualizationPanel.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings.
|
||||
VisualizationPanel.zoomButton.text=Zoom to events
|
||||
ViewFrame.histogramTask.title=Rebuild Histogram
|
||||
ViewFrame.histogramTask.preparing=preparing
|
||||
ViewFrame.histogramTask.resetUI=resetting ui
|
||||
ViewFrame.histogramTask.queryDb=querying db
|
||||
ViewFrame.histogramTask.updateUI2=updating ui
|
||||
ViewFrame.noEventsDialogLabel.text=There are no events visible with the current zoom / filter settings.
|
||||
ViewFrame.zoomButton.text=Zoom to events
|
||||
TimeZonePanel.localRadio.text=Local Time Zone
|
||||
TimeZonePanel.otherRadio.text=GMT / UTC
|
||||
VisualizationPanel.resetFiltersButton.text=Reset all filters
|
||||
ViewFrame.resetFiltersButton.text=Reset all filters
|
||||
|
@ -1,24 +1,24 @@
|
||||
Timeline.node.root=\u30eb\u30fc\u30c8
|
||||
Timeline.ui.TimeLineChart.tooltip.text=\u30c0\u30d6\u30eb\u30af\u30ea\u30c3\u30af\u3067\u4e0b\u8a18\u306e\u7bc4\u56f2\u3078\u30ba\u30fc\u30e0\uff1a\n{0}\u301c{1}\n\u53f3\u30af\u30ea\u30c3\u30af\u3067\u5143\u306b\u623b\u308a\u307e\u3059\u3002
|
||||
Timeline.ui.ZoomRanges.all.text=\u5168\u3066
|
||||
TimeLineResultView.startDateToEndDate.text={0}\u304b\u3089{1}
|
||||
VisualizationPanel.histogramTask.preparing=\u6e96\u5099\u4e2d
|
||||
VisualizationPanel.histogramTask.queryDb=DB\u3092\u30af\u30a8\u30ea\u4e2d
|
||||
VisualizationPanel.histogramTask.resetUI=ui\u3092\u518d\u8a2d\u5b9a\u4e2d
|
||||
VisualizationPanel.histogramTask.title=\u30d2\u30b9\u30c8\u30b0\u30e9\u30e0\u3092\u518d\u30d3\u30eb\u30c9
|
||||
VisualizationPanel.histogramTask.updateUI2=ui\u3092\u66f4\u65b0\u4e2d
|
||||
|
||||
ViewFrame.histogramTask.preparing=\u6e96\u5099\u4e2d
|
||||
ViewFrame.histogramTask.queryDb=DB\u3092\u30af\u30a8\u30ea\u4e2d
|
||||
ViewFrame.histogramTask.resetUI=ui\u3092\u518d\u8a2d\u5b9a\u4e2d
|
||||
ViewFrame.histogramTask.title=\u30d2\u30b9\u30c8\u30b0\u30e9\u30e0\u3092\u518d\u30d3\u30eb\u30c9
|
||||
ViewFrame.histogramTask.updateUI2=ui\u3092\u66f4\u65b0\u4e2d
|
||||
TimeZonePanel.localRadio.text=\u30ed\u30fc\u30ab\u30eb\u30bf\u30a4\u30e0\u30be\u30fc\u30f3
|
||||
VisualizationPanel.countsToggle.text=\u30ab\u30a6\u30f3\u30c8
|
||||
VisualizationPanel.detailsToggle.text=\u8a73\u7d30
|
||||
VisualizationPanel.endLabel.text=\u30a8\u30f3\u30c9\uff1a
|
||||
VisualizationPanel.noEventsDialogLabel.text=\u73fe\u5728\u306e\u30ba\u30fc\u30e0\uff0f\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3067\u306f\u898b\u3048\u308b\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093\u3002
|
||||
VisualizationPanel.resetFiltersButton.text=\u5168\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u30fc\u3092\u30ea\u30bb\u30c3\u30c8
|
||||
VisualizationPanel.startLabel.text=\u30b9\u30bf\u30fc\u30c8\uff1a
|
||||
VisualizationPanel.visualizationModeLabel.text=\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\u30e2\u30fc\u30c9\uff1a
|
||||
VisualizationPanel.zoomButton.text=\u30a4\u30d9\u30f3\u30c8\u3078\u30ba\u30fc\u30e0
|
||||
VisualizationPanel.zoomMenuButton.text=\u4e0b\u8a18\u3078\u30ba\u30fc\u30e0\u30a4\u30f3\uff0f\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8
|
||||
ViewFrame.countsToggle.text=\u30ab\u30a6\u30f3\u30c8
|
||||
ViewFrame.detailsToggle.text=\u8a73\u7d30
|
||||
ViewFrame.endLabel.text=\u30a8\u30f3\u30c9\uff1a
|
||||
ViewFrame.noEventsDialogLabel.text=\u73fe\u5728\u306e\u30ba\u30fc\u30e0\uff0f\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3067\u306f\u898b\u3048\u308b\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093\u3002
|
||||
ViewFrame.resetFiltersButton.text=\u5168\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u30fc\u3092\u30ea\u30bb\u30c3\u30c8
|
||||
ViewFrame.startLabel.text=\u30b9\u30bf\u30fc\u30c8\uff1a
|
||||
ViewFrame.viewModeLabel.text=\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\u30e2\u30fc\u30c9\uff1a
|
||||
ViewFrame.zoomButton.text=\u30a4\u30d9\u30f3\u30c8\u3078\u30ba\u30fc\u30e0
|
||||
ViewFrame.zoomMenuButton.text=\u4e0b\u8a18\u3078\u30ba\u30fc\u30e0\u30a4\u30f3\uff0f\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8
|
||||
*=Autopsy\u30d5\u30a9\u30ec\u30f3\u30b8\u30c3\u30af\u30d6\u30e9\u30a6\u30b6
|
||||
AbstractVisualization.Default_Tooltip.text=\u30de\u30a6\u30b9\u3092\u30c9\u30e9\u30c3\u30b0\u3057\u3066\u30ba\u30fc\u30e0\u3059\u308b\u30bf\u30a4\u30e0\u9593\u9694\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\u305d\u306e\u4ed6\u306e\u30a2\u30af\u30b7\u30e7\u30f3\u306f\u53f3\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f\u3060\u3055\u3044
|
||||
AbstractTimelineChart.defaultTooltip.text=\u30de\u30a6\u30b9\u3092\u30c9\u30e9\u30c3\u30b0\u3057\u3066\u30ba\u30fc\u30e0\u3059\u308b\u30bf\u30a4\u30e0\u9593\u9694\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\u305d\u306e\u4ed6\u306e\u30a2\u30af\u30b7\u30e7\u30f3\u306f\u53f3\u30af\u30ea\u30c3\u30af\u3057\u3066\u304f\u3060\u3055\u3044
|
||||
IntervalSelector.ClearSelectedIntervalAction.tooltTipText=\u9078\u629e\u3057\u305f\u9593\u9694\u3092\u30af\u30ea\u30a2\u3059\u308b
|
||||
IntervalSelector.ZoomAction.name=\u30ba\u30fc\u30e0
|
||||
NoEventsDialog.titledPane.text=\u898b\u308c\u308b\u30a4\u30d9\u30f3\u30c8\u304c\u3042\u308a\u307e\u305b\u3093
|
||||
@ -40,6 +40,6 @@ Timeline.ui.ZoomRanges.fiveyears.text=5\u5e74
|
||||
Timeline.ui.ZoomRanges.tenyears.text=10\u5e74
|
||||
TimeLineChart.zoomHistoryActionGroup.name=\u30ba\u30fc\u30e0\u5c65\u6b74
|
||||
TimeZonePanel.title=\u6642\u9593\u3092\u6b21\u3067\u8868\u793a\uff1a
|
||||
VisualizationPanel.refresh=\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5
|
||||
VisualizationPanel.tagsAddedOrDeleted=\u30bf\u30b0\u304c\u4f5c\u6210\u3055\u308c\u307e\u3057\u305f\u304a\u3088\u3073\u307e\u305f\u306f\u524a\u9664\u3055\u308c\u307e\u3057\u305f\u3002\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\u304c\u6700\u65b0\u3067\u306f\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
|
||||
VisualizationUpdateTask.preparing=\u30ba\u30fc\u30e0\u304a\u3088\u3073\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3092\u89e3\u6790\u4e2d
|
||||
ViewFrame.refresh=\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5
|
||||
ViewFrame.tagsAddedOrDeleted=\u30bf\u30b0\u304c\u4f5c\u6210\u3055\u308c\u307e\u3057\u305f\u304a\u3088\u3073\u307e\u305f\u306f\u524a\u9664\u3055\u308c\u307e\u3057\u305f\u3002\u30d3\u30b8\u30e5\u30a2\u30e9\u30a4\u30bc\u30fc\u30b7\u30e7\u30f3\u304c\u6700\u65b0\u3067\u306f\u306a\u3044\u304b\u3082\u3057\u308c\u307e\u305b\u3093\u3002
|
||||
ViewRefreshTask.preparing=\u30ba\u30fc\u30e0\u304a\u3088\u3073\u30d5\u30a3\u30eb\u30bf\u30fc\u8a2d\u5b9a\u3092\u89e3\u6790\u4e2d
|
||||
|
@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.ui;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javafx.beans.Observable;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
|
||||
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
|
||||
|
||||
/**
|
||||
* Since it was too hard to derive from {@link DataResultPanel}, this class
|
||||
* implements {@link TimeLineView}, listens to the events/state of a the
|
||||
* assigned {@link FilteredEventsModel} and acts appropriately on its
|
||||
* {@link DataResultPanel}. That is, this class acts as a sort of bridge/adapter
|
||||
* between a FilteredEventsModel instance and a DataResultPanel instance.
|
||||
*/
|
||||
public class TimeLineResultView {
|
||||
|
||||
/**
|
||||
* the {@link DataResultPanel} that is the real view proxied by this class
|
||||
*/
|
||||
private final DataResultPanel dataResultPanel;
|
||||
|
||||
private final TimeLineController controller;
|
||||
|
||||
private final FilteredEventsModel filteredEvents;
|
||||
|
||||
private Set<Long> selectedEventIDs = new HashSet<>();
|
||||
|
||||
public DataResultPanel getDataResultPanel() {
|
||||
return dataResultPanel;
|
||||
}
|
||||
|
||||
public TimeLineResultView(TimeLineController controller, DataContent dataContent) {
|
||||
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
dataResultPanel = DataResultPanel.createInstanceUninitialized("", "", Node.EMPTY, 0, dataContent);
|
||||
|
||||
//set up listeners on relevant properties
|
||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
||||
dataResultPanel.setPath(getSummaryString());
|
||||
});
|
||||
|
||||
controller.getSelectedEventIDs().addListener((Observable o) -> {
|
||||
refresh();
|
||||
});
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a String representation of all the Events displayed
|
||||
*/
|
||||
private String getSummaryString() {
|
||||
if (controller.getSelectedTimeRange().get() != null) {
|
||||
final DateTimeFormatter zonedFormatter = TimeLineController.getZonedFormatter();
|
||||
return NbBundle.getMessage(this.getClass(), "TimeLineResultView.startDateToEndDate.text",
|
||||
controller.getSelectedTimeRange().get().getStart()
|
||||
.withZone(TimeLineController.getJodaTimeZone())
|
||||
.toString(zonedFormatter),
|
||||
controller.getSelectedTimeRange().get().getEnd()
|
||||
.withZone(TimeLineController.getJodaTimeZone())
|
||||
.toString(zonedFormatter));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* refresh this view with the events selected in the controller
|
||||
*/
|
||||
public final void refresh() {
|
||||
|
||||
Set<Long> newSelectedEventIDs = new HashSet<>(controller.getSelectedEventIDs());
|
||||
if (selectedEventIDs.equals(newSelectedEventIDs) == false) {
|
||||
selectedEventIDs = newSelectedEventIDs;
|
||||
final EventRootNode root = new EventRootNode(
|
||||
NbBundle.getMessage(this.getClass(), "Timeline.node.root"), selectedEventIDs,
|
||||
filteredEvents);
|
||||
|
||||
//this must be in edt or exception is thrown
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dataResultPanel.setPath(getSummaryString());
|
||||
dataResultPanel.setNode(root);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
<items>
|
||||
<HBox alignment="CENTER_LEFT" BorderPane.alignment="CENTER" HBox.hgrow="NEVER">
|
||||
<children>
|
||||
<Label fx:id="visualizationModeLabel" text="Visualisation Mode:" textAlignment="CENTER" wrapText="true" HBox.hgrow="NEVER">
|
||||
<Label fx:id="viewModeLabel" text="View Mode:" textAlignment="CENTER" wrapText="true" HBox.hgrow="NEVER">
|
||||
<HBox.margin>
|
||||
<Insets right="5.0" />
|
||||
</HBox.margin>
|
||||
@ -32,7 +32,7 @@
|
||||
</font>
|
||||
</Label>
|
||||
|
||||
<SegmentedButton maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" HBox.hgrow="NEVER">
|
||||
<SegmentedButton fx:id="modeSegButton" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" HBox.hgrow="NEVER">
|
||||
<buttons>
|
||||
<ToggleButton fx:id="countsToggle" alignment="TOP_LEFT" mnemonicParsing="false" selected="true">
|
||||
<graphic>
|
||||
@ -58,8 +58,20 @@
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</ToggleButton>
|
||||
<ToggleButton fx:id="listToggle" alignment="CENTER_RIGHT" layoutX="74.0" mnemonicParsing="false" selected="false">
|
||||
<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/table.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</ToggleButton>
|
||||
</buttons>
|
||||
|
||||
|
||||
</SegmentedButton>
|
||||
</children>
|
||||
<padding>
|
||||
@ -81,7 +93,7 @@
|
||||
</graphic>
|
||||
</Button>
|
||||
<Separator maxWidth="1.7976931348623157E308" orientation="VERTICAL" />
|
||||
<Button fx:id="refreshButton" alignment="CENTER_RIGHT" mnemonicParsing="false" text="Refresh Vis.">
|
||||
<Button fx:id="refreshButton" alignment="CENTER_RIGHT" mnemonicParsing="false" text="Refresh View">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
@ -29,15 +29,12 @@ import java.util.function.Supplier;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToolBar;
|
||||
import javafx.scene.control.Tooltip;
|
||||
@ -60,8 +57,10 @@ import javax.annotation.Nonnull;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import jfxtras.scene.control.LocalDateTimePicker;
|
||||
import jfxtras.scene.control.LocalDateTimeTextField;
|
||||
import jfxtras.scene.control.ToggleGroupValue;
|
||||
import org.controlsfx.control.NotificationPane;
|
||||
import org.controlsfx.control.RangeSlider;
|
||||
import org.controlsfx.control.SegmentedButton;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.joda.time.DateTime;
|
||||
@ -73,7 +72,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.ViewMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport;
|
||||
@ -88,18 +87,19 @@ import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.EventsTree;
|
||||
import org.sleuthkit.autopsy.timeline.ui.listvew.ListViewPane;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
|
||||
/**
|
||||
* A container for an AbstractVisualizationPane. Has a Toolbar on top to hold
|
||||
* settings widgets supplied by contained AbstractVisualizationPane, and the
|
||||
* A container for an AbstractTimelineView. Has a Toolbar on top to hold
|
||||
* settings widgets supplied by contained AbstractTimelineView, and the
|
||||
* histogram / time selection on bottom.
|
||||
*
|
||||
* TODO: Refactor common code out of histogram and CountsView? -jm
|
||||
*/
|
||||
final public class VisualizationPanel extends BorderPane {
|
||||
final public class ViewFrame extends BorderPane {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(VisualizationPanel.class.getName());
|
||||
private static final Logger LOGGER = Logger.getLogger(ViewFrame.class.getName());
|
||||
|
||||
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
|
||||
private static final Image WARNING = new Image("org/sleuthkit/autopsy/timeline/images/warning_triangle.png", 16, 16, true, true); // NON-NLS
|
||||
@ -108,7 +108,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
|
||||
/**
|
||||
* Region that will be stacked in between the no-events "dialog" and the
|
||||
* hosted AbstractVisualizationPane in order to gray out the visualization.
|
||||
* hosted AbstractTimelineView in order to gray out the AbstractTimelineView.
|
||||
*/
|
||||
private final static Region NO_EVENTS_BACKGROUND = new Region() {
|
||||
{
|
||||
@ -121,7 +121,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
private LoggedTask<Void> histogramTask;
|
||||
|
||||
private final EventsTree eventsTree;
|
||||
private AbstractVisualizationPane<?, ?, ?, ?> visualization;
|
||||
private AbstractTimeLineView hostedView;
|
||||
|
||||
/*
|
||||
* HBox that contains the histogram bars.
|
||||
@ -159,12 +159,16 @@ final public class VisualizationPanel extends BorderPane {
|
||||
@FXML
|
||||
private ToolBar toolBar;
|
||||
@FXML
|
||||
private Label visualizationModeLabel;
|
||||
private Label viewModeLabel;
|
||||
@FXML
|
||||
private SegmentedButton modeSegButton;
|
||||
@FXML
|
||||
private ToggleButton countsToggle;
|
||||
@FXML
|
||||
private ToggleButton detailsToggle;
|
||||
@FXML
|
||||
private ToggleButton listToggle;
|
||||
@FXML
|
||||
private Button snapShotButton;
|
||||
@FXML
|
||||
private Button refreshButton;
|
||||
@ -172,7 +176,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
private Button updateDBButton;
|
||||
|
||||
/*
|
||||
* Wraps contained visualization so that we can show notifications over it.
|
||||
* Wraps contained AbstractTimelineView so that we can show notifications over it.
|
||||
*/
|
||||
private final NotificationPane notificationPane = new NotificationPane();
|
||||
|
||||
@ -241,25 +245,26 @@ final public class VisualizationPanel extends BorderPane {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The TimeLineController for this VisualizationPanel
|
||||
* @param eventsTree The EventsTree this VisualizationPanel hosts.
|
||||
* @param controller The TimeLineController for this ViewFrame
|
||||
* @param eventsTree The EventsTree this ViewFrame hosts.
|
||||
*/
|
||||
public VisualizationPanel(@Nonnull TimeLineController controller, @Nonnull EventsTree eventsTree) {
|
||||
public ViewFrame(@Nonnull TimeLineController controller, @Nonnull EventsTree eventsTree) {
|
||||
this.controller = controller;
|
||||
this.filteredEvents = controller.getEventsModel();
|
||||
this.eventsTree = eventsTree;
|
||||
FXMLConstructor.construct(this, "VisualizationPanel.fxml"); // NON-NLS
|
||||
FXMLConstructor.construct(this, "ViewFrame.fxml"); // NON-NLS
|
||||
}
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages({
|
||||
"VisualizationPanel.visualizationModeLabel.text=Visualization Mode:",
|
||||
"VisualizationPanel.startLabel.text=Start:",
|
||||
"VisualizationPanel.endLabel.text=End:",
|
||||
"VisualizationPanel.countsToggle.text=Counts",
|
||||
"VisualizationPanel.detailsToggle.text=Details",
|
||||
"VisualizationPanel.zoomMenuButton.text=Zoom in/out to",
|
||||
"VisualizationPanel.tagsAddedOrDeleted=Tags have been created and/or deleted. The visualization may not be up to date."
|
||||
"ViewFrame.viewModeLabel.text=View Mode:",
|
||||
"ViewFrame.startLabel.text=Start:",
|
||||
"ViewFrame.endLabel.text=End:",
|
||||
"ViewFrame.countsToggle.text=Counts",
|
||||
"ViewFrame.detailsToggle.text=Details",
|
||||
"ViewFrame.listToggle.text=List",
|
||||
"ViewFrame.zoomMenuButton.text=Zoom in/out to",
|
||||
"ViewFrame.tagsAddedOrDeleted=Tags have been created and/or deleted. The view may not be up to date."
|
||||
})
|
||||
void initialize() {
|
||||
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||
@ -273,37 +278,32 @@ final public class VisualizationPanel extends BorderPane {
|
||||
notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
|
||||
setCenter(notificationPane);
|
||||
|
||||
//configure visualization mode toggle
|
||||
visualizationModeLabel.setText(Bundle.VisualizationPanel_visualizationModeLabel_text());
|
||||
countsToggle.setText(Bundle.VisualizationPanel_countsToggle_text());
|
||||
detailsToggle.setText(Bundle.VisualizationPanel_detailsToggle_text());
|
||||
ChangeListener<Toggle> toggleListener = (ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue) -> {
|
||||
if (newValue == null) {
|
||||
countsToggle.getToggleGroup().selectToggle(oldValue != null ? oldValue : countsToggle);
|
||||
} else if (newValue == countsToggle && oldValue != null) {
|
||||
controller.setVisualizationMode(VisualizationMode.COUNTS);
|
||||
} else if (newValue == detailsToggle && oldValue != null) {
|
||||
controller.setVisualizationMode(VisualizationMode.DETAIL);
|
||||
}
|
||||
};
|
||||
//configure view mode toggle
|
||||
viewModeLabel.setText(Bundle.ViewFrame_viewModeLabel_text());
|
||||
countsToggle.setText(Bundle.ViewFrame_countsToggle_text());
|
||||
detailsToggle.setText(Bundle.ViewFrame_detailsToggle_text());
|
||||
listToggle.setText(Bundle.ViewFrame_listToggle_text());
|
||||
|
||||
if (countsToggle.getToggleGroup() != null) {
|
||||
countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
|
||||
} else {
|
||||
countsToggle.toggleGroupProperty().addListener((Observable toggleGroup) -> {
|
||||
countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
|
||||
});
|
||||
}
|
||||
ToggleGroupValue<ViewMode> visModeToggleGroup = new ToggleGroupValue<>();
|
||||
visModeToggleGroup.add(listToggle, ViewMode.LIST);
|
||||
visModeToggleGroup.add(detailsToggle, ViewMode.DETAIL);
|
||||
visModeToggleGroup.add(countsToggle, ViewMode.COUNTS);
|
||||
|
||||
controller.visualizationModeProperty().addListener(visualizationMode -> syncVisualizationMode());
|
||||
syncVisualizationMode();
|
||||
modeSegButton.setToggleGroup(visModeToggleGroup);
|
||||
|
||||
visModeToggleGroup.valueProperty().addListener((observable, oldVisMode, newValue) -> {
|
||||
controller.setViewMode(newValue != null ? newValue : (oldVisMode != null ? oldVisMode : ViewMode.COUNTS));
|
||||
});
|
||||
|
||||
controller.viewModeProperty().addListener(viewMode -> syncViewMode());
|
||||
syncViewMode();
|
||||
|
||||
ActionUtils.configureButton(new SaveSnapshotAsReport(controller, notificationPane::getContent), snapShotButton);
|
||||
ActionUtils.configureButton(new UpdateDB(controller), updateDBButton);
|
||||
|
||||
/////configure start and end pickers
|
||||
startLabel.setText(Bundle.VisualizationPanel_startLabel_text());
|
||||
endLabel.setText(Bundle.VisualizationPanel_endLabel_text());
|
||||
startLabel.setText(Bundle.ViewFrame_startLabel_text());
|
||||
endLabel.setText(Bundle.ViewFrame_endLabel_text());
|
||||
|
||||
//suppress stacktraces on malformed input
|
||||
//TODO: should we do anything else? show a warning?
|
||||
@ -326,7 +326,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
rangeHistogramStack.getChildren().add(rangeSlider);
|
||||
|
||||
/*
|
||||
* this padding attempts to compensates for the fact that the
|
||||
* This padding attempts to compensates for the fact that the
|
||||
* rangeslider track doesn't extend to edge of node,and so the
|
||||
* histrogram doesn't quite line up with the rangeslider
|
||||
*/
|
||||
@ -344,7 +344,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
}
|
||||
})));
|
||||
}
|
||||
zoomMenuButton.setText(Bundle.VisualizationPanel_zoomMenuButton_text());
|
||||
zoomMenuButton.setText(Bundle.ViewFrame_zoomMenuButton_text());
|
||||
ActionUtils.configureButton(new ZoomOut(controller), zoomOutButton);
|
||||
ActionUtils.configureButton(new ZoomIn(controller), zoomInButton);
|
||||
|
||||
@ -355,28 +355,28 @@ final public class VisualizationPanel extends BorderPane {
|
||||
TimeLineController.getTimeZone().addListener(timeZoneProp -> refreshTimeUI());
|
||||
filteredEvents.timeRangeProperty().addListener(timeRangeProp -> refreshTimeUI());
|
||||
filteredEvents.zoomParametersProperty().addListener(zoomListener);
|
||||
refreshTimeUI(); //populate the viz
|
||||
refreshTimeUI(); //populate the view
|
||||
|
||||
refreshHistorgram();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle TagsUpdatedEvents by marking that the visualization needs to be
|
||||
* Handle TagsUpdatedEvents by marking that the view needs to be
|
||||
* refreshed.
|
||||
*
|
||||
* NOTE: This VisualizationPanel must be registered with the
|
||||
* NOTE: This ViewFrame must be registered with the
|
||||
* filteredEventsModel's EventBus in order for this handler to be invoked.
|
||||
*
|
||||
* @param event The TagsUpdatedEvent to handle.
|
||||
*/
|
||||
@Subscribe
|
||||
public void handleTimeLineTagUpdate(TagsUpdatedEvent event) {
|
||||
visualization.setOutOfDate();
|
||||
hostedView.setOutOfDate();
|
||||
Platform.runLater(() -> {
|
||||
if (notificationPane.isShowing() == false) {
|
||||
notificationPane.getActions().setAll(new Refresh());
|
||||
notificationPane.show(Bundle.VisualizationPanel_tagsAddedOrDeleted(), new ImageView(INFORMATION));
|
||||
notificationPane.show(Bundle.ViewFrame_tagsAddedOrDeleted(), new ImageView(INFORMATION));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -385,7 +385,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
* Handle a RefreshRequestedEvent from the events model by clearing the
|
||||
* refresh notification.
|
||||
*
|
||||
* NOTE: This VisualizationPanel must be registered with the
|
||||
* NOTE: This ViewFrame must be registered with the
|
||||
* filteredEventsModel's EventBus in order for this handler to be invoked.
|
||||
*
|
||||
* @param event The RefreshRequestedEvent to handle.
|
||||
@ -393,7 +393,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
@Subscribe
|
||||
public void handleRefreshRequested(RefreshRequestedEvent event) {
|
||||
Platform.runLater(() -> {
|
||||
if (Bundle.VisualizationPanel_tagsAddedOrDeleted().equals(notificationPane.getText())) {
|
||||
if (Bundle.ViewFrame_tagsAddedOrDeleted().equals(notificationPane.getText())) {
|
||||
notificationPane.hide();
|
||||
}
|
||||
});
|
||||
@ -401,16 +401,16 @@ final public class VisualizationPanel extends BorderPane {
|
||||
|
||||
/**
|
||||
* Handle a DBUpdatedEvent from the events model by refreshing the
|
||||
* visualization.
|
||||
* view.
|
||||
*
|
||||
* NOTE: This VisualizationPanel must be registered with the
|
||||
* NOTE: This ViewFrame must be registered with the
|
||||
* filteredEventsModel's EventBus in order for this handler to be invoked.
|
||||
*
|
||||
* @param event The DBUpdatedEvent to handle.
|
||||
*/
|
||||
@Subscribe
|
||||
public void handleDBUpdated(DBUpdatedEvent event) {
|
||||
visualization.refresh();
|
||||
hostedView.refresh();
|
||||
refreshHistorgram();
|
||||
Platform.runLater(notificationPane::hide);
|
||||
}
|
||||
@ -419,7 +419,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
* Handle a DataSourceAddedEvent from the events model by showing a
|
||||
* notification.
|
||||
*
|
||||
* NOTE: This VisualizationPanel must be registered with the
|
||||
* NOTE: This ViewFrame must be registered with the
|
||||
* filteredEventsModel's EventBus in order for this handler to be invoked.
|
||||
*
|
||||
* @param event The DataSourceAddedEvent to handle.
|
||||
@ -427,11 +427,11 @@ final public class VisualizationPanel extends BorderPane {
|
||||
@Subscribe
|
||||
@NbBundle.Messages({
|
||||
"# {0} - datasource name",
|
||||
"VisualizationPanel.notification.newDataSource={0} has been added as a new datasource. The Timeline DB may be out of date."})
|
||||
"ViewFrame.notification.newDataSource={0} has been added as a new datasource. The Timeline DB may be out of date."})
|
||||
public void handlDataSourceAdded(DataSourceAddedEvent event) {
|
||||
Platform.runLater(() -> {
|
||||
notificationPane.getActions().setAll(new UpdateDB(controller));
|
||||
notificationPane.show(Bundle.VisualizationPanel_notification_newDataSource(event.getDataSource().getName()), new ImageView(WARNING));
|
||||
notificationPane.show(Bundle.ViewFrame_notification_newDataSource(event.getDataSource().getName()), new ImageView(WARNING));
|
||||
});
|
||||
}
|
||||
|
||||
@ -439,7 +439,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
* Handle a DataSourceAnalysisCompletedEvent from the events modelby showing
|
||||
* a notification.
|
||||
*
|
||||
* NOTE: This VisualizationPanel must be registered with the
|
||||
* NOTE: This ViewFrame must be registered with the
|
||||
* filteredEventsModel's EventBus in order for this handler to be invoked.
|
||||
*
|
||||
* @param event The DataSourceAnalysisCompletedEvent to handle.
|
||||
@ -447,11 +447,11 @@ final public class VisualizationPanel extends BorderPane {
|
||||
@Subscribe
|
||||
@NbBundle.Messages({
|
||||
"# {0} - datasource name",
|
||||
"VisualizationPanel.notification.analysisComplete=Analysis has finished for {0}. The Timeline DB may be out of date."})
|
||||
"ViewFrame.notification.analysisComplete=Analysis has finished for {0}. The Timeline DB may be out of date."})
|
||||
public void handleAnalysisCompleted(DataSourceAnalysisCompletedEvent event) {
|
||||
Platform.runLater(() -> {
|
||||
notificationPane.getActions().setAll(new UpdateDB(controller));
|
||||
notificationPane.show(Bundle.VisualizationPanel_notification_analysisComplete(event.getDataSource().getName()), new ImageView(WARNING));
|
||||
notificationPane.show(Bundle.ViewFrame_notification_analysisComplete(event.getDataSource().getName()), new ImageView(WARNING));
|
||||
});
|
||||
}
|
||||
|
||||
@ -464,13 +464,13 @@ final public class VisualizationPanel extends BorderPane {
|
||||
}
|
||||
|
||||
histogramTask = new LoggedTask<Void>(
|
||||
NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.title"), true) { // NON-NLS
|
||||
NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.title"), true) { // NON-NLS
|
||||
private final Lighting lighting = new Lighting();
|
||||
|
||||
@Override
|
||||
protected Void call() throws Exception {
|
||||
|
||||
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.preparing")); // NON-NLS
|
||||
updateMessage(NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.preparing")); // NON-NLS
|
||||
|
||||
long max = 0;
|
||||
final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval());
|
||||
@ -483,7 +483,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
|
||||
//clear old data, and reset ranges and series
|
||||
Platform.runLater(() -> {
|
||||
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.resetUI")); // NON-NLS
|
||||
updateMessage(NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.resetUI")); // NON-NLS
|
||||
|
||||
});
|
||||
|
||||
@ -500,7 +500,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
|
||||
start = end;
|
||||
|
||||
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.queryDb")); // NON-NLS
|
||||
updateMessage(NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.queryDb")); // NON-NLS
|
||||
//query for current range
|
||||
long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum();
|
||||
bins.add(count);
|
||||
@ -510,7 +510,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
final double fMax = Math.log(max);
|
||||
final ArrayList<Long> fbins = new ArrayList<>(bins);
|
||||
Platform.runLater(() -> {
|
||||
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.updateUI2")); // NON-NLS
|
||||
updateMessage(NbBundle.getMessage(ViewFrame.class, "ViewFrame.histogramTask.updateUI2")); // NON-NLS
|
||||
|
||||
histogramBox.getChildren().clear();
|
||||
|
||||
@ -576,18 +576,28 @@ final public class VisualizationPanel extends BorderPane {
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to the given VisualizationMode, by swapping out the hosted
|
||||
* AbstractVislualization for one of the correct type.
|
||||
* Switch to the given ViewMode, by swapping out the hosted
|
||||
* AbstractTimelineView for one of the correct type.
|
||||
*/
|
||||
private void syncVisualizationMode() {
|
||||
AbstractVisualizationPane<?, ?, ?, ?> vizPane;
|
||||
VisualizationMode visMode = controller.visualizationModeProperty().get();
|
||||
private void syncViewMode() {
|
||||
AbstractTimeLineView view;
|
||||
ViewMode viewMode = controller.viewModeProperty().get();
|
||||
|
||||
//make new visualization.
|
||||
switch (visMode) {
|
||||
//make new view.
|
||||
switch (viewMode) {
|
||||
case LIST:
|
||||
view = new ListViewPane(controller);
|
||||
Platform.runLater(() -> {
|
||||
listToggle.setSelected(true);
|
||||
//TODO: should remove listeners from events tree
|
||||
});
|
||||
break;
|
||||
case COUNTS:
|
||||
vizPane = new CountsViewPane(controller);
|
||||
Platform.runLater(() -> countsToggle.setSelected(true));
|
||||
view = new CountsViewPane(controller);
|
||||
Platform.runLater(() -> {
|
||||
countsToggle.setSelected(true);
|
||||
//TODO: should remove listeners from events tree
|
||||
});
|
||||
break;
|
||||
case DETAIL:
|
||||
DetailViewPane detailViewPane = new DetailViewPane(controller);
|
||||
@ -596,34 +606,34 @@ final public class VisualizationPanel extends BorderPane {
|
||||
detailViewPane.setHighLightedEvents(eventsTree.getSelectedEvents());
|
||||
eventsTree.setDetailViewPane(detailViewPane);
|
||||
});
|
||||
vizPane = detailViewPane;
|
||||
view = detailViewPane;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown VisualizationMode: " + visMode.toString());
|
||||
throw new IllegalArgumentException("Unknown ViewMode: " + viewMode.toString());
|
||||
}
|
||||
|
||||
//Set the new AbstractVisualizationPane as the one hosted by this VisualizationPanel.
|
||||
//Set the new AbstractTimeLineView as the one hosted by this ViewFrame.
|
||||
Platform.runLater(() -> {
|
||||
//clear out old vis.
|
||||
if (visualization != null) {
|
||||
toolBar.getItems().removeAll(visualization.getSettingsNodes());
|
||||
visualization.dispose();
|
||||
//clear out old view.
|
||||
if (hostedView != null) {
|
||||
toolBar.getItems().removeAll(hostedView.getSettingsNodes());
|
||||
hostedView.dispose();
|
||||
}
|
||||
|
||||
visualization = vizPane;
|
||||
//setup new vis.
|
||||
ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new visualization
|
||||
visualization.refresh();
|
||||
toolBar.getItems().addAll(2, vizPane.getSettingsNodes());
|
||||
notificationPane.setContent(visualization);
|
||||
hostedView = view;
|
||||
//setup new view.
|
||||
ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new view
|
||||
hostedView.refresh();
|
||||
toolBar.getItems().addAll(2, view.getSettingsNodes());
|
||||
notificationPane.setContent(hostedView);
|
||||
|
||||
//listen to has events property and show "dialog" if it is false.
|
||||
visualization.hasVisibleEventsProperty().addListener(hasEvents -> {
|
||||
notificationPane.setContent(visualization.hasVisibleEvents()
|
||||
? visualization
|
||||
: new StackPane(visualization,
|
||||
hostedView.hasVisibleEventsProperty().addListener(hasEvents -> {
|
||||
notificationPane.setContent(hostedView.hasVisibleEvents()
|
||||
? hostedView
|
||||
: new StackPane(hostedView,
|
||||
NO_EVENTS_BACKGROUND,
|
||||
new NoEventsDialog(() -> notificationPane.setContent(visualization))
|
||||
new NoEventsDialog(() -> notificationPane.setContent(hostedView))
|
||||
)
|
||||
);
|
||||
});
|
||||
@ -660,7 +670,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
|
||||
|
||||
titledPane.setText(Bundle.NoEventsDialog_titledPane_text());
|
||||
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS
|
||||
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "ViewFrame.noEventsDialogLabel.text")); // NON-NLS
|
||||
|
||||
dismissButton.setOnAction(actionEvent -> closeCallback.run());
|
||||
|
||||
@ -689,7 +699,7 @@ final public class VisualizationPanel extends BorderPane {
|
||||
LocalDateTime pickerTime = pickerSupplier.get().getLocalDateTime();
|
||||
if (pickerTime != null) {
|
||||
controller.pushTimeRange(intervalMapper.apply(filteredEvents.timeRangeProperty().get(), localDateTimeToEpochMilli(pickerTime)));
|
||||
Platform.runLater(VisualizationPanel.this::refreshTimeUI);
|
||||
Platform.runLater(ViewFrame.this::refreshTimeUI);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -757,19 +767,19 @@ final public class VisualizationPanel extends BorderPane {
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that refreshes the Visualization.
|
||||
* Action that refreshes the View.
|
||||
*/
|
||||
private class Refresh extends Action {
|
||||
|
||||
@NbBundle.Messages({
|
||||
"VisualizationPanel.refresh.text=Refresh Vis.",
|
||||
"VisualizationPanel.refresh.longText=Refresh the visualization to include information that is in the DB but not visualized, such as newly updated tags."})
|
||||
"ViewFrame.refresh.text=Refresh View",
|
||||
"ViewFrame.refresh.longText=Refresh the view to include information that is in the DB but not displayed, such as newly updated tags."})
|
||||
Refresh() {
|
||||
super(Bundle.VisualizationPanel_refresh_text());
|
||||
setLongText(Bundle.VisualizationPanel_refresh_longText());
|
||||
super(Bundle.ViewFrame_refresh_text());
|
||||
setLongText(Bundle.ViewFrame_refresh_longText());
|
||||
setGraphic(new ImageView(REFRESH));
|
||||
setEventHandler(actionEvent -> filteredEvents.postRefreshRequest());
|
||||
disabledProperty().bind(visualization.outOfDateProperty().not());
|
||||
disabledProperty().bind(hostedView.outOfDateProperty().not());
|
||||
}
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
|
||||
/**
|
||||
@ -77,7 +77,7 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
* Platform.runLater(java.lang.Runnable). The FilteredEventsModel should
|
||||
* encapsulate all need synchronization internally.
|
||||
*/
|
||||
public class CountsViewPane extends AbstractVisualizationPane<String, Number, Node, EventCountsChart> {
|
||||
public class CountsViewPane extends AbstractTimelineChart<String, Number, Node, EventCountsChart> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CountsViewPane.class.getName());
|
||||
|
||||
@ -105,13 +105,15 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The TimelineController for this visualization.
|
||||
* @param controller The TimelineController for this view.
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"# {0} - scale name",
|
||||
"CountsViewPane.numberOfEvents=Number of Events ({0})"})
|
||||
public CountsViewPane(TimeLineController controller) {
|
||||
super(controller);
|
||||
|
||||
|
||||
setChart(new EventCountsChart(controller, dateAxis, countAxis, getSelectedNodes()));
|
||||
getChart().setData(dataSeries);
|
||||
Tooltip.install(getChart(), getDefaultTooltip());
|
||||
@ -156,7 +158,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
@Override
|
||||
protected void clearChartData() {
|
||||
protected void clearData() {
|
||||
for (XYChart.Series<String, Number> series : dataSeries) {
|
||||
series.getData().clear();
|
||||
}
|
||||
@ -247,7 +249,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
|
||||
"CountsViewPane.scaleLabel.text=Scale:",
|
||||
"CountsViewPane.scaleHelp.label.text=Scales: ",
|
||||
"CountsViewPane.linearRadio.text=Linear",
|
||||
"CountsViewPane.scaleHelpLinear=The linear scale is good for many use cases. When this scale is selected, the height of the bars represents the counts in a linear, one-to-one fashion, and the y-axis is labeled with values. When the range of values is very large, time periods with low counts may have a bar that is too small to see. To help the user detect this, the labels for date ranges with events are bold. To see bars that are too small, there are three options: adjust the window size so that the visualization area has more vertical space, adjust the time range shown so that time periods with larger bars are excluded, or adjust the scale setting to logarithmic.",
|
||||
"CountsViewPane.scaleHelpLinear=The linear scale is good for many use cases. When this scale is selected, the height of the bars represents the counts in a linear, one-to-one fashion, and the y-axis is labeled with values. When the range of values is very large, time periods with low counts may have a bar that is too small to see. To help the user detect this, the labels for date ranges with events are bold. To see bars that are too small, there are three options: adjust the window size so that the timeline has more vertical space, adjust the time range shown so that time periods with larger bars are excluded, or adjust the scale setting to logarithmic.",
|
||||
"CountsViewPane.scaleHelpLog=The logarithmic scale represents the number of events in a non-linear way that compresses the difference between large and small numbers. Note that even with the logarithmic scale, an extremely large difference in counts may still produce bars too small to see. In this case the only option may be to filter events to reduce the difference in counts. NOTE: Because the logarithmic scale is applied to each event type separately, the meaning of the height of the combined bar is not intuitive, and to emphasize this, no labels are shown on the y-axis with the logarithmic scale. The logarithmic scale should be used to quickly compare the counts ",
|
||||
"CountsViewPane.scaleHelpLog2=across time within a type, or across types for one time period, but not both.",
|
||||
"CountsViewPane.scaleHelpLog3= The actual counts (available in tooltips or the result viewer) should be used for absolute comparisons. Use the logarithmic scale with care."})
|
||||
@ -334,13 +336,19 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
|
||||
*/
|
||||
@NbBundle.Messages({
|
||||
"CountsViewPane.loggedTask.name=Updating Counts View",
|
||||
"CountsViewPane.loggedTask.updatingCounts=Populating visualization"})
|
||||
private class CountsUpdateTask extends VisualizationRefreshTask<List<String>> {
|
||||
"CountsViewPane.loggedTask.updatingCounts=Populating view"})
|
||||
private class CountsUpdateTask extends ViewRefreshTask<List<String>> {
|
||||
|
||||
CountsUpdateTask() {
|
||||
super(Bundle.CountsViewPane_loggedTask_name(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
super.succeeded();
|
||||
layoutDateLabels();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean call() throws Exception {
|
||||
super.call();
|
||||
@ -354,7 +362,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
|
||||
List<Interval> intervals = rangeInfo.getIntervals();
|
||||
|
||||
//clear old data, and reset ranges and series
|
||||
resetChart(Lists.transform(intervals, rangeInfo::formatForTick));
|
||||
resetView(Lists.transform(intervals, rangeInfo::formatForTick));
|
||||
|
||||
updateMessage(Bundle.CountsViewPane_loggedTask_updatingCounts());
|
||||
int chartMax = 0;
|
||||
@ -412,7 +420,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDateAxisValues(List<String> categories) {
|
||||
protected void setDateValues(List<String> categories) {
|
||||
dateAxis.getCategories().setAll(categories);
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ import org.joda.time.Seconds;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.ColorUtilities;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.ViewMode;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||
@ -403,27 +403,8 @@ final class EventCountsChart extends StackedBarChart<String, Number> implements
|
||||
Bundle.CountsViewPane_detailSwitchMessage(),
|
||||
Bundle.CountsViewPane_detailSwitchTitle(), JOptionPane.YES_NO_OPTION);
|
||||
if (showConfirmDialog == JOptionPane.YES_OPTION) {
|
||||
controller.setVisualizationMode(VisualizationMode.DETAIL);
|
||||
controller.setViewMode(ViewMode.DETAIL);
|
||||
}
|
||||
|
||||
/*
|
||||
* //I would like to use the JAvafx dialog, but it doesn't
|
||||
* block the ui (because it is embeded in a TopComponent)
|
||||
* -jm
|
||||
*
|
||||
* final Dialogs.CommandLink yes = new
|
||||
* Dialogs.CommandLink("Yes", "switch to Details view");
|
||||
* final Dialogs.CommandLink no = new
|
||||
* Dialogs.CommandLink("No", "return to Counts view with a
|
||||
* resolution of Seconds"); Action choice = Dialogs.create()
|
||||
* .title("Switch to Details View?") .masthead("There is no
|
||||
* temporal resolution smaller than Seconds.")
|
||||
* .message("Would you like to switch to the Details view
|
||||
* instead?") .showCommandLinks(Arrays.asList(yes, no));
|
||||
*
|
||||
* if (choice == yes) {
|
||||
* controller.setViewMode(VisualizationMode.DETAIL); }
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.EventStripe;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
|
||||
import org.sleuthkit.autopsy.timeline.utils.MappedList;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||
@ -73,7 +73,7 @@ import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||
* grouped EventStripes, etc, etc. The leaves of the trees are EventClusters or
|
||||
* SingleEvents.
|
||||
*/
|
||||
public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStripe, EventNodeBase<?>, DetailsChart> {
|
||||
public class DetailViewPane extends AbstractTimelineChart<DateTime, EventStripe, EventNodeBase<?>, DetailsChart> {
|
||||
|
||||
private final static Logger LOGGER = Logger.getLogger(DetailViewPane.class.getName());
|
||||
|
||||
@ -91,7 +91,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
|
||||
|
||||
/**
|
||||
* Local copy of the zoomParams. Used to backout of a zoomParam change
|
||||
* without needing to requery/redraw the vis.
|
||||
* without needing to requery/redraw the view.
|
||||
*/
|
||||
private ZoomParams currentZoomParams;
|
||||
|
||||
@ -204,7 +204,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
@Override
|
||||
protected void clearChartData() {
|
||||
protected void clearData() {
|
||||
getChart().reset();
|
||||
}
|
||||
|
||||
@ -347,13 +347,12 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
|
||||
@NbBundle.Messages({
|
||||
"DetailViewPane.loggedTask.queryDb=Retreiving event data",
|
||||
"DetailViewPane.loggedTask.name=Updating Details View",
|
||||
"DetailViewPane.loggedTask.updateUI=Populating visualization",
|
||||
"DetailViewPane.loggedTask.updateUI=Populating view",
|
||||
"DetailViewPane.loggedTask.continueButton=Continue",
|
||||
"DetailViewPane.loggedTask.backButton=Back (Cancel)",
|
||||
"# {0} - number of events",
|
||||
|
||||
"DetailViewPane.loggedTask.prompt=You are about to show details for {0} events. This might be very slow and could exhaust available memory.\n\nDo you want to continue?"})
|
||||
private class DetailsUpdateTask extends VisualizationRefreshTask<Interval> {
|
||||
private class DetailsUpdateTask extends ViewRefreshTask<Interval> {
|
||||
|
||||
DetailsUpdateTask() {
|
||||
super(Bundle.DetailViewPane_loggedTask_name(), true);
|
||||
@ -409,7 +408,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
|
||||
currentZoomParams = newZoomParams;
|
||||
|
||||
//clear the chart and set the horixontal axis
|
||||
resetChart(eventsModel.getTimeRange());
|
||||
resetView(eventsModel.getTimeRange());
|
||||
|
||||
updateMessage(Bundle.DetailViewPane_loggedTask_updateUI());
|
||||
|
||||
@ -433,9 +432,15 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDateAxisValues(Interval timeRange) {
|
||||
protected void setDateValues(Interval timeRange) {
|
||||
detailsChartDateAxis.setRange(timeRange, true);
|
||||
pinnedDateAxis.setRange(timeRange, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
super.succeeded();
|
||||
layoutDateLabels();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -432,8 +432,8 @@ final class DetailsChart extends Control implements TimeLineChart<DateTime> {
|
||||
configureMouseListeners(pinnedLane, mouseClickedHandler, chartDragHandler);
|
||||
|
||||
//show and hide pinned lane in response to settings property change
|
||||
getSkinnable().getLayoutSettings().pinnedLaneShowing().addListener(observable -> syncPinnedShowing());
|
||||
syncPinnedShowing();
|
||||
getSkinnable().getLayoutSettings().pinnedLaneShowing().addListener(observable -> syncPinnedLaneShowing());
|
||||
syncPinnedLaneShowing();
|
||||
|
||||
//show and remove interval selector in sync with control state change
|
||||
getSkinnable().intervalSelectorProp.addListener((observable, oldIntervalSelector, newIntervalSelector) -> {
|
||||
@ -484,7 +484,7 @@ final class DetailsChart extends Control implements TimeLineChart<DateTime> {
|
||||
* Show the pinned lane if and only if the settings object says it
|
||||
* should be.
|
||||
*/
|
||||
private void syncPinnedShowing() {
|
||||
private void syncPinnedLaneShowing() {
|
||||
boolean pinnedLaneShowing = getSkinnable().getLayoutSettings().isPinnedLaneShowing();
|
||||
if (pinnedLaneShowing == false) {
|
||||
//Save the divider position for later.
|
||||
|
@ -58,11 +58,11 @@ import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||
import org.sleuthkit.autopsy.timeline.filters.DescriptionFilter;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
|
||||
import org.sleuthkit.autopsy.timeline.ui.ContextMenuProvider;
|
||||
|
||||
/**
|
||||
* One "lane" of a the details visualization, contains all the core logic and
|
||||
* One "lane" of a the details view, contains all the core logic and
|
||||
* layout code.
|
||||
*
|
||||
* NOTE: It was too hard to control the threading of this chart via the
|
||||
@ -178,7 +178,7 @@ abstract class DetailsChartLane<Y extends TimeLineEvent> extends XYChart<DateTim
|
||||
//add a dummy series or the chart is never rendered
|
||||
setData(FXCollections.observableList(Arrays.asList(new Series<DateTime, Y>())));
|
||||
|
||||
Tooltip.install(this, AbstractVisualizationPane.getDefaultTooltip());
|
||||
Tooltip.install(this, AbstractTimelineChart.getDefaultTooltip());
|
||||
|
||||
dateAxis.setAutoRanging(false);
|
||||
setLegendVisible(false);
|
||||
|
@ -77,7 +77,7 @@ import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.events.TagsAddedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.events.TagsDeletedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
|
||||
import org.sleuthkit.autopsy.timeline.ui.ContextMenuProvider;
|
||||
import static org.sleuthkit.autopsy.timeline.ui.detailview.EventNodeBase.show;
|
||||
import static org.sleuthkit.autopsy.timeline.ui.detailview.MultiEventNodeBase.CORNER_RADII_3;
|
||||
@ -167,7 +167,7 @@ public abstract class EventNodeBase<Type extends TimeLineEvent> extends StackPan
|
||||
|
||||
//set up mouse hover effect and tooltip
|
||||
setOnMouseEntered(mouseEntered -> {
|
||||
Tooltip.uninstall(chartLane, AbstractVisualizationPane.getDefaultTooltip());
|
||||
Tooltip.uninstall(chartLane, AbstractTimelineChart.getDefaultTooltip());
|
||||
showHoverControls(true);
|
||||
toFront();
|
||||
});
|
||||
@ -176,7 +176,7 @@ public abstract class EventNodeBase<Type extends TimeLineEvent> extends StackPan
|
||||
if (parentNode != null) {
|
||||
parentNode.showHoverControls(true);
|
||||
} else {
|
||||
Tooltip.install(chartLane, AbstractVisualizationPane.getDefaultTooltip());
|
||||
Tooltip.install(chartLane, AbstractTimelineChart.getDefaultTooltip());
|
||||
}
|
||||
});
|
||||
setOnMouseClicked(new ClickHandler());
|
||||
|
@ -25,7 +25,7 @@ import javafx.scene.shape.Line;
|
||||
import org.joda.time.DateTime;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimelineChart;
|
||||
|
||||
/**
|
||||
* Subclass of {@link Line} with appropriate behavior (mouse listeners) to act
|
||||
@ -35,7 +35,7 @@ import org.sleuthkit.autopsy.timeline.ui.AbstractVisualizationPane;
|
||||
"GuideLine.tooltip.text={0}\nRight-click to remove.\nDrag to reposition."})
|
||||
class GuideLine extends Line {
|
||||
|
||||
private static final Tooltip CHART_DEFAULT_TOOLTIP = AbstractVisualizationPane.getDefaultTooltip();
|
||||
private final Tooltip CHART_DEFAULT_TOOLTIP = AbstractTimelineChart.getDefaultTooltip();
|
||||
|
||||
private final Tooltip tooltip = new Tooltip();
|
||||
private final DetailsChart chart;
|
||||
|
@ -41,7 +41,6 @@ import org.controlsfx.control.action.ActionUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||
@ -129,20 +128,29 @@ final public class FilterSetPanel extends BorderPane {
|
||||
hiddenDescriptionsListView.setItems(controller.getQuickHideFilters());
|
||||
hiddenDescriptionsListView.setCellFactory(listView -> getNewDiscriptionFilterListCell());
|
||||
|
||||
controller.visualizationModeProperty().addListener(observable -> {
|
||||
//show and hide the "hidden descriptions" panel depending on the current view mode
|
||||
controller.viewModeProperty().addListener(observable -> {
|
||||
applyFilters();
|
||||
if (controller.visualizationModeProperty().get() == VisualizationMode.COUNTS) {
|
||||
dividerPosition = splitPane.getDividerPositions()[0];
|
||||
splitPane.setDividerPositions(1);
|
||||
hiddenDescriptionsPane.setExpanded(false);
|
||||
hiddenDescriptionsPane.setCollapsible(false);
|
||||
hiddenDescriptionsPane.setDisable(true);
|
||||
} else {
|
||||
splitPane.setDividerPositions(dividerPosition);
|
||||
hiddenDescriptionsPane.setDisable(false);
|
||||
hiddenDescriptionsPane.setCollapsible(true);
|
||||
hiddenDescriptionsPane.setExpanded(true);
|
||||
hiddenDescriptionsPane.setCollapsible(false);
|
||||
switch (controller.getViewMode()) {
|
||||
case COUNTS:
|
||||
case LIST:
|
||||
//hide for counts and lists, but remember divider position
|
||||
dividerPosition = splitPane.getDividerPositions()[0];
|
||||
splitPane.setDividerPositions(1);
|
||||
hiddenDescriptionsPane.setExpanded(false);
|
||||
hiddenDescriptionsPane.setCollapsible(false);
|
||||
hiddenDescriptionsPane.setDisable(true);
|
||||
break;
|
||||
case DETAIL:
|
||||
//show and restore divider position.
|
||||
splitPane.setDividerPositions(dividerPosition);
|
||||
hiddenDescriptionsPane.setDisable(false);
|
||||
hiddenDescriptionsPane.setCollapsible(true);
|
||||
hiddenDescriptionsPane.setExpanded(true);
|
||||
hiddenDescriptionsPane.setCollapsible(false);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown ViewMode: " + controller.getViewMode());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TableColumn?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
|
||||
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<top>
|
||||
<HBox alignment="CENTER" BorderPane.alignment="CENTER">
|
||||
<children>
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
<Label fx:id="eventCountLabel" text=" # of events" />
|
||||
</children>
|
||||
<BorderPane.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</BorderPane.margin>
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<TableView fx:id="table" tableMenuButtonVisible="true" BorderPane.alignment="CENTER">
|
||||
<columns>
|
||||
<TableColumn fx:id="dateTimeColumn" editable="false" maxWidth="200.0" minWidth="150.0" prefWidth="150.0" resizable="false" sortable="false" text="Date/Time" />
|
||||
<TableColumn fx:id="typeColumn" editable="false" maxWidth="100.0" minWidth="100.0" prefWidth="100.0" sortable="false" text="Event Type" />
|
||||
<TableColumn fx:id="descriptionColumn" editable="false" maxWidth="3000.0" minWidth="100.0" prefWidth="300.0" sortable="false" text="Description" />
|
||||
<TableColumn fx:id="knownColumn" editable="false" maxWidth="75.0" minWidth="75.0" prefWidth="75.0" resizable="false" sortable="false" text="Known" />
|
||||
<TableColumn fx:id="idColumn" editable="false" maxWidth="50.0" minWidth="50.0" prefWidth="50.0" resizable="false" sortable="false" text="ID" />
|
||||
<TableColumn fx:id="taggedColumn" maxWidth="75.0" minWidth="75.0" prefWidth="75.0" resizable="false" text="Tagged" />
|
||||
<TableColumn fx:id="hashHitColumn" maxWidth="75.0" minWidth="75.0" prefWidth="75.0" resizable="false" text="Hash Hit" />
|
||||
</columns>
|
||||
<columnResizePolicy>
|
||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
|
||||
</columnResizePolicy>
|
||||
</TableView>
|
||||
</center>
|
||||
</fx:root>
|
@ -0,0 +1,575 @@
|
||||
/*
|
||||
* 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.ui.listvew;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
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.StringBinding;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.OverrunStyle;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableRow;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Callback;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.JMenuItem;
|
||||
import org.controlsfx.control.Notifications;
|
||||
import org.openide.awt.Actions;
|
||||
import org.openide.util.NbBundle;
|
||||
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.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.SingleEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.BaseTypes;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.FileSystemTypes;
|
||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventNode;
|
||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* The inner component that makes up the List view. Manages the TableView.
|
||||
*/
|
||||
class ListTimeline extends BorderPane {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(ListTimeline.class.getName());
|
||||
|
||||
private static final Image HASH_HIT = new Image("/org/sleuthkit/autopsy/images/hashset_hits.png"); //NON-NLS
|
||||
private static final Image TAG = new Image("/org/sleuthkit/autopsy/images/green-tag-icon-16.png"); //NON-NLS
|
||||
|
||||
/**
|
||||
* call-back used to wrap the CombinedEvent in a ObservableValue
|
||||
*/
|
||||
private static final Callback<TableColumn.CellDataFeatures<CombinedEvent, CombinedEvent>, ObservableValue<CombinedEvent>> CELL_VALUE_FACTORY = param -> new SimpleObjectProperty<>(param.getValue());
|
||||
|
||||
@FXML
|
||||
private Label eventCountLabel;
|
||||
@FXML
|
||||
private TableView<CombinedEvent> table;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> idColumn;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> dateTimeColumn;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> descriptionColumn;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> typeColumn;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> knownColumn;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> taggedColumn;
|
||||
@FXML
|
||||
private TableColumn<CombinedEvent, CombinedEvent> hashHitColumn;
|
||||
|
||||
/**
|
||||
* Observable list used to track selected events.
|
||||
*/
|
||||
private final ObservableList<Long> selectedEventIDs = FXCollections.observableArrayList();
|
||||
|
||||
private final TimeLineController controller;
|
||||
private final SleuthkitCase sleuthkitCase;
|
||||
private final TagsManager tagsManager;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller The controller for this timeline
|
||||
*/
|
||||
ListTimeline(TimeLineController controller) {
|
||||
this.controller = controller;
|
||||
sleuthkitCase = controller.getAutopsyCase().getSleuthkitCase();
|
||||
tagsManager = controller.getAutopsyCase().getServices().getTagsManager();
|
||||
FXMLConstructor.construct(this, ListTimeline.class, "ListTimeline.fxml"); //NON-NLS
|
||||
}
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages({
|
||||
"# {0} - the number of events",
|
||||
"ListTimeline.eventCountLabel.text={0} events"})
|
||||
void initialize() {
|
||||
assert eventCountLabel != null : "fx:id=\"eventCountLabel\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
assert table != null : "fx:id=\"table\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
assert idColumn != null : "fx:id=\"idColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
assert dateTimeColumn != null : "fx:id=\"dateTimeColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
assert descriptionColumn != null : "fx:id=\"descriptionColumn\" was not injected: check your FXML file 'ListViewPane.fxml'."; //NON-NLS
|
||||
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
|
||||
|
||||
//override default row with one that provides context menus
|
||||
table.setRowFactory(tableView -> new EventRow());
|
||||
|
||||
//remove idColumn (can be restored for debugging).
|
||||
table.getColumns().remove(idColumn);
|
||||
|
||||
//// set up cell and cell-value factories for columns
|
||||
dateTimeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||
dateTimeColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
|
||||
TimeLineController.getZonedFormatter().print(singleEvent.getStartMillis())));
|
||||
|
||||
descriptionColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||
descriptionColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
|
||||
singleEvent.getDescription(DescriptionLoD.FULL)));
|
||||
|
||||
typeColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||
typeColumn.setCellFactory(col -> new EventTypeCell());
|
||||
|
||||
knownColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||
knownColumn.setCellFactory(col -> new TextEventTableCell(singleEvent ->
|
||||
singleEvent.getKnown().getName()));
|
||||
|
||||
taggedColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||
taggedColumn.setCellFactory(col -> new TaggedCell());
|
||||
|
||||
hashHitColumn.setCellValueFactory(CELL_VALUE_FACTORY);
|
||||
hashHitColumn.setCellFactory(col -> new HashHitCell());
|
||||
|
||||
//bind event count label to number of items in the table
|
||||
eventCountLabel.textProperty().bind(new StringBinding() {
|
||||
{
|
||||
bind(table.getItems());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String computeValue() {
|
||||
return Bundle.ListTimeline_eventCountLabel_text(table.getItems().size());
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Collection of CombinedEvents to show in the table.
|
||||
*
|
||||
* @param events The Collection of events to sho in the table.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
void setCombinedEvents(Collection<CombinedEvent> events) {
|
||||
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.
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* TableCell to show the (sub) type of an event.
|
||||
*/
|
||||
private class EventTypeCell extends EventTableCell {
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ListView.EventTypeCell.modifiedTooltip=File Modified ( M )",
|
||||
"ListView.EventTypeCell.accessedTooltip=File Accessed ( A )",
|
||||
"ListView.EventTypeCell.createdTooltip=File Created ( B, for Born )",
|
||||
"ListView.EventTypeCell.changedTooltip=File Changed ( C )"
|
||||
})
|
||||
@Override
|
||||
protected void updateItem(CombinedEvent item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
setTooltip(null);
|
||||
} else {
|
||||
if (item.getEventTypes().stream().allMatch(eventType -> eventType instanceof FileSystemTypes)) {
|
||||
String typeString = ""; //NON-NLS
|
||||
VBox toolTipVbox = new VBox(5);
|
||||
|
||||
for (FileSystemTypes type : Arrays.asList(FileSystemTypes.FILE_MODIFIED, FileSystemTypes.FILE_ACCESSED, FileSystemTypes.FILE_CHANGED, FileSystemTypes.FILE_CREATED)) {
|
||||
if (item.getEventTypes().contains(type)) {
|
||||
switch (type) {
|
||||
case FILE_MODIFIED:
|
||||
typeString += "M"; //NON-NLS
|
||||
toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_modifiedTooltip(), new ImageView(type.getFXImage())));
|
||||
break;
|
||||
case FILE_ACCESSED:
|
||||
typeString += "A"; //NON-NLS
|
||||
toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_accessedTooltip(), new ImageView(type.getFXImage())));
|
||||
break;
|
||||
case FILE_CREATED:
|
||||
typeString += "B"; //NON-NLS
|
||||
toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_createdTooltip(), new ImageView(type.getFXImage())));
|
||||
break;
|
||||
case FILE_CHANGED:
|
||||
typeString += "C"; //NON-NLS
|
||||
toolTipVbox.getChildren().add(new Label(Bundle.ListView_EventTypeCell_changedTooltip(), new ImageView(type.getFXImage())));
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown FileSystemType: " + type.name()); //NON-NLS
|
||||
}
|
||||
} else {
|
||||
typeString += "_"; //NON-NLS
|
||||
}
|
||||
}
|
||||
setText(typeString);
|
||||
setGraphic(new ImageView(BaseTypes.FILE_SYSTEM.getFXImage()));
|
||||
Tooltip tooltip = new Tooltip();
|
||||
tooltip.setGraphic(toolTipVbox);
|
||||
setTooltip(tooltip);
|
||||
|
||||
} else {
|
||||
EventType eventType = Iterables.getOnlyElement(item.getEventTypes());
|
||||
setText(eventType.getDisplayName());
|
||||
setGraphic(new ImageView(eventType.getFXImage()));
|
||||
setTooltip(new Tooltip(eventType.getDisplayName()));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A TableCell that shows information about the tags applied to a event.
|
||||
*/
|
||||
private class TaggedCell extends EventTableCell {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
TaggedCell() {
|
||||
setAlignment(Pos.CENTER);
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ListTimeline.taggedTooltip.error=There was a problem getting the tag names for the selected event.",
|
||||
"# {0} - tag names",
|
||||
"ListTimeline.taggedTooltip.text=Tags:\n{0}"})
|
||||
@Override
|
||||
protected void updateItem(CombinedEvent item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null || (getEvent().isTagged() == false)) {
|
||||
setGraphic(null);
|
||||
setTooltip(null);
|
||||
} else {
|
||||
/*
|
||||
* if the cell is not empty and the event is tagged, show the
|
||||
* tagged icon, and show a list of tag names in the tooltip
|
||||
*/
|
||||
setGraphic(new ImageView(TAG));
|
||||
|
||||
SortedSet<String> tagNames = new TreeSet<>();
|
||||
try {
|
||||
//get file tags
|
||||
AbstractFile abstractFileById = sleuthkitCase.getAbstractFileById(getEvent().getFileID());
|
||||
tagsManager.getContentTagsByContent(abstractFileById).stream()
|
||||
.map(tag -> tag.getName().getDisplayName())
|
||||
.forEach(tagNames::add);
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup tags for obj id " + getEvent().getFileID(), ex); //NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
Notifications.create()
|
||||
.owner(getScene().getWindow())
|
||||
.text(Bundle.ListTimeline_taggedTooltip_error())
|
||||
.showError();
|
||||
});
|
||||
}
|
||||
getEvent().getArtifactID().ifPresent(artifactID -> {
|
||||
//get artifact tags, if there is an artifact associated with the event.
|
||||
try {
|
||||
BlackboardArtifact artifact = sleuthkitCase.getBlackboardArtifact(artifactID);
|
||||
tagsManager.getBlackboardArtifactTagsByArtifact(artifact).stream()
|
||||
.map(tag -> tag.getName().getDisplayName())
|
||||
.forEach(tagNames::add);
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup tags for artifact id " + artifactID, ex); //NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
Notifications.create()
|
||||
.owner(getScene().getWindow())
|
||||
.text(Bundle.ListTimeline_taggedTooltip_error())
|
||||
.showError();
|
||||
});
|
||||
}
|
||||
});
|
||||
Tooltip tooltip = new Tooltip(Bundle.ListTimeline_taggedTooltip_text(String.join("\n", tagNames))); //NON-NLS
|
||||
tooltip.setGraphic(new ImageView(TAG));
|
||||
setTooltip(tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TableCell to show the hash hits if any associated with the file backing
|
||||
* an event.
|
||||
*/
|
||||
private class HashHitCell extends EventTableCell {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
HashHitCell() {
|
||||
setAlignment(Pos.CENTER);
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ListTimeline.hashHitTooltip.error=There was a problem getting the hash set names for the selected event.",
|
||||
"# {0} - hash set names",
|
||||
"ListTimeline.hashHitTooltip.text=Hash Sets:\n{0}"})
|
||||
@Override
|
||||
protected void updateItem(CombinedEvent item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null || (getEvent().isHashHit() == false)) {
|
||||
setGraphic(null);
|
||||
setTooltip(null);
|
||||
} else {
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
setGraphic(new ImageView(HASH_HIT));
|
||||
try {
|
||||
Set<String> hashSetNames = new TreeSet<>(sleuthkitCase.getAbstractFileById(getEvent().getFileID()).getHashSetNames());
|
||||
Tooltip tooltip = new Tooltip(Bundle.ListTimeline_hashHitTooltip_text(String.join("\n", hashSetNames))); //NON-NLS
|
||||
tooltip.setGraphic(new ImageView(HASH_HIT));
|
||||
setTooltip(tooltip);
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup hash set names for obj id " + getEvent().getFileID(), ex); //NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
Notifications.create()
|
||||
.owner(getScene().getWindow())
|
||||
.text(Bundle.ListTimeline_hashHitTooltip_error())
|
||||
.showError();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TableCell to show text derived from a SingleEvent by the given Function.
|
||||
*/
|
||||
private class TextEventTableCell extends EventTableCell {
|
||||
|
||||
private final Function<SingleEvent, String> textSupplier;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param textSupplier Function that takes a SingleEvent and produces a
|
||||
* String to show in this TableCell.
|
||||
*/
|
||||
TextEventTableCell(Function<SingleEvent, String> textSupplier) {
|
||||
this.textSupplier = textSupplier;
|
||||
setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
|
||||
setEllipsisString(" ... "); //NON-NLS
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(CombinedEvent item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(textSupplier.apply(getEvent()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for TableCells that represent a MergedEvent by way of a
|
||||
* representative SingleEvent.
|
||||
*/
|
||||
private abstract class EventTableCell extends TableCell<CombinedEvent, CombinedEvent> {
|
||||
|
||||
private SingleEvent event;
|
||||
|
||||
/**
|
||||
* Get the representative SingleEvent for this cell.
|
||||
*
|
||||
* @return The representative SingleEvent for this cell.
|
||||
*/
|
||||
SingleEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(CombinedEvent item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
event = null;
|
||||
} else {
|
||||
//stash the event in the cell for derived classed to use.
|
||||
event = controller.getEventsModel().getEventById(item.getRepresentativeEventID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TableRow that adds a right-click context menu.
|
||||
*/
|
||||
private class EventRow extends TableRow<CombinedEvent> {
|
||||
|
||||
private SingleEvent event;
|
||||
|
||||
/**
|
||||
* Get the representative SingleEvent for this row .
|
||||
*
|
||||
* @return The representative SingleEvent for this row .
|
||||
*/
|
||||
SingleEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ListChart.errorMsg=There was a problem getting the content for the selected event."})
|
||||
@Override
|
||||
protected void updateItem(CombinedEvent item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
event = null;
|
||||
} else {
|
||||
event = controller.getEventsModel().getEventById(item.getRepresentativeEventID());
|
||||
|
||||
setOnContextMenuRequested(contextMenuEvent -> {
|
||||
//make a new context menu on each request in order to include uptodate tag names and hash sets
|
||||
try {
|
||||
EventNode node = EventNode.createEventNode(item.getRepresentativeEventID(), controller.getEventsModel());
|
||||
List<MenuItem> menuItems = new ArrayList<>();
|
||||
|
||||
//for each actions avaialable on node, make a menu item.
|
||||
for (Action action : node.getActions(false)) {
|
||||
if (action == null) {
|
||||
// swing/netbeans uses null action to represent separator in menu
|
||||
menuItems.add(new SeparatorMenuItem());
|
||||
} else {
|
||||
String actionName = Objects.toString(action.getValue(Action.NAME));
|
||||
//for now, suppress properties and tools actions, by ignoring them
|
||||
if (Arrays.asList("&Properties", "Tools").contains(actionName) == false) { //NON-NLS
|
||||
if (action instanceof Presenter.Popup) {
|
||||
/*
|
||||
* If the action is really the root of a
|
||||
* set of actions (eg, tagging). Make a
|
||||
* menu that parallels the action's
|
||||
* menu.
|
||||
*/
|
||||
JMenuItem submenu = ((Presenter.Popup) action).getPopupPresenter();
|
||||
menuItems.add(SwingFXMenuUtils.createFXMenu(submenu));
|
||||
} else {
|
||||
menuItems.add(SwingFXMenuUtils.createFXMenu(new Actions.MenuItem(action, false)));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//show new context menu.
|
||||
new ContextMenu(menuItems.toArray(new MenuItem[menuItems.size()]))
|
||||
.show(this, contextMenuEvent.getScreenX(), contextMenuEvent.getScreenY());
|
||||
} catch (IllegalStateException ex) {
|
||||
//Since the case is closed, the user probably doesn't care about this, just log it as a precaution.
|
||||
LOGGER.log(Level.SEVERE, "There was no case open to lookup the Sleuthkit object backing a SingleEvent.", ex); //NON-NLS
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to lookup Sleuthkit object backing a SingleEvent.", ex); //NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
Notifications.create()
|
||||
.owner(getScene().getWindow())
|
||||
.text(Bundle.ListChart_errorMsg())
|
||||
.showError();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.timeline.ui.listvew;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.scene.Parent;
|
||||
import org.joda.time.Interval;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.CombinedEvent;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.ui.AbstractTimeLineView;
|
||||
|
||||
/**
|
||||
* An AbstractTimeLineView that uses a TableView to display events.
|
||||
*/
|
||||
public class ListViewPane extends AbstractTimeLineView {
|
||||
|
||||
private final ListTimeline listTimeline;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param controller
|
||||
*/
|
||||
public ListViewPane(TimeLineController controller) {
|
||||
super(controller);
|
||||
listTimeline = new ListTimeline(controller);
|
||||
|
||||
//initialize chart;
|
||||
setCenter(listTimeline);
|
||||
setSettingsNodes(new ListViewPane.ListViewSettingsPane().getChildrenUnmodifiable());
|
||||
|
||||
//keep controller's list of selected event IDs in sync with this list's
|
||||
listTimeline.getSelectedEventIDs().addListener((Observable selectedIDs) -> {
|
||||
controller.selectEventIDs(listTimeline.getSelectedEventIDs());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task<Boolean> getNewUpdateTask() {
|
||||
return new ListUpdateTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void clearData() {
|
||||
listTimeline.clear();
|
||||
}
|
||||
|
||||
private static class ListViewSettingsPane extends Parent {
|
||||
}
|
||||
|
||||
private class ListUpdateTask extends ViewRefreshTask<Interval> {
|
||||
|
||||
ListUpdateTask() {
|
||||
super("List update task", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean call() throws Exception {
|
||||
super.call();
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
FilteredEventsModel eventsModel = getEventsModel();
|
||||
|
||||
//grab the currently selected event
|
||||
HashSet<CombinedEvent> selectedEvents = new HashSet<>(listTimeline.getSelectedEvents());
|
||||
|
||||
//clear the chart and set the time range.
|
||||
resetView(eventsModel.getTimeRange());
|
||||
|
||||
//get the combined events to be displayed
|
||||
updateMessage("Querying DB for events");
|
||||
List<CombinedEvent> combinedEvents = eventsModel.getCombinedEvents();
|
||||
|
||||
updateMessage("Updating UI");
|
||||
Platform.runLater(() -> {
|
||||
//put the combined events into the table.
|
||||
listTimeline.setCombinedEvents(combinedEvents);
|
||||
//restore the selected event
|
||||
listTimeline.selectEvents(selectedEvents);
|
||||
});
|
||||
|
||||
return combinedEvents.isEmpty() == false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
super.cancelled();
|
||||
getController().retreat();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDateValues(Interval timeRange) {
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.ui.listvew;
|
||||
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
import javax.swing.MenuElement;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
/**
|
||||
* Allows creation of JavaFX menus with the same structure as Swing menus and
|
||||
* which invoke the same actions.
|
||||
*/
|
||||
public class SwingFXMenuUtils extends MenuItem {
|
||||
|
||||
/**
|
||||
* Factory method that creates a JavaFX MenuItem backed by a MenuElement
|
||||
*
|
||||
* @param jMenuElement The MenuElement to create a JavaFX menu for.
|
||||
*
|
||||
* @return a MenuItem for the given MenuElement
|
||||
*/
|
||||
public static MenuItem createFXMenu(MenuElement jMenuElement) {
|
||||
if (jMenuElement == null) {
|
||||
//Since null is sometime used to represenet a seperator, follow that convention.
|
||||
return new SeparatorMenuItem();
|
||||
} else if (jMenuElement instanceof JMenu) {
|
||||
return new MenuAdapter((JMenu) jMenuElement);
|
||||
} else if (jMenuElement instanceof JPopupMenu) {
|
||||
return new MenuAdapter((JPopupMenu) jMenuElement);
|
||||
} else {
|
||||
return new MenuItemAdapter((JMenuItem) jMenuElement);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A JavaFX MenuItem that invokes the backing JMenuItem when clicked.
|
||||
*/
|
||||
private static class MenuItemAdapter extends MenuItem {
|
||||
|
||||
private MenuItemAdapter(final JMenuItem jMenuItem) {
|
||||
super(jMenuItem.getText());
|
||||
setOnAction(actionEvent -> SwingUtilities.invokeLater(jMenuItem::doClick));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A JavaFX Menu that has the same structure as a given Swing JMenu or
|
||||
* JPopupMenu.
|
||||
*/
|
||||
private static class MenuAdapter extends Menu {
|
||||
|
||||
/**
|
||||
* Constructor for JMenu
|
||||
*
|
||||
* @param jMenu The JMenu to parallel in this Menu.
|
||||
*/
|
||||
MenuAdapter(final JMenu jMenu) {
|
||||
super(jMenu.getText());
|
||||
populateSubMenus(jMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for JPopupMenu
|
||||
*
|
||||
* @param jPopupMenu The JPopupMenu to parallel in this Menu.
|
||||
*/
|
||||
MenuAdapter(JPopupMenu jPopupMenu) {
|
||||
super(jPopupMenu.getLabel());
|
||||
populateSubMenus(jPopupMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the sub menus of this menu.
|
||||
*
|
||||
* @param menu The MenuElement whose sub elements will be used to
|
||||
* populate the sub menus of this menu.
|
||||
*/
|
||||
private void populateSubMenus(MenuElement menu) {
|
||||
for (MenuElement menuElement : menu.getSubElements()) {
|
||||
if (menuElement == null) {
|
||||
//Since null is sometime used to represenet a seperator, follow that convention.
|
||||
getItems().add(new SeparatorMenuItem());
|
||||
|
||||
} else if (menuElement instanceof JMenuItem) {
|
||||
getItems().add(SwingFXMenuUtils.createFXMenu(menuElement));
|
||||
|
||||
} else if (menuElement instanceof JPopupMenu) {
|
||||
populateSubMenus(menuElement);
|
||||
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Unown MenuElement subclass: " + menuElement.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
@ -31,7 +32,7 @@ import javafx.util.StringConverter;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||
import org.sleuthkit.autopsy.timeline.ViewMode;
|
||||
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||
|
||||
@ -102,15 +103,14 @@ public class ZoomSettingsPane extends TitledPane {
|
||||
Function.identity());
|
||||
descrLODLabel.setText(Bundle.ZoomSettingsPane_descrLODLabel_text());
|
||||
//the description slider is only usefull in the detail view
|
||||
descrLODSlider.disableProperty().bind(controller.visualizationModeProperty().isEqualTo(VisualizationMode.COUNTS));
|
||||
descrLODSlider.disableProperty().bind(controller.viewModeProperty().isEqualTo(ViewMode.COUNTS));
|
||||
|
||||
/**
|
||||
* In order for the selected value in the time unit slider to correspond
|
||||
* to the amount of time used as units along the x-axis of the
|
||||
* visualization, and since we don't want to show "forever" as a time
|
||||
* unit, the range of the slider is restricted, and there is an offset
|
||||
* of 1 between the "real" value, and what is shown in the slider
|
||||
* labels.
|
||||
* to the amount of time used as units along the x-axis of the view, and
|
||||
* since we don't want to show "forever" as a time unit, the range of
|
||||
* the slider is restricted, and there is an offset of 1 between the
|
||||
* "real" value, and what is shown in the slider labels.
|
||||
*/
|
||||
timeUnitSlider.setMax(TimeUnits.values().length - 2);
|
||||
configureSliderListeners(timeUnitSlider,
|
||||
@ -121,6 +121,12 @@ public class ZoomSettingsPane extends TitledPane {
|
||||
modelTimeRange -> RangeDivisionInfo.getRangeDivisionInfo(modelTimeRange).getPeriodSize().ordinal() - 1,
|
||||
index -> index + 1); //compensate for the -1 above when mapping to the Enum whose displayName will be shown at index
|
||||
timeUnitLabel.setText(Bundle.ZoomSettingsPane_timeUnitLabel_text());
|
||||
|
||||
//hide the whole panel in list mode
|
||||
BooleanBinding notListMode = controller.viewModeProperty().isNotEqualTo(ViewMode.LIST);
|
||||
visibleProperty().bind(notListMode);
|
||||
managedProperty().bind(notListMode);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -176,7 +182,7 @@ public class ZoomSettingsPane extends TitledPane {
|
||||
//set the tick labels to the enum displayNames
|
||||
slider.setLabelFormatter(new EnumSliderLabelFormatter<>(enumClass, labelIndexMapper));
|
||||
|
||||
//make a listener to responds to slider value changes (by updating the visualization)
|
||||
//make a listener to responds to slider value changes (by updating the view)
|
||||
final InvalidationListener sliderListener = observable -> {
|
||||
//only process event if the slider value is not changing (user has released slider thumb)
|
||||
if (slider.isValueChanging() == false) {
|
||||
|
@ -14,6 +14,14 @@
|
||||
<specification-version>8.25.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.openide.util.ui</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>9.4.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
</module-dependencies>
|
||||
<test-dependencies>
|
||||
<test-type>
|
||||
|
@ -15,6 +15,22 @@
|
||||
<specification-version>1.32.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.netbeans.api.progress.compat8</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>1.46.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.netbeans.api.progress.nb</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>1.46.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.netbeans.modules.options.api</code-name-base>
|
||||
<build-prerequisite/>
|
||||
@ -89,6 +105,14 @@
|
||||
<specification-version>8.19.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.openide.util.ui</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>9.4.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.openide.windows</code-name-base>
|
||||
<build-prerequisite/>
|
||||
|
@ -44,7 +44,7 @@ public final class OpenHelpAction implements ActionListener {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
try {
|
||||
Desktop.getDesktop().browse(URI.create("http://sleuthkit.org/autopsy/docs/user-docs/4.0/image_gallery_page.html")); //NON-NLS
|
||||
Desktop.getDesktop().browse(URI.create("http://sleuthkit.org/autopsy/docs/user-docs/4.1/image_gallery_page.html")); //NON-NLS
|
||||
} catch (IOException ex) {
|
||||
Logger.getLogger(OpenHelpAction.class.getName()).log(Level.SEVERE, "failed to open help page", ex); //NON-NLS
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javax.swing.JMenu;
|
||||
@ -34,9 +33,7 @@ public class SwingMenuItemAdapter extends MenuItem {
|
||||
SwingMenuItemAdapter(final JMenuItem jMenuItem) {
|
||||
super(jMenuItem.getText());
|
||||
this.jMenuItem = jMenuItem;
|
||||
setOnAction((ActionEvent t) -> {
|
||||
jMenuItem.doClick();
|
||||
});
|
||||
setOnAction(actionEvent -> jMenuItem.doClick());
|
||||
}
|
||||
|
||||
public static MenuItem create(MenuElement jmenuItem) {
|
||||
|
@ -15,6 +15,22 @@
|
||||
<specification-version>1.24.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.netbeans.api.progress.compat8</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>1.46.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.netbeans.api.progress.nb</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>1.46.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.netbeans.modules.options.api</code-name-base>
|
||||
<build-prerequisite/>
|
||||
@ -81,6 +97,14 @@
|
||||
<specification-version>8.8.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.openide.util.ui</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>9.4.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.openide.windows</code-name-base>
|
||||
<build-prerequisite/>
|
||||
|
@ -59,7 +59,6 @@ AbstractKeywordSearchPerformer.search.emptyKeywordErrorBody=Keyword list is empt
|
||||
AbstractKeywordSearchPerformer.search.noFilesInIdxMsg=<html>No files are in index yet. <br />Try again later. Index is updated every {0} minutes.</html>
|
||||
AbstractKeywordSearchPerformer.search.noFilesIdxdMsg=<html>No files were indexed.<br />Re-ingest the image with the Keyword Search Module enabled. </html>
|
||||
ExtractedContentPanel.setMarkup.panelTxt=<span style\='font-style\:italic'>Loading text... Please wait</span>
|
||||
ExtractedContentViewer.toString=Extracted Text
|
||||
ExtractedContentViewer.toolTip=Displays extracted text from files and keyword-search results. Requires Keyword Search ingest to be run on a file to activate this viewer.
|
||||
ExtractedContentViewer.getTitle=Indexed Text
|
||||
ExtractedContentViewer.getSolrContent.knownFileMsg=<p style\=''font-style\:italic''>{0} is a known file (based on MD5 hash) and does not have text in the index.</p>
|
||||
|
@ -47,7 +47,6 @@ AbstractKeywordSearchPerformer.search.emptyKeywordErrorBody=\u30ad\u30fc\u30ef\u
|
||||
AbstractKeywordSearchPerformer.search.noFilesInIdxMsg=<html>\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306b\u307e\u3060\u30d5\u30a1\u30a4\u30eb\u304c\u3042\u308a\u307e\u305b\u3093\u3002<br />\u3057\u3070\u3089\u304f\u3057\u3066\u304b\u3089\u518d\u5ea6\u5b9f\u884c\u3057\u3066\u304f\u3060\u3055\u3044\u3002\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306f{0}\u5206\u3054\u3068\u306b\u66f4\u65b0\u3055\u308c\u307e\u3059\u3002</html>
|
||||
AbstractKeywordSearchPerformer.search.noFilesIdxdMsg=<html>\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u5316\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb\u304c\u3042\u308a\u307e\u305b\u3093\u3002<br />\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30e2\u30b8\u30e5\u30fc\u30eb\u3092\u6709\u52b9\u5316\u3057\u3066\u30a4\u30e1\u30fc\u30b8\u3092\u518d\u5ea6\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3002</html>
|
||||
ExtractedContentPanel.setMarkup.panelTxt=<span style\='font-style\:italic'>\u30c6\u30ad\u30b9\u30c8\u30ed\u30fc\u30c9\u4e2d...\u3057\u3070\u3089\u304f\u304a\u5f85\u3061\u304f\u3060\u3055\u3044\u3002</span>
|
||||
ExtractedContentViewer.toString=\u62bd\u51fa\u3055\u308c\u305f\u30c6\u30ad\u30b9\u30c8
|
||||
ExtractedContentViewer.toolTip=\u30d5\u30a1\u30a4\u30eb\u3084\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u7d50\u679c\u304b\u3089\u62bd\u51fa\u3055\u308c\u305f\u30c6\u30ad\u30b9\u30c8\u3092\u8868\u793a\u3002\u3053\u306e\u30d3\u30e5\u30fc\u30a2\u3092\u6709\u52b9\u5316\u3059\u308b\u306b\u306f\u3001\u30d5\u30a1\u30a4\u30eb\u306b\u5bfe\u3057\u3066\u30ad\u30fc\u30ef\u30fc\u30c9\u691c\u7d22\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u3092\u5b9f\u884c\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059\u3002
|
||||
ExtractedContentViewer.getTitle=\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u5316\u3055\u308c\u305f\u30c6\u30ad\u30b9\u30c8
|
||||
ExtractedContentViewer.getSolrContent.knownFileMsg=<p style\=''font-style\:italic''>{0}\u306f\u65e2\u77e5\u30d5\u30a1\u30a4\u30eb\u3067\u3059\uff08MDS\u30cf\u30c3\u30b7\u30e5\u306b\u57fa\u3065\u304f\u3068\uff09\u3002\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306b\u30c6\u30ad\u30b9\u30c8\u304c\u3042\u308a\u307e\u305b\u3093\u3002</p>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2015 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -26,11 +26,11 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.openide.nodes.Node;
|
||||
import org.openide.util.lookup.ServiceProvider;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
@ -40,8 +40,8 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||
|
||||
/**
|
||||
* Displays the indexed text associated with a file or a blackboard artifact,
|
||||
* possibly marked up with HTML to highlight keyword hits.
|
||||
* A content viewer that displays the indexed text associated with a file or an
|
||||
* artifact, possibly marked up with HTML to highlight keyword hits.
|
||||
*/
|
||||
@ServiceProvider(service = DataContentViewer.class, position = 4)
|
||||
public class ExtractedContentViewer implements DataContentViewer {
|
||||
@ -51,17 +51,26 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
private ExtractedContentPanel panel;
|
||||
private volatile Node currentNode = null;
|
||||
private IndexedText currentSource = null;
|
||||
private final IsDirVisitor isDirVisitor = new IsDirVisitor();
|
||||
|
||||
/**
|
||||
* Constructs a content viewer that displays the indexed text associated
|
||||
* with a file or an artifact, possibly marked up with HTML to highlight
|
||||
* keyword hits.
|
||||
*/
|
||||
public ExtractedContentViewer() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the node displayed by the content viewer.
|
||||
*
|
||||
* @param node The node to display
|
||||
*/
|
||||
@Override
|
||||
public void setNode(final Node selectedNode) {
|
||||
public void setNode(final Node node) {
|
||||
/*
|
||||
* Clear the viewer.
|
||||
*/
|
||||
if (selectedNode == null) {
|
||||
if (node == null) {
|
||||
currentNode = null;
|
||||
resetComponent();
|
||||
return;
|
||||
@ -71,51 +80,89 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
* This deals with the known bug with an unknown cause where setNode is
|
||||
* sometimes called twice for the same node.
|
||||
*/
|
||||
if (selectedNode == currentNode) {
|
||||
if (node == currentNode) {
|
||||
return;
|
||||
} else {
|
||||
currentNode = selectedNode;
|
||||
currentNode = node;
|
||||
}
|
||||
|
||||
/*
|
||||
* Assemble a collection of all of the "sources" of extracted and
|
||||
* indexed text to present in a paged display. First look for the text
|
||||
* marked up with HTML to highlight keyword hits that will be present if
|
||||
* the node is a keyword hit blakcboard artifact.
|
||||
* Assemble a collection of all of the indexed text "sources" associated
|
||||
* with the node.
|
||||
*/
|
||||
final List<IndexedText> sources = new ArrayList<>();
|
||||
sources.addAll(selectedNode.getLookup().lookupAll(IndexedText.class));
|
||||
IndexedText highlightedHitText = null;
|
||||
IndexedText rawContentText = null;
|
||||
IndexedText rawArtifactText = null;
|
||||
List<IndexedText> sources = new ArrayList<>();
|
||||
|
||||
/*
|
||||
* Now look for the "raw" extracted text if this is a node for another
|
||||
* type of artifact or for content.
|
||||
* First add the text marked up with HTML to highlight keyword hits that
|
||||
* will be present in the selected node's lookup if the node is for a
|
||||
* keyword hit artifact.
|
||||
*/
|
||||
long documentID = getDocumentId(currentNode);
|
||||
if (INVALID_DOCUMENT_ID == documentID) {
|
||||
setPanel(sources);
|
||||
return;
|
||||
sources.addAll(node.getLookup().lookupAll(IndexedText.class));
|
||||
if (!sources.isEmpty()) {
|
||||
highlightedHitText = sources.get(0);
|
||||
}
|
||||
IndexedText rawSource;
|
||||
if (documentID > INVALID_DOCUMENT_ID) {
|
||||
// Add a content item
|
||||
Content content = currentNode.getLookup().lookup(Content.class);
|
||||
rawSource = new RawText(content, content.getId());
|
||||
|
||||
/*
|
||||
* Next, add the "raw" (not highlighted) text, if any, for any content
|
||||
* associated with the node.
|
||||
*/
|
||||
Content content = currentNode.getLookup().lookup(Content.class);
|
||||
if (null != content && solrHasContent(content.getId())) {
|
||||
rawContentText = new RawText(content, content.getId());
|
||||
sources.add(rawContentText);
|
||||
}
|
||||
|
||||
/*
|
||||
* Finally, add the "raw" (not highlighted) text, if any, for any
|
||||
* artifact associated with the node.
|
||||
*/
|
||||
BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
|
||||
if (null != artifact) {
|
||||
/*
|
||||
* For keyword hit artifacts, add the text of the artifact that hit,
|
||||
* not the hit artifact; otherwise add the text for the artifact.
|
||||
*/
|
||||
if (artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
|
||||
rawArtifactText = new RawText(artifact, artifact.getArtifactID());
|
||||
sources.add(rawArtifactText);
|
||||
} else {
|
||||
try {
|
||||
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
|
||||
if (attribute != null) {
|
||||
long artifactId = attribute.getValueLong();
|
||||
BlackboardArtifact associatedArtifact = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifact(artifactId);
|
||||
rawArtifactText = new RawText(associatedArtifact, associatedArtifact.getArtifactID());
|
||||
sources.add(rawArtifactText);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error getting associated artifact attributes", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Now set the default source to be displayed.
|
||||
*/
|
||||
if (null != highlightedHitText) {
|
||||
currentSource = highlightedHitText;
|
||||
} else if (null != rawContentText) {
|
||||
currentSource = rawContentText;
|
||||
} else {
|
||||
// Add an artifact item
|
||||
BlackboardArtifact blackboardArtifact = currentNode.getLookup().lookup(BlackboardArtifact.class);
|
||||
rawSource = new RawText(blackboardArtifact, documentID);
|
||||
currentSource = rawArtifactText;
|
||||
}
|
||||
currentSource = rawSource;
|
||||
sources.add(rawSource);
|
||||
|
||||
/*
|
||||
* Initialize the pages for the sources. The first source in the list
|
||||
* of sources will be displayed.
|
||||
* Push the text sources into the panel.
|
||||
*/
|
||||
int currentPage = currentSource.getCurrentPage();
|
||||
if (currentPage == 0 && currentSource.hasNextPage()) {
|
||||
currentSource.nextPage();
|
||||
}
|
||||
for (IndexedText source : sources) {
|
||||
int currentPage = source.getCurrentPage();
|
||||
if (currentPage == 0 && source.hasNextPage()) {
|
||||
source.nextPage();
|
||||
}
|
||||
}
|
||||
updatePageControls();
|
||||
setPanel(sources);
|
||||
}
|
||||
@ -288,8 +335,8 @@ public class ExtractedContentViewer implements DataContentViewer {
|
||||
/*
|
||||
* For keyword search hit artifact nodes and all other nodes, the
|
||||
* document ID for the extracted text is the ID of the associated
|
||||
* content, if any, unless there is an associated artifact, which
|
||||
* is handled above.
|
||||
* content, if any, unless there is an associated artifact, which is
|
||||
* handled above.
|
||||
*/
|
||||
Content content = node.getLookup().lookup(Content.class);
|
||||
if (content != null) {
|
||||
|
@ -187,11 +187,12 @@
|
||||
<Connection code="tableModel" type="code"/>
|
||||
</Property>
|
||||
<Property name="autoResizeMode" type="int" value="0"/>
|
||||
<Property name="gridColor" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||
<Color blue="99" green="99" red="99" type="rgb"/>
|
||||
</Property>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[30000, 30000]"/>
|
||||
</Property>
|
||||
<Property name="showHorizontalLines" type="boolean" value="false"/>
|
||||
<Property name="showVerticalLines" type="boolean" value="false"/>
|
||||
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
|
||||
<TableHeader reorderingAllowed="false" resizingAllowed="true"/>
|
||||
</Property>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2015 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -45,6 +45,13 @@ import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.corecomponents.OptionsPanel;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import java.awt.Component;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import static javax.swing.SwingConstants.CENTER;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
|
||||
/**
|
||||
* GlobalEditListPanel widget to manage keywords in lists
|
||||
@ -52,7 +59,8 @@ import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionListener, OptionsPanel {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(GlobalEditListPanel.class.getName());
|
||||
private KeywordTableModel tableModel;
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final KeywordTableModel tableModel;
|
||||
private KeywordList currentKeywordList;
|
||||
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
|
||||
|
||||
@ -73,8 +81,6 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis
|
||||
saveListButton.setToolTipText(NbBundle.getMessage(this.getClass(), "KeywordSearchEditListPanel.customizeComponents.saveCurrentWIthNewNameToolTip"));
|
||||
deleteWordButton.setToolTipText(NbBundle.getMessage(this.getClass(), "KeywordSearchEditListPanel.customizeComponents.removeSelectedMsg"));
|
||||
|
||||
keywordTable.setShowHorizontalLines(false);
|
||||
keywordTable.setShowVerticalLines(false);
|
||||
keywordTable.getParent().setBackground(keywordTable.getBackground());
|
||||
final int width = jScrollPane1.getPreferredSize().width;
|
||||
keywordTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
|
||||
@ -85,7 +91,8 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis
|
||||
column.setPreferredWidth(((int) (width * 0.90)));
|
||||
} else {
|
||||
column.setPreferredWidth(((int) (width * 0.10)));
|
||||
//column.setCellRenderer(new CheckBoxRenderer());
|
||||
column.setCellRenderer(new CheckBoxRenderer());
|
||||
column.setHeaderRenderer(new HeaderRenderer(keywordTable));
|
||||
}
|
||||
}
|
||||
keywordTable.setCellSelectionEnabled(false);
|
||||
@ -239,9 +246,8 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis
|
||||
|
||||
keywordTable.setModel(tableModel);
|
||||
keywordTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF);
|
||||
keywordTable.setGridColor(new java.awt.Color(153, 153, 153));
|
||||
keywordTable.setMaximumSize(new java.awt.Dimension(30000, 30000));
|
||||
keywordTable.setShowHorizontalLines(false);
|
||||
keywordTable.setShowVerticalLines(false);
|
||||
keywordTable.getTableHeader().setReorderingAllowed(false);
|
||||
jScrollPane1.setViewportView(keywordTable);
|
||||
|
||||
@ -686,4 +692,55 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis
|
||||
resync();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cell renderer for boolean cells that shows a center-aligned green check
|
||||
* mark if true, nothing if false.
|
||||
*/
|
||||
private class CheckBoxRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
final ImageIcon theCheck = new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/checkmark.png")); // NON-NLS
|
||||
|
||||
CheckBoxRenderer() {
|
||||
setHorizontalAlignment(CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Messages("IsRegularExpression=Keyword is a regular expression")
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
|
||||
if ((value instanceof Boolean)) {
|
||||
if ((Boolean) value) {
|
||||
setIcon(theCheck);
|
||||
setToolTipText(Bundle.IsRegularExpression());
|
||||
} else {
|
||||
setIcon(null);
|
||||
setToolTipText(null);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cell renderer for header cells that center-aligns the header text.
|
||||
*/
|
||||
private static class HeaderRenderer implements TableCellRenderer {
|
||||
|
||||
private DefaultTableCellRenderer renderer;
|
||||
|
||||
public HeaderRenderer(JTable table) {
|
||||
renderer = (DefaultTableCellRenderer) table.getTableHeader().getDefaultRenderer();
|
||||
renderer.setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(
|
||||
JTable table, Object value, boolean isSelected,
|
||||
boolean hasFocus, int row, int col) {
|
||||
return renderer.getTableCellRendererComponent(
|
||||
table, value, isSelected, hasFocus, row, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-2015 Basis Technology Corp.
|
||||
* Copyright 2011-2016 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -30,8 +30,8 @@ import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
* Display content with just raw text
|
||||
*
|
||||
* A "source" for the extracted content viewer that displays "raw" (not
|
||||
* highlighted) indexed text for a file or an artifact.
|
||||
*/
|
||||
class RawText implements IndexedText {
|
||||
|
||||
@ -151,17 +151,24 @@ class RawText implements IndexedText {
|
||||
if (this.content != null) {
|
||||
return getContentText(currentPage, hasChunks);
|
||||
} else if (this.blackboardArtifact != null) {
|
||||
return KeywordSearch.getServer().getSolrContent(this.objectId, 1);
|
||||
return getArtifactText();
|
||||
}
|
||||
} catch (SolrServerException | NoOpenCoreException ex) {
|
||||
logger.log(Level.WARNING, "Couldn't get extracted content.", ex); //NON-NLS
|
||||
} catch (SolrServerException ex) {
|
||||
logger.log(Level.SEVERE, "Couldn't get extracted content", ex); //NON-NLS
|
||||
}
|
||||
return NbBundle.getMessage(this.getClass(), "RawText.getText.error.msg");
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"RawText.FileText=File Text",
|
||||
"RawText.ResultText=Result Text"})
|
||||
@Override
|
||||
public String toString() {
|
||||
return NbBundle.getMessage(this.getClass(), "ExtractedContentViewer.toString");
|
||||
if (null != content) {
|
||||
return Bundle.RawText_FileText();
|
||||
} else {
|
||||
return Bundle.RawText_ResultText();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -267,9 +274,23 @@ class RawText implements IndexedText {
|
||||
cachedString = sb.toString();
|
||||
cachedChunk = chunkId;
|
||||
} catch (NoOpenCoreException ex) {
|
||||
logger.log(Level.WARNING, "Couldn't get text content.", ex); //NON-NLS
|
||||
logger.log(Level.SEVERE, "No open core", ex); //NON-NLS
|
||||
return "";
|
||||
}
|
||||
return cachedString;
|
||||
}
|
||||
|
||||
private String getArtifactText() throws SolrServerException{
|
||||
try {
|
||||
String indexedText = KeywordSearch.getServer().getSolrContent(this.objectId, 1);
|
||||
indexedText = EscapeUtil.escapeHtml(indexedText).trim();
|
||||
StringBuilder sb = new StringBuilder(indexedText.length() + 20);
|
||||
sb.append("<pre>").append(indexedText).append("</pre>"); //NON-NLS
|
||||
return sb.toString();
|
||||
} catch (NoOpenCoreException ex) {
|
||||
logger.log(Level.SEVERE, "No open core", ex); //NON-NLS
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
BIN
KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/checkmark.png
Executable file
BIN
KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/checkmark.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 279 B |
@ -46,6 +46,14 @@
|
||||
<specification-version>8.19.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.openide.util.ui</code-name-base>
|
||||
<build-prerequisite/>
|
||||
<compile-dependency/>
|
||||
<run-dependency>
|
||||
<specification-version>9.4.1</specification-version>
|
||||
</run-dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<code-name-base>org.sleuthkit.autopsy.core</code-name-base>
|
||||
<build-prerequisite/>
|
||||
|
@ -22,18 +22,18 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.recentactivity;
|
||||
|
||||
import org.sleuthkit.autopsy.coreutils.SQLiteDBConnect;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.coreutils.SQLiteDBConnect;
|
||||
import org.sleuthkit.autopsy.ingest.IngestJobContext;
|
||||
import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException;
|
||||
import org.sleuthkit.datamodel.*;
|
||||
@ -103,15 +103,15 @@ abstract class Extract {
|
||||
*
|
||||
* @param bbart Blackboard artifact to be indexed
|
||||
*/
|
||||
@Messages({"Extract.indexError.message=Failed to index artifact for keyword search."})
|
||||
void indexArtifact(BlackboardArtifact bbart) {
|
||||
Blackboard blackboard = Case.getCurrentCase().getServices().getBlackboard();
|
||||
try {
|
||||
// index the artifact for keyword search
|
||||
blackboard.indexArtifact(bbart);
|
||||
} catch (Blackboard.BlackboardException ex) {
|
||||
logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", bbart.getDisplayName()), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(
|
||||
NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), bbart.getDisplayName());
|
||||
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bbart.getDisplayName(), ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error(Bundle.Extract_indexError_message(), bbart.getDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user