diff --git a/BUILDING.txt b/BUILDING.txt index 6a53ff870a..1f928252e6 100644 --- a/BUILDING.txt +++ b/BUILDING.txt @@ -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 diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 3f1e5f172d..216a8e0f82 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -15,6 +15,22 @@ 1.28.1 + + org.netbeans.api.progress.compat8 + + + + 1.46.1 + + + + org.netbeans.api.progress.nb + + + + 1.46.1 + + org.netbeans.core @@ -123,6 +139,22 @@ 7.62.1 + + org.openide.filesystems.compat8 + + + + 9.7.1 + + + + org.openide.filesystems.nb + + + + 9.7.1 + + org.openide.modules @@ -163,6 +195,14 @@ 8.15.1 + + org.openide.util.ui + + + + 9.4.1 + + org.openide.windows diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java index 2111f3a554..28ce44b3c7 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddBlackboardArtifactTagAction.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2013-2015 Basis Technology Corp. + * + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit 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 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 selectedArtifacts = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(BlackboardArtifact.class)); new Thread(() -> { for (BlackboardArtifact artifact : selectedArtifacts) { diff --git a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java index d504600446..e32f9df902 100755 --- a/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java +++ b/Core/src/org/sleuthkit/autopsy/actions/AddContentTagAction.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2013-2015 Basis Technology Corp. + * + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit 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 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 selectedFiles = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class)); new Thread(() -> { for (AbstractFile file : selectedFiles) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java index d5064e3b4e..70b10b0f0e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Blackboard.java @@ -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. */ diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties index 9353ca914b..1a3be9454f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle.properties @@ -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 diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties index 956a5e6169..ca8ed3431a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Bundle_ja.properties @@ -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 diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java index 5b958cd5a7..380adef01e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java @@ -1,19 +1,19 @@ /* * * Autopsy Forensic Browser - * + * * Copyright 2011-2016 Basis Technology Corp. - * + * * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com * Project Contact/Architect: carrier sleuthkit 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 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 findFilesByMimeType(Collection 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 findFilesByMimeType(Content dataSource, Collection 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 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 findFiles(String fileName) throws TskCoreException { - List 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 dataSources = tskCase.getRootObjects(); + List result = new ArrayList<>(); + List dataSources = caseDb.getRootObjects(); for (Content dataSource : dataSources) { result.addAll(findFiles(dataSource, fileName)); } @@ -104,290 +138,245 @@ 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 findFiles(String fileName, String dirName) throws TskCoreException { - List result = new ArrayList<>(); - - if (tskCase == null) { - throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles2.exception.msg")); + public synchronized List findFiles(String fileName, String parentName) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); } - List dataSources = tskCase.getRootObjects(); + List result = new ArrayList<>(); + List 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 findFiles(String fileName, AbstractFile parentFile) throws TskCoreException { - List result = new ArrayList<>(); - - if (tskCase == null) { - throw new TskCoreException(NbBundle.getMessage(this.getClass(), "FileManager.findFiles3.exception.msg")); + public synchronized List findFiles(String fileName, AbstractFile parent) throws TskCoreException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); } - List dataSources = tskCase.getRootObjects(); + List result = new ArrayList<>(); + List 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 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 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 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 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 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()); } /** - * @param dataSource data source Content (Image, parent-less - * VirtualDirectory) where to find localFiles - * @param filePath The full path to the file(s) of interest. This can - * optionally include the image and volume names. + * 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. * - * @return a list of AbstractFile that have the given file path. + * @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 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 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 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 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 addCarvedFiles(List 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 addCarvedFiles(List 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 localAbsPaths, FileAddProgressUpdater addProgressUpdater) throws TskCoreException { - List 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 +408,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 localFilePaths, FileAddProgressUpdater progressUpdater) throws TskCoreException, TskDataException { + if (null == caseDb) { + throw new TskCoreException("File manager has been closed"); + } List 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 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 +436,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 +454,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 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 +499,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 +507,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 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; } + } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java index 880bc8008e..a8d11d184c 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/Services.java @@ -1,19 +1,18 @@ /* * * Autopsy Forensic Browser - * - * Copyright 2012-2015 Basis Technology Corp. - * + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com - * Project Contact/Architect: carrier sleuthkit 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) { diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java index 963f54e51f..e8d61bdeba 100755 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/TagsManager.java @@ -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 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 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 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 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 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 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 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 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 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; } /** diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index ff2ff6da87..aaf091c278 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -341,6 +341,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C if (null != explorerManager && null != emNodeSelectionListener) { explorerManager.removePropertyChangeListener(emNodeSelectionListener); + explorerManager = null; } // clear all set nodes diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/TextUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/TextUtil.java index 053968d598..f2eed8e474 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/TextUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/TextUtil.java @@ -57,4 +57,26 @@ public class TextUtil { return orientation; } + + + /** + * This method determines if a passed-in Java char (16 bits) is a valid + * UTF-8 printable character, returning true if so, false if not. + * + * Note that this method can have ramifications for characters outside the + * Unicode Base Multilingual Plane (BMP), which require more than 16 bits. + * We are using Java characters (16 bits) to look at the data and this will + * not accurately identify any non-BMP character (larger than 16 bits) + * ending with 0xFFFF and 0xFFFE. In the interest of a fast solution, we + * have chosen to ignore the extended planes above Unicode BMP for the time + * being. The net result of this is some non-BMP characters may be + * interspersed with '^' characters in Autopsy. + * + * @param ch the character to test + * + * @return Returns true if the character is valid UTF-8, false if not. + */ + public static boolean isValidSolrUTF8(char ch) { + return ((ch <= 0xFDD0 || ch >= 0xFDEF) && (ch > 0x1F || ch == 0x9 || ch == 0xA || ch == 0xD) && (ch != 0xFFFF) && (ch != 0xFFFE)); + } } diff --git a/Core/src/org/sleuthkit/autopsy/externalresults/ExternalResultsImporter.java b/Core/src/org/sleuthkit/autopsy/externalresults/ExternalResultsImporter.java index 9deeb0e841..24255057d0 100644 --- a/Core/src/org/sleuthkit/autopsy/externalresults/ExternalResultsImporter.java +++ b/Core/src/org/sleuthkit/autopsy/externalresults/ExternalResultsImporter.java @@ -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)) { diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties b/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties index d946e2f9c2..c5fd4d48e4 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/filesearch/Bundle.properties @@ -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: diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.form b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.form index 4b253ff1e6..2d09e16507 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.form +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.form @@ -180,6 +180,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.java index b2bb6d295c..ea3642a65d 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/DateSearchPanel.java @@ -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. diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.form b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.form index 3de5bd4680..beb7227419 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.form +++ b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.form @@ -53,6 +53,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java index 9a564fcdbc..291ced32e7 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/KnownStatusSearchPanel.java @@ -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; diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.form b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.form index 9221d39633..a8c7fa65f9 100755 --- a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.form +++ b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.form @@ -25,13 +25,13 @@ - + - + @@ -44,12 +44,12 @@ - + - + @@ -62,7 +62,7 @@ - + @@ -77,12 +77,15 @@ - + - + + + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java index 8a90761946..b4172354a4 100755 --- a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java @@ -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 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(); - 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() { + mimeTypeList.setModel(new javax.swing.AbstractListModel() { 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()) ); }// //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 jList1; private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JCheckBox mimeTypeCheckBox; + private javax.swing.JList mimeTypeList; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.form b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.form index 7b5e61f5e1..2dfc5af1c3 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.form +++ b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.form @@ -94,6 +94,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java index dc68c320de..1ae2842947 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/NameSearchPanel.java @@ -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; diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.form b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.form index 26a8e904bc..7987a8a031 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.form +++ b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.form @@ -116,6 +116,9 @@ + + + diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java index e530ba7b42..4a0bc91d5e 100644 --- a/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/SizeSearchPanel.java @@ -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 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(); + sizeUnitComboBox = new javax.swing.JComboBox<>(); sizeTextField = new JFormattedTextField(NumberFormat.getIntegerInstance()); - sizeCompareComboBox = new javax.swing.JComboBox(); + 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(new String[] { "Byte(s)", "KB", "MB", "GB", "TB" })); //NON-NLS + sizeUnitComboBox.setModel(new javax.swing.DefaultComboBoxModel(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(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(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; diff --git a/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java b/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java index 69b7c57f4f..af43facec9 100644 --- a/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java +++ b/Core/src/org/sleuthkit/autopsy/keywordsearchservice/KeywordSearchService.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit 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; - } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/BrowserLocationAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/BrowserLocationAnalyzer.java index d7f0543c49..d76abda578 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/BrowserLocationAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/BrowserLocationAnalyzer.java @@ -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) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/CacheLocationAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/CacheLocationAnalyzer.java index 266bf09469..9f36f3cf83 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/CacheLocationAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/CacheLocationAnalyzer.java @@ -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()); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/CallLogAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/CallLogAnalyzer.java index 7df25e15ff..198a503e4a 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/CallLogAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/CallLogAnalyzer.java @@ -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 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 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) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/ContactAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/ContactAnalyzer.java index 25affa740f..9eb8ac0ae9 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/ContactAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/ContactAnalyzer.java @@ -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()); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/GoogleMapLocationAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/GoogleMapLocationAnalyzer.java index d7889257dc..fbb6c9befd 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/GoogleMapLocationAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/GoogleMapLocationAnalyzer.java @@ -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) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/TangoMessageAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/TangoMessageAnalyzer.java index 09dba30821..41d8676eed 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/TangoMessageAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/TangoMessageAnalyzer.java @@ -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) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/TextMessageAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/TextMessageAnalyzer.java index edf12e0b66..71139690da 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/TextMessageAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/TextMessageAnalyzer.java @@ -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) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/android/WWFMessageAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/android/WWFMessageAnalyzer.java index 87bbd8dc4d..8db7132bf0 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/android/WWFMessageAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/android/WWFMessageAnalyzer.java @@ -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) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 8e25feaa23..18a81126fa 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -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)); diff --git a/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java index 2534d376f7..1fd1bd9116 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java @@ -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; } diff --git a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java index 6f9a9ea98c..07d2a9a5bd 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/fileextmismatch/FileExtMismatchIngestModule.java @@ -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))); diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/CustomFileTypesManager.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/CustomFileTypesManager.java index 784dea47b9..bea6212681 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/CustomFileTypesManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/CustomFileTypesManager.java @@ -104,6 +104,19 @@ final class CustomFileTypesManager { return customTypes; } + /** + * Gets the custom file types defined by Autopsy. + * + * @return A list of custom file types, possibly empty. + */ + synchronized List getAutopsyDefinedFileTypes() { + /** + * It is safe to return references instead of copies in this snapshot + * because FileType objects are immutable. + */ + return new ArrayList<>(autopsyDefinedFileTypes); + } + /** * Gets the user-defined custom file types. * diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java index 44a7cda7da..487335d828 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java @@ -56,8 +56,8 @@ public class FileTypeDetector { */ public FileTypeDetector() throws FileTypeDetectorInitException { try { - userDefinedFileTypes = CustomFileTypesManager.getInstance().getFileTypes(); - autopsyDefinedFileTypes = CustomFileTypesManager.getInstance().getFileTypes(); + userDefinedFileTypes = CustomFileTypesManager.getInstance().getUserDefinedFileTypes(); + autopsyDefinedFileTypes = CustomFileTypesManager.getInstance().getAutopsyDefinedFileTypes(); } catch (CustomFileTypesManager.CustomFileTypesException ex) { throw new FileTypeDetectorInitException("Error loading custom file types", ex); //NON-NLS } @@ -168,7 +168,7 @@ public class FileTypeDetector { /** * Detects the MIME type of a file. The result is saved to the case database - * only if the add to case dastabase flag is set. + * only if the add to case database flag is set. * * @param file The file to test. * @param addToCaseDb Whether the MIME type should be added to the case diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form index 741c775f3c..0b33ec364f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form @@ -25,22 +25,24 @@ - + - - - - + + + + + + @@ -62,7 +64,7 @@ - + @@ -101,7 +103,10 @@ - + + + + @@ -110,7 +115,7 @@ - + @@ -119,7 +124,10 @@ - + + + + @@ -154,14 +162,14 @@ - - + + - + @@ -263,7 +271,7 @@ - + @@ -291,11 +299,11 @@ - - - - + + + + @@ -320,6 +328,7 @@ + diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java index da67c5697a..a4b4ff49d3 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java @@ -36,8 +36,8 @@ import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; -import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; import org.sleuthkit.autopsy.modules.filetypeid.CustomFileTypesManager.CustomFileTypesException; +import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; /** * A panel to allow a user to make custom file type definitions. In addition to @@ -329,6 +329,8 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane setMaximumSize(null); setPreferredSize(new java.awt.Dimension(752, 507)); + jPanel3.setPreferredSize(new java.awt.Dimension(781, 339)); + ingestRunningWarningLabel.setFont(ingestRunningWarningLabel.getFont().deriveFont(ingestRunningWarningLabel.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); ingestRunningWarningLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/modules/filetypeid/warning16.png"))); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(ingestRunningWarningLabel, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.ingestRunningWarningLabel.text")); // NOI18N @@ -336,11 +338,13 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane jLabel3.setFont(jLabel3.getFont().deriveFont(jLabel3.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.jLabel3.text")); // NOI18N - jScrollPane2.setMinimumSize(new java.awt.Dimension(300, 100)); + jScrollPane2.setMinimumSize(new java.awt.Dimension(300, 0)); + jScrollPane2.setPreferredSize(new java.awt.Dimension(550, 275)); - jSplitPane1.setMinimumSize(new java.awt.Dimension(558, 285)); + jSplitPane1.setMinimumSize(new java.awt.Dimension(0, 0)); - jPanel1.setMinimumSize(new java.awt.Dimension(362, 283)); + jPanel1.setMinimumSize(new java.awt.Dimension(362, 0)); + jPanel1.setPreferredSize(new java.awt.Dimension(362, 0)); jLabel2.setFont(jLabel2.getFont().deriveFont(jLabel2.getFont().getStyle() & ~java.awt.Font.BOLD, 11)); org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.jLabel2.text")); // NOI18N @@ -399,20 +403,20 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane .addGap(12, 12, 12) .addComponent(jLabel2) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(typesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 397, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(typesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 203, Short.MAX_VALUE) + .addGap(10, 10, 10) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(newTypeButton) .addComponent(editTypeButton) .addComponent(deleteTypeButton)) - .addContainerGap()) + .addGap(7, 7, 7)) ); jPanel1Layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {deleteTypeButton, newTypeButton}); jSplitPane1.setLeftComponent(jPanel1); - jPanel2.setMinimumSize(new java.awt.Dimension(79, 283)); + jPanel2.setMinimumSize(new java.awt.Dimension(79, 0)); org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.jLabel1.text")); // NOI18N @@ -421,6 +425,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane public int getSize() { return signatures.length; } public Signature getElementAt(int i) { return signatures[i]; } }); + signatureList.setEnabled(false); signatureList.setMaximumSize(new java.awt.Dimension(32767, 32767)); signatureList.setPreferredSize(null); jScrollPane1.setViewportView(signatureList); @@ -444,8 +449,8 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane .addContainerGap() .addComponent(jLabel1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE) - .addGap(40, 40, 40)) + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 235, Short.MAX_VALUE) + .addContainerGap()) ); jSplitPane1.setRightComponent(jPanel2); @@ -470,7 +475,7 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane .addContainerGap() .addComponent(jLabel3) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 281, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(ingestRunningWarningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) @@ -481,14 +486,12 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, 752, Short.MAX_VALUE) .addGap(0, 0, 0)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(0, 0, 0)) + .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, 345, Short.MAX_VALUE) ); }// //GEN-END:initComponents diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java index 96f0c1b16e..1b60250a6b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbCreateDatabaseDialog.java @@ -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; @@ -30,6 +31,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; @@ -273,12 +275,17 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog { private void saveAsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveAsButtonActionPerformed try { - String lastBaseDirectory = ""; + String lastBaseDirectory = Paths.get(PlatformUtil.getUserConfigDirectory(), "HashDatabases").toString(); if (ModuleSettings.settingExists(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY)) { lastBaseDirectory = ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY); } StringBuilder path = new StringBuilder(); path.append(lastBaseDirectory); + File hashDbFolder = new File(path.toString()); + // create the folder if it doesn't exist + if (!hashDbFolder.exists()){ + hashDbFolder.mkdir(); + } if (!hashSetNameTextField.getText().isEmpty()) { path.append(File.separator).append(hashSetNameTextField.getText()); } else { diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java index 18fd2c6202..337fa49f0b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbImportDatabaseDialog.java @@ -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 { }// //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 { diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java index 0199727cb2..13f0d9914d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java @@ -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) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java index 22ca91c064..b3627c3ef3 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/CallLogAnalyzer.java @@ -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) { diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java index 7c63e28a8d..8741f4077b 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/ContactAnalyzer.java @@ -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()); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java b/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java index 44c08ae0cf..9ef547f4d9 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java +++ b/Core/src/org/sleuthkit/autopsy/modules/iOS/TextMessageAnalyzer.java @@ -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()); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java index 62b423015b..95f52b06e1 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java @@ -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))); diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java index 4709a3986b..9ee7f1a172 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java @@ -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(); diff --git a/Core/src/org/sleuthkit/autopsy/modules/stix/StixArtifactData.java b/Core/src/org/sleuthkit/autopsy/modules/stix/StixArtifactData.java index 9a19e4cedf..8a8466d89f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/stix/StixArtifactData.java +++ b/Core/src/org/sleuthkit/autopsy/modules/stix/StixArtifactData.java @@ -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()); } } diff --git a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties index 0be3a1924f..6e920ad631 100644 --- a/Core/src/org/sleuthkit/autopsy/report/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/report/Bundle.properties @@ -179,7 +179,7 @@ ReportHTML.addThumbRows.dataType.msg=Tagged Results and Contents that contain im ReportHTML.thumbLink.tags=Tags\: ReportHTML.getName.text=Results - HTML ReportHTML.getDesc.text=A report about results and tagged items in HTML format. -ReportHTML.writeIndex.title=Autopsy Report for case {0} +ReportHTML.writeIndex.title=for case {0} ReportHTML.writeIndex.noFrames.msg=Your browser is not compatible with our frame setup. ReportHTML.writeIndex.noFrames.seeNav=Please see the navigation page for artifact links, ReportHTML.writeIndex.seeSum=and the summary page for a case summary. @@ -225,7 +225,8 @@ ReportHTML.writeSum.noCaseNum=No case number 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 diff --git a/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties index 6ff657b68a..a877c52899 100644 --- a/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/report/Bundle_ja.properties @@ -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 diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index eeca1efe56..54fa7b6b7e 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -838,10 +838,17 @@ class ReportHTML implements TableReportModule { try { indexOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFilePath), "UTF-8")); //NON-NLS StringBuilder index = new StringBuilder(); - index.append("\n").append( //NON-NLS + 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( "\n"); //NON-NLS - index.append("\n"); //NON-NLS + index.append("\n"); //NON-NLS index.append("\n"); //NON-NLS index.append("\n"); //NON-NLS index.append("\n"); //NON-NLS diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportKML.java b/Core/src/org/sleuthkit/autopsy/report/ReportKML.java index abdd7dee96..a24850c514 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportKML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportKML.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-2016 Basis Technology Corp. * contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,33 +27,56 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.*; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.datamodel.BlackboardArtifact; -import java.io.BufferedReader; -import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; import java.util.logging.Level; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.Namespace; import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; -import org.apache.commons.lang.StringEscapeUtils; +import org.jdom2.CDATA; +import org.openide.filesystems.FileUtil; /** - * Generates a KML file based on geo coordinates store in blackboard. + * Generates a KML file based on geospatial information from the BlackBoard. */ class ReportKML implements GeneralReportModule { private static final Logger logger = Logger.getLogger(ReportKML.class.getName()); + private static final String KML_STYLE_FILE = "style.kml"; + private static final String REPORT_KML = "ReportKML.kml"; + private static final String STYLESHEETS_PATH = "/org/sleuthkit/autopsy/report/stylesheets/"; private static ReportKML instance = null; private Case currentCase; private SleuthkitCase skCase; - private String reportPath; + private final SimpleDateFormat kmlDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); + private Namespace ns; + private final String SEP = "
"; + + private enum FeatureColor { + RED("style.kml#redFeature"), + GREEN("style.kml#greenFeature"), + BLUE("style.kml#blueFeature"), + PURPLE("style.kml#purpleFeature"), + WHITE("style.kml#whiteFeature"), + YELLOW("style.kml#yellowFeature"); + private String color; + + FeatureColor(String color) { + this.color = color; + } + + String getColor() { + return this.color; + } + } // Hidden constructor for the report private ReportKML() { @@ -77,282 +100,727 @@ class ReportKML implements GeneralReportModule { public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) { // Start the progress bar and setup the report - progressPanel.setIndeterminate(false); + progressPanel.setIndeterminate(true); progressPanel.start(); progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.querying")); - reportPath = baseReportDir + "ReportKML.kml"; //NON-NLS - String reportPath2 = baseReportDir + "ReportKML.txt"; //NON-NLS + String kmlFileFullPath = baseReportDir + REPORT_KML; //NON-NLS currentCase = Case.getCurrentCase(); skCase = currentCase.getSleuthkitCase(); progressPanel.updateStatusLabel(NbBundle.getMessage(this.getClass(), "ReportKML.progress.loading")); + + ns = Namespace.getNamespace("", "http://www.opengis.net/kml/2.2"); //NON-NLS + + Element kml = new Element("kml", ns); //NON-NLS + kml.addNamespaceDeclaration(Namespace.getNamespace("gx", "http://www.google.com/kml/ext/2.2")); //NON-NLS + kml.addNamespaceDeclaration(Namespace.getNamespace("kml", "http://www.opengis.net/kml/2.2")); //NON-NLS + kml.addNamespaceDeclaration(Namespace.getNamespace("atom", "http://www.w3.org/2005/Atom")); //NON-NLS + Document kmlDocument = new Document(kml); + + Element document = new Element("Document", ns); //NON-NLS + kml.addContent(document); + + Element name = new Element("name", ns); //NON-NLS + ReportBranding rb = new ReportBranding(); + name.setText(rb.getReportTitle() + " KML"); //NON-NLS + document.addContent(name); + // Check if ingest has finished - String ingestwarning = ""; if (IngestManager.getInstance().isIngestRunning()) { - ingestwarning = NbBundle.getMessage(this.getClass(), "ReportBodyFile.ingestWarning.text"); + Element ingestwarning = new Element("snippet", ns); //NON-NLS + ingestwarning.addContent(NbBundle.getMessage(this.getClass(), "ReportBodyFile.ingestWarning.text")); //NON-NLS + document.addContent(ingestwarning); } - progressPanel.setMaximumProgress(5); - progressPanel.increment(); - // @@@ BC: I don't get why we do this in two passes. - // Why not just print the coordinates as we find them and make some utility methods to do the printing? - // Should pull out time values for all of these points and store in TimeSpan element + // Create folder structure + Element gpsExifMetadataFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataExifMetadataFolder = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/camera-icon-16.png"); //NON-NLS + Element hrefExifMetadata = new Element("href", ns).addContent(cdataExifMetadataFolder); //NON-NLS + gpsExifMetadataFolder.addContent(new Element("Icon", ns).addContent(hrefExifMetadata)); //NON-NLS + + Element gpsBookmarksFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataBookmarks = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gpsfav.png"); //NON-NLS + Element hrefBookmarks = new Element("href", ns).addContent(cdataBookmarks); //NON-NLS + gpsBookmarksFolder.addContent(new Element("Icon", ns).addContent(hrefBookmarks)); //NON-NLS + + Element gpsLastKnownLocationFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataLastKnownLocation = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-lastlocation.png"); //NON-NLS + Element hrefLastKnownLocation = new Element("href", ns).addContent(cdataLastKnownLocation); //NON-NLS + gpsLastKnownLocationFolder.addContent(new Element("Icon", ns).addContent(hrefLastKnownLocation)); //NON-NLS + + Element gpsRouteFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataRoute = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS + Element hrefRoute = new Element("href", ns).addContent(cdataRoute); //NON-NLS + gpsRouteFolder.addContent(new Element("Icon", ns).addContent(hrefRoute)); //NON-NLS + + Element gpsSearchesFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataSearches = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-search.png"); //NON-NLS + Element hrefSearches = new Element("href", ns).addContent(cdataSearches); //NON-NLS + gpsSearchesFolder.addContent(new Element("Icon", ns).addContent(hrefSearches)); //NON-NLS + + Element gpsTrackpointsFolder = new Element("Folder", ns); //NON-NLS + CDATA cdataTrackpoints = new CDATA("https://raw.githubusercontent.com/sleuthkit/autopsy/develop/Core/src/org/sleuthkit/autopsy/images/gps-trackpoint.png"); //NON-NLS + Element hrefTrackpoints = new Element("href", ns).addContent(cdataTrackpoints); //NON-NLS + gpsTrackpointsFolder.addContent(new Element("Icon", ns).addContent(hrefTrackpoints)); //NON-NLS + + gpsExifMetadataFolder.addContent(new Element("name", ns).addContent("EXIF Metadata")); //NON-NLS + gpsBookmarksFolder.addContent(new Element("name", ns).addContent("GPS Bookmarks")); //NON-NLS + gpsLastKnownLocationFolder.addContent(new Element("name", ns).addContent("GPS Last Known Location")); //NON-NLS + gpsRouteFolder.addContent(new Element("name", ns).addContent("GPS Routes")); //NON-NLS + gpsSearchesFolder.addContent(new Element("name", ns).addContent("GPS Searches")); //NON-NLS + gpsTrackpointsFolder.addContent(new Element("name", ns).addContent("GPS Trackpoints")); //NON-NLS + + document.addContent(gpsExifMetadataFolder); + document.addContent(gpsBookmarksFolder); + document.addContent(gpsLastKnownLocationFolder); + document.addContent(gpsRouteFolder); + document.addContent(gpsSearchesFolder); + document.addContent(gpsTrackpointsFolder); + + ReportProgressPanel.ReportStatus result = ReportProgressPanel.ReportStatus.COMPLETE; + + /** + * In the following code, nulls are okay, and are handled when we go to + * write out the KML feature. Nulls are expected to be returned from any + * method where the artifact is not found and is handled in the + * individual feature creation methods. This is done because we don't + * know beforehand which attributes will be included for which artifact, + * as anyone could write a module that adds additional attributes to an + * artifact. + * + * If there are any issues reading the database getting artifacts and + * attributes, or any exceptions thrown during this process, a severe + * error is logged, the report is marked as "Incomplete KML Report", and + * we use a best-effort method to generate KML information on everything + * we can successfully pull out of the database. + */ try { - try (BufferedWriter out = new BufferedWriter(new FileWriter(reportPath2))) { + for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)) { + try { + Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); + String desc = getDescriptionFromArtifact(artifact, "EXIF Metadata With Locations"); //NON-NLS + Double lat = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); + Double lon = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); + Element point = makePoint(lat, lon, getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE)); - double lat = 0; // temp latitude - double lon = 0; //temp longitude - AbstractFile aFile; - String geoPath = ""; // will hold values of images to put in kml - String imageName = ""; - - File f; - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)) { - lat = 0; - lon = 0; - geoPath = ""; - String extractedToPath; - for (BlackboardAttribute attribute : artifact.getAttributes()) { - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID()) //latitude - { - - lat = attribute.getValueDouble(); + if (lat != null && lat != 0.0 && lon != null && lon != 0.0) { + AbstractFile abstractFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); + Path path = null; + copyFileUsingStream(abstractFile, Paths.get(baseReportDir, abstractFile.getName()).toFile()); + try { + path = Paths.get(removeLeadingImgAndVol(abstractFile.getUniquePath())); + } catch (TskCoreException ex) { + path = Paths.get(abstractFile.getParentPath(), abstractFile.getName()); } - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()) //longitude - { - lon = attribute.getValueDouble(); + String formattedCoordinates = String.format("%.2f, %.2f", lat, lon); + if (path == null) { + path = Paths.get(abstractFile.getName()); } + gpsExifMetadataFolder.addContent(makePlacemarkWithPicture(abstractFile.getName(), FeatureColor.RED, desc, timestamp, point, path, formattedCoordinates)); } - if (lon != 0 && lat != 0) { - aFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()); - - if (aFile != null) { - extractedToPath = reportPath + aFile.getName(); - geoPath = extractedToPath; - f = new File(extractedToPath); - f.createNewFile(); - copyFileUsingStream(aFile, f); - imageName = aFile.getName(); - } - out.write(String.valueOf(lat)); - out.write(";"); - out.write(String.valueOf(lon)); - out.write(";"); - out.write(String.valueOf(geoPath)); - out.write(";"); - out.write(String.valueOf(imageName)); - out.write("\n"); - // lat lon path name - } + } catch (Exception ex) { + logger.log(Level.SEVERE, "Could not extract photo information.", ex); //NON-NLS + result = ReportProgressPanel.ReportStatus.ERROR; } - - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT)) { - lat = 0; - lon = 0; - for (BlackboardAttribute attribute : artifact.getAttributes()) { - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID()) //latitude - { - lat = attribute.getValueDouble(); - } - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID()) //longitude - { - lon = attribute.getValueDouble(); - } - } - if (lon != 0 && lat != 0) { - out.write(lat + ";" + lon + "\n"); - } - } - - for (BlackboardArtifact artifact : skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE)) { - lat = 0; - lon = 0; - double destlat = 0; - double destlon = 0; - String name = ""; - String location = ""; - for (BlackboardAttribute attribute : artifact.getAttributes()) { - if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID()) //latitude - { - lat = attribute.getValueDouble(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END.getTypeID()) //longitude - { - destlat = attribute.getValueDouble(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START.getTypeID()) //longitude - { - lon = attribute.getValueDouble(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END.getTypeID()) //longitude - { - destlon = attribute.getValueDouble(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID()) //longitude - { - name = attribute.getValueString(); - } else if (attribute.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION.getTypeID()) //longitude - { - location = attribute.getValueString(); - } - } - - // @@@ Should do something more fancy with these in KML and store them as a single point. - String display = name; - if (display.isEmpty()) { - display = location; - } - - if (lon != 0 && lat != 0) { - out.write(NbBundle.getMessage(this.getClass(), "ReportKML.latLongStartPoint", lat, lon, display)); - } - if (destlat != 0 && destlon != 0) { - out.write(NbBundle.getMessage(this.getClass(), "ReportKML.latLongEndPoint", destlat, destlon, - display)); - } - } - - out.flush(); - - progressPanel.increment(); - /* - * Step 1: generate XML stub - */ - Namespace ns = Namespace.getNamespace("", "http://earth.google.com/kml/2.2"); //NON-NLS - // kml - Element kml = new Element("kml", ns); //NON-NLS - Document kmlDocument = new Document(kml); - - // Document - Element document = new Element("Document", ns); //NON-NLS - kml.addContent(document); - - // name - Element name = new Element("name", ns); //NON-NLS - name.setText("Java Generated KML Document"); //NON-NLS - document.addContent(name); - - /* - * Step 2: add in Style elements - */ - // Style - Element style = new Element("Style", ns); //NON-NLS - style.setAttribute("id", "redIcon"); //NON-NLS - document.addContent(style); - - // IconStyle - Element iconStyle = new Element("IconStyle", ns); //NON-NLS - style.addContent(iconStyle); - - // color - Element color = new Element("color", ns); //NON-NLS - color.setText("990000ff"); //NON-NLS - iconStyle.addContent(color); - - // Icon - Element icon = new Element("Icon", ns); //NON-NLS - iconStyle.addContent(icon); - - // href - Element href = new Element("href", ns); //NON-NLS - href.setText("http://www.cs.mun.ca/~hoeber/teaching/cs4767/notes/02.1-kml/circle.png"); //NON-NLS - icon.addContent(href); - progressPanel.increment(); - /* - * Step 3: read data from source location and add in a Placemark - * for each data element - */ - - File file = new File(reportPath2); - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - String line = reader.readLine(); - while (line != null) { - String[] lineParts = line.split(";"); - if (lineParts.length > 1) { - String coordinates = lineParts[1].trim() + "," + lineParts[0].trim(); //lat,lon - // Placemark - Element placemark = new Element("Placemark", ns); //NON-NLS - document.addContent(placemark); - - if (lineParts.length == 4) { - // name - Element pmName = new Element("name", ns); //NON-NLS - pmName.setText(lineParts[3].trim()); - placemark.addContent(pmName); - - String savedPath = lineParts[2].trim(); - if (savedPath.isEmpty() == false) { - // Path - Element pmPath = new Element("Path", ns); //NON-NLS - pmPath.setText(savedPath); - placemark.addContent(pmPath); - - // description - Element pmDescription = new Element("description", ns); //NON-NLS - String xml = "
" + featureType + ""); //NON-NLS + + String name = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME); + if (name != null && !name.isEmpty()) { + result.append("Name: ").append(name).append(SEP); //NON-NLS + } + + String location = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION); + if (location != null && !location.isEmpty()) { + result.append("Location: ").append(location).append(SEP); //NON-NLS + } + + Long timestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); + if (timestamp != null) { + result.append("Timestamp: ").append(getTimeStamp(timestamp)).append(SEP); //NON-NLS + result.append("Unix timestamp: ").append(timestamp).append(SEP); //NON-NLS + } + + Long startingTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START); + if (startingTimestamp != null) { + result.append("Starting Timestamp: ").append(getTimeStamp(startingTimestamp)).append(SEP); //NON-NLS + result.append("Starting Unix timestamp: ").append(startingTimestamp).append(SEP); //NON-NLS + } + + Long endingTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END); + if (endingTimestamp != null) { + result.append("Ending Timestamp: ").append(getTimeStamp(endingTimestamp)).append(SEP); //NON-NLS + result.append("Ending Unix timestamp: ").append(endingTimestamp).append(SEP); //NON-NLS + } + + Long createdTimestamp = getLong(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED); + if (createdTimestamp != null) { + result.append("Created Timestamp: ").append(getTimeStamp(createdTimestamp)).append(SEP); //NON-NLS + result.append("Created Unix timestamp: ").append(createdTimestamp).append(SEP); //NON-NLS + } + + Double latitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE); + if (latitude != null) { + result.append("Latitude: ").append(latitude).append(SEP); //NON-NLS + } + + Double longitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE); + if (longitude != null) { + result.append("Longitude: ").append(longitude).append(SEP); //NON-NLS + } + + Double latitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); + if (latitudeStart != null) { + result.append("Latitude Start: ").append(latitudeStart).append(SEP); //NON-NLS + } + + Double longitudeStart = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); + if (longitudeStart != null) { + result.append("Longitude Start: ").append(longitudeStart).append(SEP); //NON-NLS + } + + Double latitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); + if (latitudeEnd != null) { + result.append("Latitude End: ").append(latitudeEnd).append(SEP); //NON-NLS + } + + Double longitudeEnd = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); + if (longitudeEnd != null) { + result.append("Longitude End: ").append(longitudeEnd).append(SEP); //NON-NLS + } + + Double velocity = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VELOCITY); + if (velocity != null) { + result.append("Velocity: ").append(velocity).append(SEP); //NON-NLS + } + + Double altitude = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); + if (altitude != null) { + result.append("Altitude: ").append(altitude).append(SEP); //NON-NLS + } + + Double bearing = getDouble(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_BEARING); + if (bearing != null) { + result.append("Bearing: ").append(bearing).append(SEP); //NON-NLS + } + + Integer hPrecision = getInteger(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_HPRECISION); + if (hPrecision != null) { + result.append("Horizontal Precision Figure of Merit: ").append(hPrecision).append(SEP); //NON-NLS + } + + Integer vPrecision = getInteger(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VPRECISION); + if (vPrecision != null) { + result.append("Vertical Precision Figure of Merit: ").append(vPrecision).append(SEP); //NON-NLS + } + + String mapDatum = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_MAPDATUM); + if (mapDatum != null && !mapDatum.isEmpty()) { + result.append("Map Datum: ").append(mapDatum).append(SEP); //NON-NLS + } + + String programName = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME); + if (programName != null && !programName.isEmpty()) { + result.append("Reported by: ").append(programName).append(SEP); //NON-NLS + } + + String flag = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG); + if (flag != null && !flag.isEmpty()) { + result.append("Flag: ").append(flag).append(SEP); //NON-NLS + } + + String pathSource = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_SOURCE); + if (pathSource != null && !pathSource.isEmpty()) { + result.append("Source: ").append(pathSource).append(SEP); //NON-NLS + } + + String deviceMake = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE); + if (deviceMake != null && !deviceMake.isEmpty()) { + result.append("Device Make: ").append(deviceMake).append(SEP); //NON-NLS + } + + String deviceModel = getString(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL); + if (deviceModel != null && !deviceModel.isEmpty()) { + result.append("Device Model: ").append(deviceModel).append(SEP); //NON-NLS + } + + return result.toString(); + } + + private String getTimeStamp(long timeStamp) { + return kmlDateFormat.format(new java.util.Date(timeStamp * 1000)); + } + + /** + * Create a Point for use in a Placemark. Note in this method altitude is + * ignored, as Google Earth apparently has trouble using altitudes for + * LineStrings, though the parameters are still in the call. Also note that + * any null value passed in will be set to 0.0, under the idea that it is + * better to show some data with gaps, than to show nothing at all. + * + * @param latitude point latitude + * @param longitude point longitude + * @param altitude point altitude. Currently ignored. + * + * @return the Point as an Element + */ + private Element makePoint(Double latitude, Double longitude, Double altitude) { + if (latitude == null) { + latitude = 0.0; + } + if (longitude == null) { + longitude = 0.0; + } + if (altitude == null) { + altitude = 0.0; + } + Element point = new Element("Point", ns); //NON-NLS + + // KML uses lon, lat. Deliberately reversed. + Element coordinates = new Element("coordinates", ns).addContent(longitude + "," + latitude + "," + altitude); //NON-NLS + + if (altitude != 0) { + /* + Though we are including a non-zero altitude, clamp it to the + ground because inaccuracies from the GPS data can cause the terrain + to occlude points when zoomed in otherwise. Show the altitude, but + keep the point clamped to the ground. We may change this later for + flying GPS sensors. + */ + Element altitudeMode = new Element("altitudeMode", ns).addContent("clampToGround"); //NON-NLS + point.addContent(altitudeMode); + } + point.addContent(coordinates); + + return point; + } + + /** + * Create a LineString for use in a Placemark. Note in this method, start + * and stop altitudes get ignored, as Google Earth apparently has trouble + * using altitudes for LineStrings, though the parameters are still in the + * call. Also note that any null value passed in will be set to 0.0, under + * the idea that it is better to show some data with gaps, than to show + * nothing at all. + * + * @param startLatitude Starting latitude + * @param startLongitude Starting longitude + * @param startAltitude Starting altitude. Currently ignored. + * @param stopLatitude Ending latitude + * @param stopLongitude Ending longitude + * @param stopAltitude Ending altitude. Currently ignored. + * + * @return the Line as an Element + */ + private Element makeLineString(Double startLatitude, Double startLongitude, Double startAltitude, Double stopLatitude, Double stopLongitude, Double stopAltitude) { + if (startLatitude == null) { + startLatitude = 0.0; + } + if (startLongitude == null) { + startLongitude = 0.0; + } + if (startAltitude == null) { + startAltitude = 0.0; + } + if (stopLatitude == null) { + stopLatitude = 0.0; + } + if (stopLongitude == null) { + stopLongitude = 0.0; + } + if (stopAltitude == null) { + stopAltitude = 0.0; + } + + Element lineString = new Element("LineString", ns); //NON-NLS + lineString.addContent(new Element("extrude", ns).addContent("1")); //NON-NLS + lineString.addContent(new Element("tessellate", ns).addContent("1")); //NON-NLS + lineString.addContent(new Element("altitudeMode", ns).addContent("clampToGround")); //NON-NLS + // KML uses lon, lat. Deliberately reversed. + lineString.addContent(new Element("coordinates", ns).addContent( + startLongitude + "," + startLatitude + ",0.0," + + stopLongitude + "," + stopLatitude + ",0.0")); //NON-NLS + return lineString; + } + + /** + * Make a Placemark for use in displaying features. Takes a + * coordinate-bearing feature (Point, LineString, etc) and places it in the + * Placemark element. + * + * @param name Placemark name + * @param color Placemark color + * @param description Description for the info bubble on the map + * @param timestamp Placemark timestamp + * @param feature The feature to show. Could be Point, LineString, etc. + * @param coordinates The coordinates to display in the list view snippet + * + * @return the entire KML placemark + */ + private Element makePlacemark(String name, FeatureColor color, String description, Long timestamp, Element feature, String coordinates) { + Element placemark = new Element("Placemark", ns); //NON-NLS + if (name != null && !name.isEmpty()) { + placemark.addContent(new Element("name", ns).addContent(name)); //NON-NLS + } else if (timestamp != null) { + placemark.addContent(new Element("name", ns).addContent(getTimeStamp(timestamp))); //NON-NLS + } else { + placemark.addContent(new Element("name", ns).addContent("")); //NON-NLS + } + placemark.addContent(new Element("styleUrl", ns).addContent(color.getColor())); //NON-NLS + placemark.addContent(new Element("description", ns).addContent(description)); //NON-NLS + if (timestamp != null) { + Element time = new Element("TimeStamp", ns); //NON-NLS + time.addContent(new Element("when", ns).addContent(getTimeStamp(timestamp))); //NON-NLS + placemark.addContent(time); + } + placemark.addContent(feature); + if (coordinates != null && !coordinates.isEmpty()) { + placemark.addContent(new Element("snippet", ns).addContent(coordinates)); //NON-NLS + } + return placemark; + } + + /** + * Make a Placemark for use in displaying features. Takes a + * coordinate-bearing feature (Point, LineString, etc) and places it in the + * Placemark element. + * + * @param name Placemark file name + * @param color Placemark color + * @param description Description for the info bubble on the map + * @param timestamp Placemark timestamp + * @param feature The feature to show. Could be Point, LineString, etc. + * @param path The path to the file in the source image + * @param coordinates The coordinates to display in the list view snippet + * + * @return the entire KML Placemark, including a picture. + */ + private Element makePlacemarkWithPicture(String name, FeatureColor color, String description, Long timestamp, Element feature, Path path, String coordinates) { + Element placemark = new Element("Placemark", ns); //NON-NLS + Element desc = new Element("description", ns); //NON-NLS + if (name != null && !name.isEmpty()) { + placemark.addContent(new Element("name", ns).addContent(name)); //NON-NLS + String image = ""; //NON-NLS + desc.addContent(image); + } + placemark.addContent(new Element("styleUrl", ns).addContent(color.getColor())); //NON-NLS + if (path != null) { + String pathAsString = path.toString(); + if (pathAsString != null && !pathAsString.isEmpty()) { + desc.addContent(description + "Source Path: " + pathAsString); + } + } + placemark.addContent(desc); + + if (timestamp != null) { + Element time = new Element("TimeStamp", ns); //NON-NLS + time.addContent(new Element("when", ns).addContent(getTimeStamp(timestamp))); //NON-NLS + placemark.addContent(time); + } + placemark.addContent(feature); + if (coordinates != null && !coordinates.isEmpty()) { + placemark.addContent(new Element("snippet", ns).addContent(coordinates)); //NON-NLS + } + return placemark; + } + + /** + * Extracts the file to the output folder. + * + * @param inputFile The input AbstractFile to copy + * @param outputFile the output file + * + * @throws IOException + */ + private void copyFileUsingStream(AbstractFile inputFile, File outputFile) throws IOException { + byte[] buffer = new byte[65536]; + int length; + outputFile.createNewFile(); + try (InputStream is = new ReadContentInputStream(inputFile); + OutputStream os = new FileOutputStream(outputFile)) { while ((length = is.read(buffer)) != -1) { os.write(buffer, 0, length); } - - } finally { - is.close(); - os.close(); } } @@ -377,4 +845,36 @@ class ReportKML implements GeneralReportModule { public JPanel getConfigurationPanel() { return null; // No configuration panel } + + /** + * This is a smash-n-grab from AbstractFile.createNonUniquePath(String). + * This method is intended to be removed when img_ and vol_ are no longer + * added to images and volumes respectively, OR when AbstractFile is sorted + * out with respect to this. + * + * @param uniquePath The path to sanitize. + * + * @return path without leading img_/vol_ in position 0 or 1 respectively. + */ + private static String removeLeadingImgAndVol(String uniquePath) { + // split the path into parts + String[] pathSegments = uniquePath.replaceFirst("^/*", "").split("/"); //NON-NLS + + // Replace image/volume name if they exist in specific entries + if (pathSegments.length > 0) { + pathSegments[0] = pathSegments[0].replaceFirst("^img_", ""); //NON-NLS + } + if (pathSegments.length > 1) { + pathSegments[1] = pathSegments[1].replaceFirst("^vol_", ""); //NON-NLS + } + + // Assemble the path + StringBuilder strbuf = new StringBuilder(); + for (String segment : pathSegments) { + if (!segment.isEmpty()) { + strbuf.append("/").append(segment); + } + } + return strbuf.toString(); + } } diff --git a/Core/src/org/sleuthkit/autopsy/report/stylesheets/style.kml b/Core/src/org/sleuthkit/autopsy/report/stylesheets/style.kml new file mode 100755 index 0000000000..aeb3992df1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/report/stylesheets/style.kml @@ -0,0 +1,299 @@ + + + + + + normal + #n_YellowPushpin + + + highlight + #h_YellowPushpin + + + + + + + normal + #n_bluePushpin + + + highlight + #h_bluePushpin + + + + + + + normal + #n_redPushpin + + + highlight + #h_redPushpin + + + + + + + normal + #n_greenPushpin + + + highlight + #h_greenPushpin + + + + + + + normal + #n_purplePushpin + + + highlight + #h_purplePushpin + + + + + + + normal + #n_whitePushpin + + + highlight + #h_whitePushpin + + + + + + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties index d23cfbb773..5b74bea711 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/Bundle.properties @@ -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=\ \ No newline at end of file +ProgressWindow.progressHeader.text=\ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties index ba1ee4f94a..17f945778a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/Bundle_ja.properties @@ -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 \ No newline at end of file +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} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java index bbc213de10..ed3f3b307a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineController.java @@ -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 = new ReadOnlyObjectWrapper<>(VisualizationMode.COUNTS); - - synchronized public ReadOnlyObjectProperty visualizationModeProperty() { - return visualizationMode.getReadOnlyProperty(); - } + private final ReadOnlyObjectWrapper viewMode = new ReadOnlyObjectWrapper<>(ViewMode.COUNTS); @GuardedBy("filteredEvents") private final FilteredEventsModel filteredEvents; @@ -223,21 +220,38 @@ public class TimeLineController { @GuardedBy("this") private final ObservableList selectedEventIDs = FXCollections.synchronizedObservableList(FXCollections.observableArrayList()); + @GuardedBy("this") + private final ReadOnlyObjectWrapper 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 getSelectedEventIDs() { return selectedEventIDs; } - @GuardedBy("this") - private final ReadOnlyObjectWrapper 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 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 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 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())); } /** @@ -449,9 +487,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(); } @@ -582,17 +620,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 events) { final LoggedTask selectEventIDsTask = new LoggedTask("Select Event IDs", true) { //NON-NLS @Override @@ -624,16 +651,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) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form index 1f0f367115..a05e7505ec 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.form @@ -54,7 +54,7 @@ - + @@ -65,7 +65,7 @@
- + @@ -82,33 +82,45 @@ - - - - - - + - + + + + + + + + + + + + - - - - - - + - + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java index f91d2e8bba..1ca594fa14 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/TimeLineTopComponent.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit 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 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 availableModes(List modes) { return Collections.emptyList(); @@ -165,12 +295,12 @@ public final class TimeLineTopComponent extends TopComponent implements Explorer // //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 }// //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); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/VisualizationMode.java b/Core/src/org/sleuthkit/autopsy/timeline/ViewMode.java similarity index 92% rename from Core/src/org/sleuthkit/autopsy/timeline/VisualizationMode.java rename to Core/src/org/sleuthkit/autopsy/timeline/ViewMode.java index 8990b792bc..bbbc3fa11b 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/VisualizationMode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ViewMode.java @@ -21,7 +21,9 @@ package org.sleuthkit.autopsy.timeline; /** * */ -public enum VisualizationMode { +public enum ViewMode { - COUNTS, DETAIL; + COUNTS, + DETAIL, + LIST; } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java index e50e1909a8..7649b35705 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/actions/SaveSnapshotAsReport.java @@ -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", diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/CombinedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/CombinedEvent.java new file mode 100644 index 0000000000..71e022e65b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/CombinedEvent.java @@ -0,0 +1,152 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit 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 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 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 getEventTypes() { + return eventTypeMap.keySet(); + } + + /** + * Get the event IDs of the combined events. + * + * @return The event IDs of the combined events. + */ + public Collection 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; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java index ace1dd68ff..9f95d31337 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/EventCluster.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit 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 { */ private final ImmutableSet hashHits; - private EventCluster(Interval spanningInterval, EventType type, Set eventIDs, - Set hashHits, Set tagged, String description, DescriptionLoD lod, + private EventCluster(Interval spanningInterval, EventType type, Collection eventIDs, + Collection hashHits, Collection tagged, String description, DescriptionLoD lod, EventStripe parent) { this.span = spanningInterval; @@ -122,8 +122,8 @@ public class EventCluster implements MultiEvent { this.parent = parent; } - public EventCluster(Interval spanningInterval, EventType type, Set eventIDs, - Set hashHits, Set tagged, String description, DescriptionLoD lod) { + public EventCluster(Interval spanningInterval, EventType type, Collection eventIDs, + Collection hashHits, Collection tagged, String description, DescriptionLoD lod) { this(spanningInterval, type, eventIDs, hashHits, tagged, description, lod, null); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java index 1431673f22..6227256130 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/datamodel/FilteredEventsModel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-15 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit 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 getEventIDs(Interval timeRange, Filter filter) { + public List 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 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. diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java index 996c35ef3b..8bbfc756af 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventDB.java @@ -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 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 getEventIDs(Interval timeRange, RootFilter filter) { + Long startTime = timeRange.getStartMillis() / 1000; + Long endTime = timeRange.getEndMillis() / 1000; - Set 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 resultIDs = new HashSet<>(); + + ArrayList 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 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 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 eventIDs = SQLHelper.unGroupConcat(rs.getString("GROUP_CONCAT(events.event_id)"), Long::valueOf); + List eventTypes = SQLHelper.unGroupConcat(rs.getString("GROUP_CONCAT(sub_type)"), s -> RootEventType.allTypes.get(Integer.valueOf(s))); + Map 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 eventIDs = SQLHelper.unGroupConcat(eventIDsString, Long::valueOf); + List 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 hashHits = SQLHelper.unGroupConcat(rs.getString("hash_hits"), Long::valueOf); //NON-NLS - Set tagged = SQLHelper.unGroupConcat(rs.getString("taggeds"), Long::valueOf); //NON-NLS + List hashHits = SQLHelper.unGroupConcat(rs.getString("hash_hits"), Long::valueOf); //NON-NLS + List 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); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java index a93bf9dc18..f4396e3d95 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/EventsRepository.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-15 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit 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 getEventIDs(Interval timeRange, RootFilter filter) { + public List 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 getCombinedEvents(Interval timeRange, RootFilter filter) { + return eventDB.getCombinedEvents(timeRange, filter); + } + public Interval getSpanningInterval(Collection eventIDs) { return eventDB.getSpanningInterval(eventIDs); } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java index 208ffa1b75..4f6dfa3eb9 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java @@ -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 Set unGroupConcat(String groupConcat, Function mapper) { - return StringUtils.isBlank(groupConcat) ? Collections.emptySet() + static List unGroupConcat(String groupConcat, Function mapper) { + return StringUtils.isBlank(groupConcat) ? Collections.emptyList() : Stream.of(groupConcat.split(",")) .map(mapper::apply) - .collect(Collectors.toSet()); + .collect(Collectors.toList()); } /** diff --git a/Core/src/org/sleuthkit/autopsy/timeline/events/RefreshRequestedEvent.java b/Core/src/org/sleuthkit/autopsy/timeline/events/RefreshRequestedEvent.java index 47b04eb479..903cb55178 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/events/RefreshRequestedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/events/RefreshRequestedEvent.java @@ -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. diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/Bundle_ja.properties index d431985f98..7096881555 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/Bundle_ja.properties @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java index abb7245eda..ee22cfd295 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit 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 accept(DisplayableItemNodeVisitor 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 { + final private class TimeProperty extends PropertySupport.ReadWrite { 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); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java index 6fd3693514..3d1d44132e 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/explorernodes/EventRootNode.java @@ -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 fileIds, FilteredEventsModel filteredEvents) { + public EventRootNode(Collection 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 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 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 { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/images/table.png b/Core/src/org/sleuthkit/autopsy/timeline/images/table.png new file mode 100644 index 0000000000..0d1e11a834 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/timeline/images/table.png differ diff --git a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java index 75377ff5bb..917d2a0881 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/snapshot/SnapShotReportWriter.java @@ -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) { diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimeLineView.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimeLineView.java new file mode 100644 index 0000000000..86fa7ec132 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimeLineView.java @@ -0,0 +1,372 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2016 Basis Technology Corp. + * Contact: carrier sleuthkit 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 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 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 getNewUpdateTask(); + + /** + * Get a List of Nodes containing settings widgets to insert into this + * view's header. + * + * @return The List of settings Nodes. + */ + protected List 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 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 The type of a single object that can represent + * the range of data displayed along the X-Axis. + */ + protected abstract class ViewRefreshTask extends LoggedTask { + + 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); + }); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimelineChart.java similarity index 53% rename from Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java rename to Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimelineChart.java index 4aa137c94f..976c6f3fff 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractTimelineChart.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-16 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -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 The type of data plotted along the x axis * @param 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> extends BorderPane { +public abstract class AbstractTimelineChart> 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 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 getSelectedNodes() { + return selectedNodes; + } /** * Access to chart data via series @@ -130,123 +109,36 @@ public abstract class AbstractVisualizationPane updateTask; - - final private TimeLineController controller; - final private FilteredEventsModel filteredEvents; - final private ObservableList 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 of the nodes that are selected in - * this visualization. - */ - protected ObservableList getSelectedNodes() { - return selectedNodes; - } - - /** - * List of Nodes to insert into the toolbar. This should be set in an - * implementations constructor. - */ - private List settingsNodes; - - /** - * Get a List of nodes containing settings widgets to insert into this - * visualization's header. - * - * @return The List of settings Nodes. - */ - protected List 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 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 getNewUpdateTask(); - /** * Get the label that should be used for a tick mark at the given value. * @@ -342,16 +203,16 @@ public abstract class AbstractVisualizationPane 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 getYAxis(); @@ -364,74 +225,6 @@ public abstract class AbstractVisualizationPane { - 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 { - 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 controller.setStatusMessage(isHover() ? DEFAULT_TOOLTIP.getText() : "")); + hoverProperty().addListener(hoverProp -> controller.setStatusMessage(isHover() ? getDefaultTooltip().getText() : "")); } @@ -521,7 +309,15 @@ public abstract class AbstractVisualizationPane> tickMarks = getXAxis().getTickMarks().sorted(Comparator.comparing(Axis.TickMark::getPosition)); - if (tickMarks.isEmpty() == false) { + if (tickMarks.isEmpty()) { + /* + * Since StackedBarChart does some funky animation/background thread + * stuff, sometimes there are no tick marks even though there is + * data. Dispatching another call to layoutDateLables() allows that + * stuff time to run before we check a gain. + */ + Platform.runLater(this::layoutDateLabels); + } else { //get the spacing between ticks in the underlying axis double spacing = getTickSpacing(); @@ -681,117 +477,4 @@ public abstract class AbstractVisualizationPane The type of a single object that can represent - * the range of data displayed along the X-Axis. - */ - abstract protected class VisualizationRefreshTask extends LoggedTask { - - 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); - } } diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties index b08bf41e42..9751a035d5 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle.properties @@ -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 diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle_ja.properties index 2ba0472b32..5c8dacfed2 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/Bundle_ja.properties @@ -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 diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java b/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java deleted file mode 100644 index f61e2b5b28..0000000000 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/TimeLineResultView.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2013 Basis Technology Corp. - * Contact: carrier sleuthkit 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 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 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); - }); - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml b/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.fxml similarity index 89% rename from Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml rename to Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.fxml index 9bb04129a1..777986e600 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml +++ b/Core/src/org/sleuthkit/autopsy/timeline/ui/ViewFrame.fxml @@ -23,7 +23,7 @@ - @@ -81,7 +93,7 @@ -