Merge branch 'develop' into TL-list-view

Conflicts:
	Core/src/org/sleuthkit/autopsy/timeline/ui/AbstractVisualizationPane.java
	Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.fxml
	Core/src/org/sleuthkit/autopsy/timeline/ui/VisualizationPanel.java
This commit is contained in:
jmillman 2016-05-13 14:05:41 -04:00
commit f2b37fca09
25 changed files with 1024 additions and 1039 deletions

View File

@ -29,8 +29,8 @@ FileTypeIdGlobalSettingsPanel.JOptionPane.invalidInterestingFilesSetName.title=M
FileTypeIdGlobalSettingsPanel.JOptionPane.storeFailed.title=Save Failed
FileTypeIdGlobalSettingsPanel.JOptionPane.loadFailed.title=Load Failed
FileTypeIdGlobalSettingsPanel.ingestRunningWarningLabel.text=Cannot make changes to file type definitions when ingest is running!
UserDefinedFileTypesManager.loadFileTypes.errorMessage=Failed to load existing file type definitions.
UserDefinedFileTypesManager.saveFileTypes.errorMessage=Failed to save file type definitions.
FileTypeIdGlobalSettingsPanel.loadFileTypes.errorMessage=Failed to load existing file type definitions.
FileTypeIdGlobalSettingsPanel.saveFileTypes.errorMessage=Failed to save file type definitions.
FileTypeIdGlobalSettingsPanel.newTypeButton.text=New Type
FileTypeIdGlobalSettingsPanel.jLabel2.text=Custom MIME Types:
FileTypeIdGlobalSettingsPanel.jLabel3.text=Autopsy can automatically detect many file types. Add your custom file types here.

View File

@ -26,8 +26,8 @@ FileTypeIdGlobalSettingsPanel.signatureComboBox.asciiItem=\u30b9\u30c8\u30ea\u30
FileTypeIdGlobalSettingsPanel.signatureComboBox.rawItem=\u30d0\u30a4\u30c8\uff08HEX\uff09
OptionsCategory_Keywords_FileTypeId=\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7ID
OptionsCategory_Name_FileTypeId=\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7
UserDefinedFileTypesManager.loadFileTypes.errorMessage=\u65e2\u5b58\u306e\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u5b9a\u7fa9\u306e\u8aad\u307f\u8fbc\u307f\u306b\u5931\u6557\u3057\u307e\u3057\u305f
UserDefinedFileTypesManager.saveFileTypes.errorMessage=\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u5b9a\u7fa9\u306e\u4fdd\u5b58\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
FileTypeIdGlobalSettingsPanel.loadFileTypes.errorMessage=\u65e2\u5b58\u306e\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u5b9a\u7fa9\u306e\u8aad\u307f\u8fbc\u307f\u306b\u5931\u6557\u3057\u307e\u3057\u305f
FileTypeIdGlobalSettingsPanel.saveFileTypes.errorMessage=\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u5b9a\u7fa9\u306e\u4fdd\u5b58\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
FileTypeIdGlobalSettingsPanel.JOptionPane.invalidInterestingFilesSetName.message=\u30a2\u30e9\u30fc\u30c8\u3092\u8a2d\u5b9a\u3059\u308b\u306b\u306f\u7591\u308f\u3057\u3044\u30d5\u30a1\u30a4\u30eb\u30bb\u30c3\u30c8\u540d\u304c\u5fc5\u8981\u3067\u3059\u3002
FileTypeIdGlobalSettingsPanel.offsetComboBox.startItem=\u958b\u59cb
FileTypeIdGlobalSettingsPanel.offsetComboBox.endItem=\u505c\u6b62

View File

@ -0,0 +1,503 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.modules.filetypeid;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.DatatypeConverter;
import javax.xml.parsers.ParserConfigurationException;
import org.openide.util.io.NbObjectInputStream;
import org.openide.util.io.NbObjectOutputStream;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.coreutils.XMLUtil;
import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* A singleton manager for the custom file types defined by Autopsy and by
* users.
*/
final class CustomFileTypesManager {
private static final String SERIALIZED_SETTINGS_FILE = "UserFileTypeDefinitions.settings"; //NON-NLS
private static final String XML_SETTINGS_FILE = "UserFileTypeDefinitions.xml"; //NON-NLS
private static final String FILE_TYPES_TAG_NAME = "FileTypes"; //NON-NLS
private static final String FILE_TYPE_TAG_NAME = "FileType"; //NON-NLS
private static final String MIME_TYPE_TAG_NAME = "MimeType"; //NON-NLS
private static final String SIGNATURE_TAG_NAME = "Signature"; //NON-NLS
private static final String SIGNATURE_TYPE_ATTRIBUTE = "type"; //NON-NLS
private static final String BYTES_TAG_NAME = "Bytes"; //NON-NLS
private static final String OFFSET_TAG_NAME = "Offset"; //NON-NLS
private static final String RELATIVE_ATTRIBUTE = "RelativeToStart"; //NON-NLS
private static CustomFileTypesManager instance;
private final List<FileType> autopsyDefinedFileTypes = new ArrayList<>();
private List<FileType> userDefinedFileTypes = new ArrayList<>();
/**
* Gets the singleton manager of the custom file types defined by Autopsy
* and by users.
*
* @return The custom file types manager singleton.
*
* @throws CustomFileTypesException if there is a problem loading the custom
* file types.
*/
synchronized static CustomFileTypesManager getInstance() throws CustomFileTypesException {
if (null == instance) {
instance = new CustomFileTypesManager();
try {
instance.loadUserDefinedFileTypes();
instance.createAutopsyDefinedFileTypes();
} catch (CustomFileTypesException ex) {
instance = null;
throw ex;
}
}
return instance;
}
/**
* Constructs a manager for the custom file types defined by Autopsy and by
* users.
*/
private CustomFileTypesManager() {
}
/**
* Gets the custom file types defined by Autopsy and by users.
*
* @return A list of custom file types, possibly empty.
*/
synchronized List<FileType> getFileTypes() {
/**
* It is safe to return references instead of copies in this snapshot
* because FileType objects are immutable.
*/
List<FileType> customTypes = new ArrayList<>(userDefinedFileTypes);
customTypes.addAll(autopsyDefinedFileTypes);
return customTypes;
}
/**
* Gets the user-defined custom file types.
*
* @return A list of file types, possibly empty.
*/
synchronized List<FileType> getUserDefinedFileTypes() {
/**
* It is safe to return references instead of copies in this snapshot
* because FileType objects are immutable.
*/
return new ArrayList<>(userDefinedFileTypes);
}
/**
* Sets the user-defined custom file types.
*
* @param newFileTypes A list of user-defined file types.
*
* @throws CustomFileTypesException if there is a problem setting the file
* types.
*/
synchronized void setUserDefinedFileTypes(List<FileType> newFileTypes) throws CustomFileTypesException {
String filePath = getFileTypeDefinitionsFilePath(SERIALIZED_SETTINGS_FILE);
writeSerializedFileTypes(newFileTypes, filePath);
userDefinedFileTypes = newFileTypes;
}
/**
* Creates the custom file types defined by Autopsy.
*
* @throws CustomFileTypesException if there is a problem creating the file
* types.
*/
private void createAutopsyDefinedFileTypes() throws CustomFileTypesException {
byte[] byteArray;
FileType fileType;
try {
/*
* Add type for xml.
*/
List<Signature> signatureList;
signatureList = new ArrayList<>();
signatureList.add(new Signature("<?xml", 0L)); //NON-NLS
fileType = new FileType("text/xml", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for gzip.
*/
byteArray = DatatypeConverter.parseHexBinary("1F8B"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("application/x-gzip", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for wk1.
*/
byteArray = DatatypeConverter.parseHexBinary("0000020006040600080000000000"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("application/x-123", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for Radiance images.
*/
byteArray = DatatypeConverter.parseHexBinary("233F52414449414E43450A");//NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("image/vnd.radiance", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for dcx images.
*/
byteArray = DatatypeConverter.parseHexBinary("B168DE3A"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("image/x-dcx", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for ics images.
*/
signatureList.clear();
signatureList.add(new Signature("icns", 0L)); //NON-NLS
fileType = new FileType("image/x-icns", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for pict images.
*/
byteArray = DatatypeConverter.parseHexBinary("001102FF"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 522L));
fileType = new FileType("image/x-pict", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
byteArray = DatatypeConverter.parseHexBinary("1100"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 522L));
fileType = new FileType("image/x-pict", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for pam.
*/
signatureList.clear();
signatureList.add(new Signature("P7", 0L)); //NON-NLS
fileType = new FileType("image/x-portable-arbitrarymap", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for pfm.
*/
signatureList.clear();
signatureList.add(new Signature("PF", 0L)); //NON-NLS
fileType = new FileType("image/x-portable-floatmap", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
signatureList.clear();
signatureList.add(new Signature("Pf", 0L)); //NON-NLS
fileType = new FileType("image/x-portable-floatmap", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for tga.
*/
byteArray = DatatypeConverter.parseHexBinary("54525545564953494F4E2D5846494C452E00"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 17, false));
fileType = new FileType("image/x-tga", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for ilbm.
*/
signatureList.clear();
signatureList.add(new Signature("FORM", 0L)); //NON-NLS
signatureList.add(new Signature("ILBM", 8L)); //NON-NLS
fileType = new FileType("image/x-ilbm", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
signatureList.clear();
signatureList.add(new Signature("FORM", 0L)); //NON-NLS
signatureList.add(new Signature("PBM", 8L)); //NON-NLS
fileType = new FileType("image/x-ilbm", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for webp.
*/
signatureList.clear();
signatureList.add(new Signature("RIFF", 0L)); //NON-NLS
signatureList.add(new Signature("WEBP", 8L)); //NON-NLS
fileType = new FileType("image/webp", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for aiff.
*/
signatureList.clear();
signatureList.add(new Signature("FORM", 0L)); //NON-NLS
signatureList.add(new Signature("AIFF", 8L)); //NON-NLS
fileType = new FileType("audio/aiff", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
signatureList.clear();
signatureList.add(new Signature("FORM", 0L)); //NON-NLS
signatureList.add(new Signature("AIFC", 8L)); //NON-NLS
fileType = new FileType("audio/aiff", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
signatureList.clear();
signatureList.add(new Signature("FORM", 0L)); //NON-NLS
signatureList.add(new Signature("8SVX", 8L)); //NON-NLS
fileType = new FileType("audio/aiff", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for iff.
*/
signatureList.clear();
signatureList.add(new Signature("FORM", 0L)); //NON-NLS
fileType = new FileType("application/x-iff", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
} catch (IllegalArgumentException ex) {
/*
* parseHexBinary() throws this if the argument passed in is not hex
*/
throw new CustomFileTypesException("Error creating Autopsy defined custom file types", ex); //NON-NLS
}
}
/**
* Loads the custom file types defined by users.
*
* @throws CustomFileTypesException if there is a problem loading the file
* types.
*/
private void loadUserDefinedFileTypes() throws CustomFileTypesException {
userDefinedFileTypes.clear();
String filePath = getFileTypeDefinitionsFilePath(SERIALIZED_SETTINGS_FILE);
if (new File(filePath).exists()) {
userDefinedFileTypes = readSerializedFileTypes(filePath);
} else {
filePath = getFileTypeDefinitionsFilePath(XML_SETTINGS_FILE);
if (new File(filePath).exists()) {
userDefinedFileTypes = readFileTypesXML(filePath);
}
}
}
/**
* Writes serialized custom file types to a file.
*
* @param fileTypes A collection of file types.
* @param filePath The path to the file.
*
* @throws CustomFileTypesException if there is a problem writing the file
* types.
*/
private static void writeSerializedFileTypes(List<FileType> fileTypes, String filePath) throws CustomFileTypesException {
try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(filePath))) {
UserDefinedFileTypesSettings settings = new UserDefinedFileTypesSettings(fileTypes);
out.writeObject(settings);
} catch (IOException ex) {
throw new CustomFileTypesException(String.format("Failed to write settings to %s", filePath), ex); //NON-NLS
}
}
/**
* Reads serialized custom file types from a file.
*
* @param filePath The path to the file.
*
* @return The custom file types.
*
* @throws CustomFileTypesException if there is a problem reading the file
* types.
*/
private static List<FileType> readSerializedFileTypes(String filePath) throws CustomFileTypesException {
File serializedDefs = new File(filePath);
try {
try (NbObjectInputStream in = new NbObjectInputStream(new FileInputStream(serializedDefs))) {
UserDefinedFileTypesSettings filesSetsSettings = (UserDefinedFileTypesSettings) in.readObject();
return filesSetsSettings.getUserDefinedFileTypes();
}
} catch (IOException | ClassNotFoundException ex) {
throw new CustomFileTypesException(String.format("Failed to read ssettings from %s", filePath), ex); //NON-NLS
}
}
/**
* Reads custom file type definitions from an XML file.
*
* @param filePath The path to the file.
*
* @return A collection of custom file types read from the XML file.
*
* @throws IOException if there is problem reading the XML
* file.
* @throws SAXException if there is a problem parsing the
* XML file.
* @throws ParserConfigurationException if there is a problem parsing the
* XML file.
*/
private static List<FileType> readFileTypesXML(String filePath) throws CustomFileTypesException {
try {
List<FileType> fileTypes = new ArrayList<>();
Document doc = XMLUtil.loadDocument(filePath);
if (doc != null) {
Element fileTypesElem = doc.getDocumentElement();
if (fileTypesElem != null && fileTypesElem.getNodeName().equals(FILE_TYPES_TAG_NAME)) {
NodeList fileTypeElems = fileTypesElem.getElementsByTagName(FILE_TYPE_TAG_NAME);
for (int i = 0; i < fileTypeElems.getLength(); ++i) {
Element fileTypeElem = (Element) fileTypeElems.item(i);
FileType fileType = parseFileType(fileTypeElem);
fileTypes.add(fileType);
}
}
}
return fileTypes;
} catch (IOException | ParserConfigurationException | SAXException ex) {
throw new CustomFileTypesException(String.format("Failed to read ssettings from %s", filePath), ex); //NON-NLS
}
}
/**
* Gets a custom file type definition from a file type XML element.
*
* @param fileTypeElem The XML element.
*
* @return A file type object.
*
* @throws IllegalArgumentException if there is a problem parsing the file
* type.
* @throws NumberFormatException if there is a problem parsing the file
* type.
*/
private static FileType parseFileType(Element fileTypeElem) throws IllegalArgumentException, NumberFormatException {
String mimeType = parseMimeType(fileTypeElem);
Signature signature = parseSignature(fileTypeElem);
// File type definitions in the XML file were written prior to the
// implementation of multiple signatures per type.
List<Signature> sigList = new ArrayList<>();
sigList.add(signature);
return new FileType(mimeType, sigList);
}
/**
* Gets the MIME type from a file type XML element.
*
* @param fileTypeElem The element
*
* @return A MIME type string.
*/
private static String parseMimeType(Element fileTypeElem) {
return getChildElementTextContent(fileTypeElem, MIME_TYPE_TAG_NAME);
}
/**
* Gets the signature from a file type XML element.
*
* @param fileTypeElem The XML element.
*
* @return The signature.
*/
private static Signature parseSignature(Element fileTypeElem) throws IllegalArgumentException, NumberFormatException {
NodeList signatureElems = fileTypeElem.getElementsByTagName(SIGNATURE_TAG_NAME);
Element signatureElem = (Element) signatureElems.item(0);
String sigTypeAttribute = signatureElem.getAttribute(SIGNATURE_TYPE_ATTRIBUTE);
Signature.Type signatureType = Signature.Type.valueOf(sigTypeAttribute);
String sigBytesString = getChildElementTextContent(signatureElem, BYTES_TAG_NAME);
byte[] signatureBytes = DatatypeConverter.parseHexBinary(sigBytesString);
Element offsetElem = (Element) signatureElem.getElementsByTagName(OFFSET_TAG_NAME).item(0);
String offsetString = offsetElem.getTextContent();
long offset = DatatypeConverter.parseLong(offsetString);
boolean isRelativeToStart;
String relativeString = offsetElem.getAttribute(RELATIVE_ATTRIBUTE);
if (null == relativeString || relativeString.equals("")) {
isRelativeToStart = true;
} else {
isRelativeToStart = DatatypeConverter.parseBoolean(relativeString);
}
return new Signature(signatureBytes, offset, signatureType, isRelativeToStart);
}
/**
* Gets the text content of a single child element.
*
* @param elem The parent element.
* @param tagName The tag name of the child element.
*
* @return The text content or null if the tag doesn't exist.
*/
private static String getChildElementTextContent(Element elem, String tagName) {
NodeList childElems = elem.getElementsByTagName(tagName);
Node childNode = childElems.item(0);
if (childNode == null) {
return null;
}
Element childElem = (Element) childNode;
return childElem.getTextContent();
}
/**
* Gets the absolute path of a file type definitions file.
*
* @param fileName The name of the file.
*
* @return The absolute path to the file.
*/
private static String getFileTypeDefinitionsFilePath(String fileName) {
Path filePath = Paths.get(PlatformUtil.getUserConfigDirectory(), fileName);
return filePath.toAbsolutePath().toString();
}
/**
* .An exception thrown by the custom file types manager.
*/
static class CustomFileTypesException extends Exception {
private static final long serialVersionUID = 1L;
CustomFileTypesException(String message) {
super(message);
}
CustomFileTypesException(String message, Throwable throwable) {
super(message, throwable);
}
}
}

View File

@ -24,9 +24,7 @@ import java.util.SortedSet;
import org.apache.tika.Tika;
import org.apache.tika.mime.MediaType;
import org.apache.tika.mime.MimeTypes;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
@ -34,24 +32,22 @@ import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* Detects the MIME type of a file by an inspection of its contents, using both
* user-defined type definitions and Tika.
* Detects the MIME type of a file by an inspection of its contents, using
* custom file type definitions by users, custom file type definitions by
* Autopsy, and Tika.
*/
@NbBundle.Messages({
"CouldNotInitializeFileTypeDetector=Error loading user-defined file types."
})
public class FileTypeDetector {
private static final Tika tika = new Tika();
private static final int BUFFER_SIZE = 64 * 1024;
private final byte buffer[] = new byte[BUFFER_SIZE];
private final List<FileType> userDefinedFileTypes;
private static final Logger logger = Logger.getLogger(FileTypeDetector.class.getName());
private final List<FileType> autopsyDefinedFileTypes;
/**
* Constructs an object that detects the MIME type of a file by an
* inspection of its contents, using both user-defined type definitions and
* Tika.
* inspection of its contents, using custom file type definitions by users,
* custom file type definitions by Autopsy, and Tika.
*
* @throws FileTypeDetectorInitException if an initialization error occurs,
* e.g., user-defined file type
@ -60,25 +56,28 @@ public class FileTypeDetector {
*/
public FileTypeDetector() throws FileTypeDetectorInitException {
try {
userDefinedFileTypes = UserDefinedFileTypesManager.getInstance().getFileTypes();
} catch (UserDefinedFileTypesManager.UserDefinedFileTypesException ex) {
throw new FileTypeDetectorInitException(Bundle.CouldNotInitializeFileTypeDetector(), ex);
userDefinedFileTypes = CustomFileTypesManager.getInstance().getFileTypes();
autopsyDefinedFileTypes = CustomFileTypesManager.getInstance().getFileTypes();
} catch (CustomFileTypesManager.CustomFileTypesException ex) {
throw new FileTypeDetectorInitException("Error loading custom file types", ex); //NON-NLS
}
}
/**
* Gets the names of the user-defined MIME types.
* Gets the names of the custom file types defined by the user or by
* Autopsy.
*
* @return A list of the user-defined MIME types.
*/
public List<String> getUserDefinedTypes() {
List<String> list = new ArrayList<>();
if (userDefinedFileTypes != null) {
for (FileType fileType : userDefinedFileTypes) {
list.add(fileType.getMimeType());
}
List<String> customFileTypes = new ArrayList<>();
for (FileType fileType : userDefinedFileTypes) {
customFileTypes.add(fileType.getMimeType());
}
return list;
for (FileType fileType : autopsyDefinedFileTypes) {
customFileTypes.add(fileType.getMimeType());
}
return customFileTypes;
}
/**
@ -90,7 +89,9 @@ public class FileTypeDetector {
* @return True or false.
*/
public boolean isDetectable(String mimeType) {
return isDetectableAsUserDefinedType(mimeType) || isDetectableByTika(mimeType);
return isDetectableAsCustomType(userDefinedFileTypes, mimeType)
|| isDetectableAsCustomType(autopsyDefinedFileTypes, mimeType)
|| isDetectableByTika(mimeType);
}
/**
@ -101,8 +102,8 @@ public class FileTypeDetector {
*
* @return True or false.
*/
private boolean isDetectableAsUserDefinedType(String mimeType) {
for (FileType fileType : userDefinedFileTypes) {
private boolean isDetectableAsCustomType(List<FileType> customTypes, String mimeType) {
for (FileType fileType : customTypes) {
if (fileType.getMimeType().equals(mimeType)) {
return true;
}
@ -204,12 +205,21 @@ public class FileTypeDetector {
}
/*
* If the file is a regular file, give precedence to user-defined types.
* If the file is a regular file, give precedence to user-defined custom
* file types.
*/
if (null == mimeType) {
mimeType = detectUserDefinedType(file);
}
/*
* If the file does not match a user-defined type, give precedence to
* custom file types defined by Autopsy.
*/
if (null == mimeType) {
mimeType = detectAutopsyDefinedType(file);
}
/*
* If the file does not match a user-defined type, send the initial
* bytes to Tika.
@ -282,8 +292,8 @@ public class FileTypeDetector {
}
/**
* Determines whether or not the a file matches a user-defined or Autopsy
* predefined file type.
* Determines whether or not the a file matches a user-defined custom file
* type.
*
* @param file The file to test.
*
@ -300,6 +310,25 @@ public class FileTypeDetector {
return null;
}
/**
* Determines whether or not the a file matches a custom file type defined
* by Autopsy.
*
* @param file The file to test.
*
* @return The file type name string or null, if no match is detected.
*
* @throws TskCoreException
*/
private String detectAutopsyDefinedType(AbstractFile file) throws TskCoreException {
for (FileType fileType : autopsyDefinedFileTypes) {
if (fileType.matches(file)) {
return fileType.getMimeType();
}
}
return null;
}
/*
* Exception thrown when a file type detector experiences an error
* condition.
@ -345,7 +374,6 @@ public class FileTypeDetector {
* instead of querying the blackboard.
*/
@Deprecated
@SuppressWarnings("deprecation")
public String detectAndPostToBlackboard(AbstractFile file) throws TskCoreException {
return getFileType(file);
}

View File

@ -24,6 +24,7 @@ import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListModel;
import javax.swing.JOptionPane;
@ -32,10 +33,11 @@ import javax.swing.event.ListSelectionListener;
import org.netbeans.spi.options.OptionsPanelController;
import org.openide.util.NbBundle;
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.UserDefinedFileTypesManager.UserDefinedFileTypesException;
import org.sleuthkit.autopsy.modules.filetypeid.CustomFileTypesManager.CustomFileTypesException;
/**
* A panel to allow a user to make custom file type definitions. In addition to
@ -43,12 +45,15 @@ import org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.User
* appears in the NetBeans options dialog as an options panel.
*/
final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(FileTypeIdGlobalSettingsPanel.class.getName());
private static final String RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureComboBox.rawItem");
private static final String ASCII_SIGNATURE_TYPE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureComboBox.asciiItem");
private static final String START_OFFSET_RELATIVE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.offsetComboBox.startItem");
private static final String END_OFFSET_RELATIVE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.offsetComboBox.endItem");
/**
* The list model for the file types list component of this panel is the set
* of MIME types associated with the user-defined file types. A mapping of
@ -207,14 +212,15 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
@Override
public void load() {
try {
fileTypes = UserDefinedFileTypesManager.getInstance().getUserDefinedFileTypes();
fileTypes = CustomFileTypesManager.getInstance().getUserDefinedFileTypes();
updateFileTypesListModel();
if (!typesListModel.isEmpty()) {
typesList.setSelectedIndex(0);
}
} catch (UserDefinedFileTypesException ex) {
} catch (CustomFileTypesException ex) {
logger.log(Level.SEVERE, "Failed to get custom file types", ex);
JOptionPane.showMessageDialog(null,
ex.getLocalizedMessage(),
NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.loadFileTypes.errorMessage"),
NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.loadFailed.title"),
JOptionPane.ERROR_MESSAGE);
fileTypes = Collections.emptyList();
@ -266,10 +272,11 @@ final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPane
@Override
public void store() {
try {
UserDefinedFileTypesManager.getInstance().setUserDefinedFileTypes(fileTypes);
} catch (UserDefinedFileTypesManager.UserDefinedFileTypesException ex) {
CustomFileTypesManager.getInstance().setUserDefinedFileTypes(fileTypes);
} catch (CustomFileTypesManager.CustomFileTypesException ex) {
logger.log(Level.SEVERE, "Failed to set custom file types", ex);
JOptionPane.showMessageDialog(null,
ex.getLocalizedMessage(),
NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.saveFileTypes.errorMessage"),
NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.storeFailed.title"),
JOptionPane.ERROR_MESSAGE);
}

View File

@ -1,616 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.modules.filetypeid;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import javax.xml.bind.DatatypeConverter;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.openide.util.NbBundle;
import org.openide.util.io.NbObjectInputStream;
import org.openide.util.io.NbObjectOutputStream;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.coreutils.XMLUtil;
import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* Manages user-defined file types characterized by MIME type, signature, and
* optional membership in an interesting files set.
* <p>
* Note that this class exposes a very simple get/set API that operates on the
* user-defined file types as a complete set - there is no concept of adding,
* editing or deleting file types singly. This works because this class is not
* exposed outside of this ingest module package and is ONLY used in a very
* specific paradigm. In this paradigm, there is a single modal writer of file
* types in the form of a global settings panel that disables itself when ingest
* is running so that multiple readers in the form of file ingest modules get a
* consistent set of file type definitions.
* <p>
* Thread-safe.
*/
final class UserDefinedFileTypesManager {
private static final Logger logger = Logger.getLogger(UserDefinedFileTypesManager.class.getName());
private static final String USER_DEFINED_TYPES_XML_FILE = "UserFileTypeDefinitions.xml"; //NON-NLS
private static final String USER_DEFINED_TYPES_SERIALIZATION_FILE = "UserFileTypeDefinitions.settings";
private static final String FILE_TYPES_TAG_NAME = "FileTypes"; //NON-NLS
private static final String FILE_TYPE_TAG_NAME = "FileType"; //NON-NLS
private static final String MIME_TYPE_TAG_NAME = "MimeType"; //NON-NLS
private static final String SIGNATURE_TAG_NAME = "Signature"; //NON-NLS
private static final String SIGNATURE_TYPE_ATTRIBUTE = "type"; //NON-NLS
private static final String BYTES_TAG_NAME = "Bytes"; //NON-NLS
private static final String OFFSET_TAG_NAME = "Offset"; //NON-NLS
private static final String RELATIVE_ATTRIBUTE = "RelativeToStart"; //NON-NLS
private static final String INTERESTING_FILES_SET_TAG_NAME = "InterestingFileSset"; //NON-NLS
private static final String ALERT_ATTRIBUTE = "alert"; //NON-NLS
private static final String ENCODING_FOR_XML_FILE = "UTF-8"; //NON-NLS
private static UserDefinedFileTypesManager instance;
/**
* File types to be persisted to the user-defined file type definitions file
* are stored in this mapping of MIME types to file types. Access to this
* map is guarded by the intrinsic lock of the user-defined file types
* manager for thread-safety.
*/
private final List<FileType> userDefinedFileTypes = new ArrayList<>();
/**
* The combined set of user-defined file types and file types predefined by
* Autopsy are stored in this mapping of MIME types to file types. This is
* the current working set of file types. Access to this map is guarded by
* the intrinsic lock of the user-defined file types manager for
* thread-safety.
*/
private final List<FileType> fileTypes = new ArrayList<>();
/**
* Gets the singleton manager of user-defined file types characterized by
* MIME type, signature, and optional membership in an interesting files
* set.
*
* @return The user-defined file types manager singleton.
*/
synchronized static UserDefinedFileTypesManager getInstance() {
if (instance == null) {
instance = new UserDefinedFileTypesManager();
}
return instance;
}
/**
* Creates a manager of user-defined file types characterized by MIME type,
* signature, and optional membership in an interesting files set.
*/
private UserDefinedFileTypesManager() {
}
/**
* Gets both the predefined and the user-defined file types.
*
* @return A mapping of file type names to file types, possibly empty.
*
* @throws
* org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException
*/
synchronized List<FileType> getFileTypes() throws UserDefinedFileTypesException {
loadFileTypes();
/**
* It is safe to return references to the internal file type objects
* because they are immutable. Note that
* Collections.unmodifiableCollection() is not used here because this
* view of the file types is a snapshot.
*/
return new ArrayList<>(fileTypes);
}
/**
* Gets the user-defined file types.
*
* @return A mapping of file type names to file types, possibly empty.
*
* @throws
* org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException
*/
synchronized List<FileType> getUserDefinedFileTypes() throws UserDefinedFileTypesException {
loadFileTypes();
/**
* It is safe to return references to the internal file type objects
* because they are immutable. Note that
* Collections.unmodifiableCollection() is not used here because this
* view of the file types is a snapshot.
*/
return new ArrayList<>(userDefinedFileTypes);
}
/**
* Loads the MIME type to file type mappings with predefined and
* user-defined types.
*
* @throws
* org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException
*/
private void loadFileTypes() throws UserDefinedFileTypesException {
fileTypes.clear();
userDefinedFileTypes.clear();
/**
* Load the predefined types first so that they can be overwritten by
* any user-defined types with the same names.
*/
loadPredefinedFileTypes();
loadUserDefinedFileTypes();
}
/**
* Adds the predefined file types to the in-memory mappings of MIME types to
* file types.
*
* @throws
* org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException
*/
private void loadPredefinedFileTypes() throws UserDefinedFileTypesException {
byte[] byteArray;
FileType fileType;
try {
List<Signature> signatureList;
signatureList = new ArrayList<>();
signatureList.add(new Signature("<?xml", 0L));
fileType = new FileType("text/xml", signatureList); //NON-NLS
fileTypes.add(fileType);
// Add rule for gzip
byteArray = DatatypeConverter.parseHexBinary("1F8B"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("application/x-gzip", signatureList); //NON-NLS
fileTypes.add(fileType);
// Add rule for .wk1
byteArray = DatatypeConverter.parseHexBinary("0000020006040600080000000000"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("application/x-123", signatureList); //NON-NLS
fileTypes.add(fileType);
// Add rule for Radiance image
byteArray = DatatypeConverter.parseHexBinary("233F52414449414E43450A");//NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("image/vnd.radiance", signatureList); //NON-NLS
fileTypes.add(fileType);
// Add rule for .dcx image
byteArray = DatatypeConverter.parseHexBinary("B168DE3A"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("image/x-dcx", signatureList); //NON-NLS
fileTypes.add(fileType);
// Add rule for .ics image
signatureList.clear();
signatureList.add(new Signature("icns", 0L));
fileType = new FileType("image/x-icns", signatureList); //NON-NLS
fileTypes.add(fileType);
// Add rule for .pict image
byteArray = DatatypeConverter.parseHexBinary("001102FF"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 522L));
fileType = new FileType("image/x-pict", signatureList); //NON-NLS
fileTypes.add(fileType);
byteArray = DatatypeConverter.parseHexBinary("1100"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 522L));
fileType = new FileType("image/x-pict", signatureList); //NON-NLS
fileTypes.add(fileType);
// Add rule for .pam
signatureList.clear();
signatureList.add(new Signature("P7", 0L));
fileType = new FileType("image/x-portable-arbitrarymap", signatureList); //NON-NLS
fileTypes.add(fileType);
// Add rule for .pfm
signatureList.clear();
signatureList.add(new Signature("PF", 0L));
fileType = new FileType("image/x-portable-floatmap", signatureList); //NON-NLS
fileTypes.add(fileType);
signatureList.clear();
signatureList.add(new Signature("Pf", 0L));
fileType = new FileType("image/x-portable-floatmap", signatureList); //NON-NLS
fileTypes.add(fileType);
// Add rule for .tga
byteArray = DatatypeConverter.parseHexBinary("54525545564953494F4E2D5846494C452E00"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 17, false));
fileType = new FileType("image/x-tga", signatureList); //NON-NLS
fileTypes.add(fileType);
// Add rule for .ilbm
signatureList.clear();
signatureList.add(new Signature("FORM", 0L));
signatureList.add(new Signature("ILBM", 8L));
fileType = new FileType("image/x-ilbm", signatureList);
fileTypes.add(fileType);
signatureList.clear();
signatureList.add(new Signature("FORM", 0L));
signatureList.add(new Signature("PBM", 8L));
fileType = new FileType("image/x-ilbm", signatureList);
fileTypes.add(fileType);
// Add rule for .webp
signatureList.clear();
signatureList.add(new Signature("RIFF", 0L));
signatureList.add(new Signature("WEBP", 8L));
fileType = new FileType("image/webp", signatureList);
fileTypes.add(fileType);
// Add rule for .aiff
signatureList.clear();
signatureList.add(new Signature("FORM", 0L));
signatureList.add(new Signature("AIFF", 8L));
fileType = new FileType("audio/aiff", signatureList);
fileTypes.add(fileType);
signatureList.clear();
signatureList.add(new Signature("FORM", 0L));
signatureList.add(new Signature("AIFC", 8L));
fileType = new FileType("audio/aiff", signatureList);
fileTypes.add(fileType);
signatureList.clear();
signatureList.add(new Signature("FORM", 0L));
signatureList.add(new Signature("8SVX", 8L));
fileType = new FileType("audio/aiff", signatureList);
fileTypes.add(fileType);
// Add .iff
signatureList.clear();
signatureList.add(new Signature("FORM", 0L));
fileType = new FileType("application/x-iff", signatureList);
fileTypes.add(fileType);
} // parseHexBinary() throws this if the argument passed in is not Hex
catch (IllegalArgumentException e) {
throw new UserDefinedFileTypesException("Error creating predefined file types", e); //
}
}
/**
* Adds the user-defined file types to the in-memory mappings of MIME types
* to file types.
*
* @throws
* org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException
*/
private void loadUserDefinedFileTypes() throws UserDefinedFileTypesException {
try {
File serialized = new File(getFileTypeDefinitionsFilePath(USER_DEFINED_TYPES_SERIALIZATION_FILE));
if (serialized.exists()) {
for (FileType fileType : readFileTypesSerialized()) {
addUserDefinedFileType(fileType);
}
} else {
String filePath = getFileTypeDefinitionsFilePath(USER_DEFINED_TYPES_XML_FILE);
File xmlFile = new File(filePath);
if (xmlFile.exists()) {
for (FileType fileType : XMLDefinitionsReader.readFileTypes(filePath)) {
addUserDefinedFileType(fileType);
}
}
}
} catch (IOException | ParserConfigurationException | SAXException ex) {
/**
* Using an all-or-none policy.
*/
fileTypes.clear();
userDefinedFileTypes.clear();
throwUserDefinedFileTypesException(ex, "UserDefinedFileTypesManager.loadFileTypes.errorMessage");
}
}
/**
* Adds a user-defined file type to the in-memory mappings of MIME types to
* file types.
*
* @param fileType The file type to add.
*/
private void addUserDefinedFileType(FileType fileType) {
userDefinedFileTypes.add(fileType);
fileTypes.add(fileType);
}
/**
* Sets the user-defined file types.
*
* @param newFileTypes A mapping of file type names to user-defined file
* types.
*/
synchronized void setUserDefinedFileTypes(List<FileType> newFileTypes) throws UserDefinedFileTypesException {
String filePath = getFileTypeDefinitionsFilePath(USER_DEFINED_TYPES_SERIALIZATION_FILE);
writeFileTypes(newFileTypes, filePath);
}
/**
* Gets the absolute path of a file type definitions file.
*
* @param fileName The name of the file.
*
* @return The absolute path to the file.
*/
private static String getFileTypeDefinitionsFilePath(String fileName) {
Path filePath = Paths.get(PlatformUtil.getUserConfigDirectory(), fileName);
return filePath.toAbsolutePath().toString();
}
/**
* Writes a set of file types to a file.
*
* @param fileTypes A collection of file types.
* @param filePath The path to the destination file.
*
* @throws ParserConfigurationException
* @throws IOException
* @throws FileNotFoundException
* @throws UnsupportedEncodingException
* @throws TransformerException
*/
private static void writeFileTypes(List<FileType> fileTypes, String filePath) throws UserDefinedFileTypesException {
try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(filePath))) {
UserDefinedFileTypesSettings settings = new UserDefinedFileTypesSettings(fileTypes);
out.writeObject(settings);
} catch (IOException ex) {
throw new UserDefinedFileTypesException(String.format("Failed to write settings to %s", filePath), ex);
}
}
/**
* Reads the file types
*
* @param filePath the file path where the file types are to be read
*
* @return the file types
*
* @throws ParserConfigurationException If the file cannot be read
*/
private static List<FileType> readFileTypesSerialized() throws UserDefinedFileTypesException {
File serializedDefs = new File(getFileTypeDefinitionsFilePath(USER_DEFINED_TYPES_SERIALIZATION_FILE));
try {
try (NbObjectInputStream in = new NbObjectInputStream(new FileInputStream(serializedDefs))) {
UserDefinedFileTypesSettings filesSetsSettings = (UserDefinedFileTypesSettings) in.readObject();
return filesSetsSettings.getUserDefinedFileTypes();
}
} catch (IOException | ClassNotFoundException ex) {
throw new UserDefinedFileTypesException("Couldn't read serialized settings.", ex);
}
}
/**
* Provides a mechanism for reading a set of file type definitions from an
* XML file.
*/
private static class XMLDefinitionsReader {
/**
* Reads a set of file type definitions from an XML file.
*
* @param filePath The path to the XML file.
*
* @return A collection of file types read from the XML file.
*/
private static List<FileType> readFileTypes(String filePath) throws IOException, SAXException, ParserConfigurationException {
List<FileType> fileTypes = new ArrayList<>();
/*
* RC: Commenting out the loadDocument overload that validates
* against the XSD is a temp fix for a failure to provide an upgrade
* path when the RelativeToStart attribute was added to the
* Signature element. The upgrade path can be supplied, but the plan
* is to replace the use of XML with object serialization for the
* settings, so it may not be worth the effort.
*/
// private static final String FILE_TYPE_DEFINITIONS_SCHEMA_FILE = "FileTypes.xsd"; //NON-NLS
// Document doc = XMLUtil.loadDocument(filePath, UserDefinedFileTypesManager.class, FILE_TYPE_DEFINITIONS_SCHEMA_FILE);
Document doc = XMLUtil.loadDocument(filePath);
if (doc != null) {
Element fileTypesElem = doc.getDocumentElement();
if (fileTypesElem != null && fileTypesElem.getNodeName().equals(FILE_TYPES_TAG_NAME)) {
NodeList fileTypeElems = fileTypesElem.getElementsByTagName(FILE_TYPE_TAG_NAME);
for (int i = 0; i < fileTypeElems.getLength(); ++i) {
Element fileTypeElem = (Element) fileTypeElems.item(i);
FileType fileType = XMLDefinitionsReader.parseFileType(fileTypeElem);
fileTypes.add(fileType);
}
}
}
return fileTypes;
}
/**
* Gets a file type definition from a file type XML element.
*
* @param fileTypeElem The XML element.
*
* @return A file type object.
*
* @throws IllegalArgumentException
* @throws NumberFormatException
*/
private static FileType parseFileType(Element fileTypeElem) throws IllegalArgumentException, NumberFormatException {
String mimeType = XMLDefinitionsReader.parseMimeType(fileTypeElem);
Signature signature = XMLDefinitionsReader.parseSignature(fileTypeElem);
// File type definitions in the XML file were written prior to the
// implementation of multiple signatures per type.
List<Signature> sigList = new ArrayList<>();
sigList.add(signature);
return new FileType(mimeType, sigList);
}
/**
* Gets the MIME type from a file type XML element.
*
* @param fileTypeElem The element
*
* @return A MIME type string.
*/
private static String parseMimeType(Element fileTypeElem) {
return getChildElementTextContent(fileTypeElem, MIME_TYPE_TAG_NAME);
}
/**
* Gets the signature from a file type XML element.
*
* @param fileTypeElem The XML element.
*
* @return The signature.
*/
private static Signature parseSignature(Element fileTypeElem) throws IllegalArgumentException, NumberFormatException {
NodeList signatureElems = fileTypeElem.getElementsByTagName(SIGNATURE_TAG_NAME);
Element signatureElem = (Element) signatureElems.item(0);
String sigTypeAttribute = signatureElem.getAttribute(SIGNATURE_TYPE_ATTRIBUTE);
Signature.Type signatureType = Signature.Type.valueOf(sigTypeAttribute);
String sigBytesString = getChildElementTextContent(signatureElem, BYTES_TAG_NAME);
byte[] signatureBytes = DatatypeConverter.parseHexBinary(sigBytesString);
Element offsetElem = (Element) signatureElem.getElementsByTagName(OFFSET_TAG_NAME).item(0);
String offsetString = offsetElem.getTextContent();
long offset = DatatypeConverter.parseLong(offsetString);
boolean isRelativeToStart;
String relativeString = offsetElem.getAttribute(RELATIVE_ATTRIBUTE);
if (null == relativeString || relativeString.equals("")) {
isRelativeToStart = true;
} else {
isRelativeToStart = DatatypeConverter.parseBoolean(relativeString);
}
return new Signature(signatureBytes, offset, signatureType, isRelativeToStart);
}
/**
* Gets the interesting files set name from a file type XML element.
*
* @param fileTypeElem The XML element.
*
* @return The files set name, possibly empty.
*/
private static String parseInterestingFilesSet(Element fileTypeElem) {
String filesSetName = "";
NodeList filesSetElems = fileTypeElem.getElementsByTagName(INTERESTING_FILES_SET_TAG_NAME);
if (filesSetElems.getLength() > 0) {
Element filesSetElem = (Element) filesSetElems.item(0);
filesSetName = filesSetElem.getTextContent();
}
return filesSetName;
}
/**
* Gets the alert attribute from a file type XML element.
*
* @param fileTypeElem The XML element.
*
* @return True or false;
*/
private static boolean parseAlert(Element fileTypeElem) {
String alertAttribute = fileTypeElem.getAttribute(ALERT_ATTRIBUTE);
return Boolean.parseBoolean(alertAttribute);
}
/**
* Gets the text content of a single child element.
*
* @param elem The parent element.
* @param tagName The tag name of the child element.
*
* @return The text content or null if the tag doesn't exist.
*/
private static String getChildElementTextContent(Element elem, String tagName) {
NodeList childElems = elem.getElementsByTagName(tagName);
Node childNode = childElems.item(0);
if (childNode == null) {
return null;
}
Element childElem = (Element) childNode;
return childElem.getTextContent();
}
/**
* Private constructor suppresses creation of instanmces of this utility
* class.
*/
private XMLDefinitionsReader() {
}
}
/**
* Logs an exception, bundles the exception with a simple message in a
* uniform exception type, and throws the wrapper exception.
*
* @param ex The exception to wrap.
* @param messageKey A key into the bundle file that maps to the desired
* message.
*
* @throws
* org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException
*/
private void throwUserDefinedFileTypesException(Exception ex, String messageKey) throws UserDefinedFileTypesException {
String message = NbBundle.getMessage(UserDefinedFileTypesManager.class, messageKey);
logger.log(Level.SEVERE, message, ex);
throw new UserDefinedFileTypesException(message, ex);
}
/**
* Used to translate more implementation-details-specific exceptions (which
* are logged by this class) into more generic exceptions for propagation to
* clients of the user-defined file types manager.
*/
static class UserDefinedFileTypesException extends Exception {
private static final long serialVersionUID = 1L;
UserDefinedFileTypesException(String message) {
super(message);
}
UserDefinedFileTypesException(String message, Throwable throwable) {
super(message, throwable);
}
}
}

View File

@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.timeline;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import javafx.collections.FXCollections;
@ -34,8 +33,6 @@ import javafx.scene.control.ListView;
import javafx.scene.image.Image;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.controlsfx.dialog.ProgressDialog;
import org.controlsfx.tools.Borders;
import org.openide.util.NbBundle;
@ -50,13 +47,13 @@ class PromptDialogManager {
private static final Logger LOGGER = Logger.getLogger(PromptDialogManager.class.getName());
@NbBundle.Messages("PrompDialogManager.buttonType.showTimeline=Show Timeline")
private static final ButtonType SHOW_TIMELINE = new ButtonType(Bundle.PrompDialogManager_buttonType_showTimeline(), ButtonBar.ButtonData.OK_DONE);
@NbBundle.Messages("PrompDialogManager.buttonType.showTimeline=Continue")
private static final ButtonType CONTINUE = new ButtonType(Bundle.PrompDialogManager_buttonType_showTimeline(), ButtonBar.ButtonData.OK_DONE);
@NbBundle.Messages("PrompDialogManager.buttonType.continueNoUpdate=Continue Without Updating")
private static final ButtonType CONTINUE_NO_UPDATE = new ButtonType(Bundle.PrompDialogManager_buttonType_continueNoUpdate(), ButtonBar.ButtonData.CANCEL_CLOSE);
@NbBundle.Messages("PrompDialogManager.buttonType.update=Update")
@NbBundle.Messages("PrompDialogManager.buttonType.update=Update DB")
private static final ButtonType UPDATE = new ButtonType(Bundle.PrompDialogManager_buttonType_update(), ButtonBar.ButtonData.OK_DONE);
/**
@ -89,7 +86,7 @@ class PromptDialogManager {
}
/**
* Bring the currently managed dialog (if there is one) to the front
* Bring the currently managed dialog (if there is one) to the front.
*
* @return True if a dialog was brought to the front, or false of there is
* no currently managed open dialog
@ -151,60 +148,45 @@ class PromptDialogManager {
}
/**
* Prompt the user that ingest is running and the db may not end up
* Prompt the user that ingest is running and the DB may not end up
* complete.
*
* @return True if they want to continue anyways
* @return True if they want to continue anyways.
*/
@NbBundle.Messages({
"PromptDialogManager.confirmDuringIngest.headerText=You are trying to show a timeline before ingest has been completed.\nThe timeline may be incomplete.",
"PromptDialogManager.confirmDuringIngest.headerText=You are trying to update the Timeline DB before ingest has been completed. The Timeline DB may be incomplete.",
"PromptDialogManager.confirmDuringIngest.contentText=Do you want to continue?"})
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
boolean confirmDuringIngest() {
currentDialog = new Alert(Alert.AlertType.CONFIRMATION, Bundle.PromptDialogManager_confirmDuringIngest_contentText(), SHOW_TIMELINE, ButtonType.CANCEL);
currentDialog = new Alert(Alert.AlertType.CONFIRMATION, Bundle.PromptDialogManager_confirmDuringIngest_contentText(), CONTINUE, ButtonType.CANCEL);
currentDialog.initModality(Modality.APPLICATION_MODAL);
currentDialog.setTitle(Bundle.Timeline_dialogs_title());
setDialogIcons(currentDialog);
currentDialog.setHeaderText(Bundle.PromptDialogManager_confirmDuringIngest_headerText());
//show dialog and map all results except "show timeline" to false.
return currentDialog.showAndWait().map(SHOW_TIMELINE::equals).orElse(false);
//show dialog and map all results except "continue" to false.
return currentDialog.showAndWait().map(CONTINUE::equals).orElse(false);
}
/**
* Prompt the user to confirm rebuilding the database for the given list of
* reasons, adding that "ingest has finished" for the datasource with the
* given name, if not blank, as a reason and as extra header text.
* reasons.
*
* @param finishedDataSourceName The name of the datasource that has
* finished be analyzed. Will be ignored if it
* is null or empty.
* @param rebuildReasons A List of reasons why the database is out
* of date.
* @param rebuildReasons A List of reasons why the database is out of date.
*
* @return True if the user a confirms rebuilding the database.
*/
@NbBundle.Messages({
"PromptDialogManager.rebuildPrompt.headerText=The Timeline database is incomplete and/or out of date."
+ " Some events may be missing or inaccurate and some features may be unavailable.",
"# {0} - data source name",
"PromptDialogManager.rebuildPrompt.ingestDone=Ingest has finished for {0}.",
"PromptDialogManager.rebuildPrompt.headerText=The Timeline DB is incomplete and/or out of date. Some events may be missing or inaccurate and some features may be unavailable.",
"PromptDialogManager.rebuildPrompt.details=Details"})
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
boolean confirmRebuild(@Nullable String finishedDataSourceName, List<String> rebuildReasons) {
boolean confirmRebuild(List<String> rebuildReasons) {
currentDialog = new Alert(Alert.AlertType.CONFIRMATION, Bundle.TimeLinecontroller_updateNowQuestion(), UPDATE, CONTINUE_NO_UPDATE);
currentDialog.initModality(Modality.APPLICATION_MODAL);
currentDialog.setTitle(Bundle.Timeline_dialogs_title());
setDialogIcons(currentDialog);
//configure header text depending on presence of finishedDataSourceName
String headerText = Bundle.PromptDialogManager_rebuildPrompt_headerText();
if (StringUtils.isNotBlank(finishedDataSourceName)) {
String datasourceMessage = Bundle.PromptDialogManager_rebuildPrompt_ingestDone(finishedDataSourceName);
rebuildReasons.add(0, datasourceMessage);
headerText = datasourceMessage + "\n\n" + headerText;
}
currentDialog.setHeaderText(headerText);
currentDialog.setHeaderText(Bundle.PromptDialogManager_rebuildPrompt_headerText());
//set up listview of reasons to rebuild
ListView<String> listView = new ListView<>(FXCollections.observableArrayList(rebuildReasons));
@ -224,17 +206,4 @@ class PromptDialogManager {
//show dialog and map all results except "update" to false.
return currentDialog.showAndWait().map(UPDATE::equals).orElse(false);
}
/**
* Prompt the user to confirm rebuilding the database for the given list of
* reasons.
*
* @param rebuildReasons S List of reasons why the database is out of date.
*
* @return True if the user a confirms rebuilding the database.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
boolean confirmRebuild(ArrayList<String> rebuildReasons) {
return confirmRebuild(null, rebuildReasons);
}
}

View File

@ -51,7 +51,6 @@ import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import static javafx.concurrent.Worker.State.FAILED;
import static javafx.concurrent.Worker.State.SUCCEEDED;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable;
import javax.swing.SwingUtilities;
@ -74,10 +73,9 @@ import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.autopsy.ingest.IngestManager;
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestJobEvent.CANCELLED;
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisEvent;
import static org.sleuthkit.autopsy.timeline.Bundle.*;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
@ -90,7 +88,6 @@ import org.sleuthkit.autopsy.timeline.zooming.DescriptionLoD;
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
import org.sleuthkit.autopsy.timeline.zooming.TimeUnits;
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
import org.sleuthkit.datamodel.Content;
/**
* Controller in the MVC design along with FilteredEventsModel TimeLineView.
@ -142,7 +139,7 @@ public class TimeLineController {
private final ReadOnlyStringWrapper taskTitle = new ReadOnlyStringWrapper();
private final ReadOnlyStringWrapper status = new ReadOnlyStringWrapper();
private final ReadOnlyStringWrapper statusMessage = new ReadOnlyStringWrapper();
/**
* Status is a string that will be displayed in the status bar as a kind of
@ -150,12 +147,12 @@ public class TimeLineController {
*
* @return The status property
*/
public ReadOnlyStringProperty getStatusProperty() {
return status.getReadOnlyProperty();
public ReadOnlyStringProperty statusMessageProperty() {
return statusMessage.getReadOnlyProperty();
}
public void setStatus(String string) {
status.set(string);
public void setStatusMessage(String string) {
statusMessage.set(string);
}
private final Case autoCase;
private final PerCaseTimelineProperties perCaseTimelineProperties;
@ -202,10 +199,10 @@ public class TimeLineController {
private final PropertyChangeListener ingestModuleListener = new AutopsyIngestModuleListener();
@GuardedBy("this")
private final ReadOnlyObjectWrapper<VisualizationMode> viewMode = new ReadOnlyObjectWrapper<>(VisualizationMode.COUNTS);
private final ReadOnlyObjectWrapper<VisualizationMode> visualizationMode = new ReadOnlyObjectWrapper<>(VisualizationMode.COUNTS);
synchronized public ReadOnlyObjectProperty<VisualizationMode> visualizationModeProperty() {
return viewMode.getReadOnlyProperty();
return visualizationMode.getReadOnlyProperty();
}
@GuardedBy("filteredEvents")
@ -264,6 +261,7 @@ public class TimeLineController {
@NbBundle.Messages({
"TimeLineController.setEventsDBStale.errMsgStale=Failed to mark the timeline db as stale. Some results may be out of date or missing.",
"TimeLineController.setEventsDBStale.errMsgNotStale=Failed to mark the timeline db as not stale. Some results may be out of date or missing."})
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void setEventsDBStale(final Boolean stale) {
eventsDBStale.set(stale);
try {
@ -389,12 +387,13 @@ public class TimeLineController {
perCaseTimelineProperties.setIngestRunning(ingestRunning);
} catch (IOException ex) {
MessageNotifyUtil.Notify.error(Bundle.Timeline_dialogs_title(),
ingestRunning ? TimeLineController_setIngestRunning_errMsgRunning()
: TimeLinecontroller_setIngestRunning_errMsgNotRunning());
ingestRunning ? Bundle.TimeLineController_setIngestRunning_errMsgRunning()
: Bundle.TimeLinecontroller_setIngestRunning_errMsgNotRunning());
LOGGER.log(Level.SEVERE, "Error marking the ingest state while the timeline db was populated.", ex); //NON-NLS
}
if (markDBNotStale) {
setEventsDBStale(false);
filteredEvents.postDBUpdated();
}
SwingUtilities.invokeLater(this::showWindow);
break;
@ -454,6 +453,7 @@ public class TimeLineController {
mainFrame.close();
mainFrame = null;
}
OpenTimelineAction.invalidateController();
}
/**
@ -470,21 +470,17 @@ public class TimeLineController {
listeningToAutopsy = true;
}
Platform.runLater(() -> promptForRebuild(null));
Platform.runLater(this::promptForRebuild);
}
/**
* Prompt the user to confirm rebuilding the db because ingest has finished
* on the datasource with the given name. Checks if a database rebuild is
* necessary for any other reasons and includes those in the prompt. If the
* user confirms, rebuilds the database. Shows the timeline window when the
* rebuild is done, or immediately if the rebuild is not confirmed.
*
* @param dataSourceName The name of the datasource that ingest has finished
* processing. Will be ignored if it is null or empty.
* Prompt the user to confirm rebuilding the db. Checks if a database
* rebuild is necessary and includes the reasons in the prompt. If the user
* confirms, rebuilds the database. Shows the timeline window when the
* rebuild is done, or immediately if the rebuild is not confirmed. F
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void promptForRebuild(@Nullable String dataSourceName) {
private void promptForRebuild() {
//if there is an existing prompt or progressdialog, just show that
if (promptDialogManager.bringCurrentDialogToFront()) {
@ -500,7 +496,7 @@ public class TimeLineController {
//if necessary prompt user with reasons to rebuild
List<String> rebuildReasons = getRebuildReasons();
if (false == rebuildReasons.isEmpty()) {
if (promptDialogManager.confirmRebuild(dataSourceName, rebuildReasons)) {
if (promptDialogManager.confirmRebuild(rebuildReasons)) {
rebuildRepo();
return;
}
@ -560,7 +556,7 @@ public class TimeLineController {
* Request a time range the same length as the given period and centered
* around the middle of the currently viewed time range.
*
* @param period The period of time to shw around the current center of the
* @param period The period of time to show around the current center of the
* view.
*/
synchronized public void pushPeriod(ReadablePeriod period) {
@ -586,9 +582,14 @@ public class TimeLineController {
pushTimeRange(new Interval(start, end));
}
synchronized public void setViewMode(VisualizationMode visualizationMode) {
if (viewMode.get() != visualizationMode) {
viewMode.set(visualizationMode);
/**
* 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);
}
}
@ -812,47 +813,6 @@ public class TimeLineController {
TimeLineController.timeZone.set(timeZone);
}
Interval getSpanningInterval(Collection<Long> eventIDs) {
return filteredEvents.getSpanningInterval(eventIDs);
}
/**
* Is the timeline window open?
*
* @return True if the timeline is open.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private boolean isWindowOpen() {
return mainFrame != null && mainFrame.isOpened() && mainFrame.isVisible();
}
/**
* Rebuild the db ONLY IF THE TIMELINE WINDOW IS OPEN. The user will be
* prompted with reasons why the database needs to be rebuilt and can still
* cancel the rebuild. The prompt will include that ingest has finished for
* the given datasource name, if not blank.
*
* @param dataSourceName The name of the datasource that has finished
* ingest. Will be ignored if it is null or empty.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
private void rebuildIfWindowOpen(@Nullable String dataSourceName) {
if (isWindowOpen()) {
Platform.runLater(() -> this.promptForRebuild(dataSourceName));
}
}
/**
* Rebuild the db ONLY IF THE TIMELINE WINDOW IS OPEN. The user will be
* prompted with reasons why the database needs to be rebuilt and can still
* cancel the rebuild.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void rebuildIfWindowOpen() {
rebuildIfWindowOpen(null);
}
/**
* Listener for IngestManager.IngestModuleEvents.
*/
@ -870,16 +830,14 @@ public class TimeLineController {
try {
Case.getCurrentCase();
} catch (IllegalStateException notUsed) {
/**
* Case is closed, do nothing.
*/
// Case is closed, do nothing.
return;
}
switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
case CONTENT_CHANGED:
case DATA_ADDED:
//since black board artifacts or new derived content have been added, the db is stale.
//since black board artifacts or new derived content have been added, the DB is stale.
Platform.runLater(() -> setEventsDBStale(true));
break;
case FILE_DONE:
@ -902,9 +860,9 @@ public class TimeLineController {
public void propertyChange(PropertyChangeEvent evt) {
switch (IngestManager.IngestJobEvent.valueOf(evt.getPropertyName())) {
case DATA_SOURCE_ANALYSIS_COMPLETED:
// include data source name in rebuild prompt on ingest completed
final Content dataSource = ((DataSourceAnalysisEvent) evt).getDataSource();
SwingUtilities.invokeLater(() -> rebuildIfWindowOpen(dataSource.getName()));
//mark db stale, and prompt to rebuild
Platform.runLater(() -> setEventsDBStale(true));
filteredEvents.postAutopsyEventLocally((AutopsyEvent) evt);
break;
case DATA_SOURCE_ANALYSIS_STARTED:
case CANCELLED:
@ -939,10 +897,10 @@ public class TimeLineController {
case DATA_SOURCE_ADDED:
//mark db stale, and prompt to rebuild
Platform.runLater(() -> setEventsDBStale(true));
filteredEvents.postAutopsyEventLocally((AutopsyEvent) evt);
break;
case CURRENT_CASE:
//close timeline on case changes.
OpenTimelineAction.invalidateController();
SwingUtilities.invokeLater(TimeLineController.this::shutDownTimeLine);
break;
}

View File

@ -25,20 +25,26 @@ import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
/**
* An action that rebuilds the events database to include any new results from
* An action that rebuilds the timeline database to include any new results from
* ingest.
*/
public class RebuildDataBase extends Action {
public class UpdateDB extends Action {
private static final Image DB_REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/database_refresh.png");
/**
* Constructor
*
* @param controller The TimeLineController for this action.
*/
@NbBundle.Messages({
"RebuildDataBase.text=Update DB",
"RebuildDataBase.longText=Update the DB to include new events."})
public RebuildDataBase(TimeLineController controller) {
public UpdateDB(TimeLineController controller) {
super(Bundle.RebuildDataBase_text());
setLongText(Bundle.RebuildDataBase_longText());
setGraphic(new ImageView(DB_REFRESH));
setEventHandler(actionEvent -> controller.rebuildRepo());
disabledProperty().bind(controller.eventsDBStaleProperty().not());
}
}

View File

@ -40,9 +40,11 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent.DeletedContentTagInfo;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
import org.sleuthkit.autopsy.timeline.db.EventsRepository;
import org.sleuthkit.autopsy.timeline.events.DBUpdatedEvent;
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
import org.sleuthkit.autopsy.timeline.events.TagsAddedEvent;
import org.sleuthkit.autopsy.timeline.events.TagsDeletedEvent;
@ -416,6 +418,15 @@ public final class FilteredEventsModel {
return false;
}
/**
* Post a TagsAddedEvent to all registered subscribers, if the given set of
* updated event IDs is not empty.
*
* @param updatedEventIDs The set of event ids to be included in the
* TagsAddedEvent.
*
* @return True if an event was posted.
*/
private boolean postTagsAdded(Set<Long> updatedEventIDs) {
boolean tagsUpdated = !updatedEventIDs.isEmpty();
if (tagsUpdated) {
@ -424,6 +435,15 @@ public final class FilteredEventsModel {
return tagsUpdated;
}
/**
* Post a TagsDeletedEvent to all registered subscribers, if the given set
* of updated event IDs is not empty.
*
* @param updatedEventIDs The set of event ids to be included in the
* TagsDeletedEvent.
*
* @return True if an event was posted.
*/
private boolean postTagsDeleted(Set<Long> updatedEventIDs) {
boolean tagsUpdated = !updatedEventIDs.isEmpty();
if (tagsUpdated) {
@ -432,16 +452,45 @@ public final class FilteredEventsModel {
return tagsUpdated;
}
/**
* Register the given object to receive events.
*
* @param o The object to register. Must implement public methods annotated
* with Subscribe.
*/
synchronized public void registerForEvents(Object o) {
eventbus.register(o);
}
/**
* Un-register the given object, so it no longer receives events.
*
* @param o The object to un-register.
*/
synchronized public void unRegisterForEvents(Object o) {
eventbus.unregister(0);
}
public void refresh() {
/**
* Post a DBUpdatedEvent to all registered subscribers.
*/
public void postDBUpdated() {
eventbus.post(new DBUpdatedEvent());
}
/**
* Post a RefreshRequestedEvent to all registered subscribers.
*/
public void postRefreshRequest() {
eventbus.post(new RefreshRequestedEvent());
}
/**
* (Re)Post an AutopsyEvent received from another event distribution system
* locally to all registered subscribers.
*/
public void postAutopsyEventLocally(AutopsyEvent event) {
eventbus.post(event);
}
}

View File

@ -0,0 +1,29 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.timeline.events;
/**
* A "local" event published by filteredEventsModel to indicate that DB has been
* updated.
*
* This event is not intended for use out side of the Timeline module.
*/
public class DBUpdatedEvent {
}

View File

@ -22,8 +22,8 @@ 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
* of the parameters ( to include more up to date tag data for example.)
* <p>
* This event is not intended for use out side of the timeline module.
*
* This event is not intended for use out side of the Timeline module.
*/
public class RefreshRequestedEvent {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.timeline.ui;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -69,7 +68,6 @@ 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.
@ -95,18 +93,29 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
/**
* Get the tool tip to use for this visualization when no more specific
* tooltip is needed.
* Tooltip is needed.
*
* @return The default tooltip.
* @return The default Tooltip.
*/
public static 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.
*/
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);
/*
* access to chart data via series
/**
* Access to chart data via series
*/
protected final ObservableList<XYChart.Series<X, Y>> dataSeries = FXCollections.<XYChart.Series<X, Y>>observableArrayList();
protected final Map<EventType, XYChart.Series<X, Y>> eventTypeToSeriesMap = new HashMap<>();
@ -128,7 +137,41 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
final private ObservableList<NodeType> selectedNodes = FXCollections.observableArrayList();
private InvalidationListener updateListener = any -> update();
/**
* Listener that is attached to various properties that should trigger a vis
* update when they change.
*/
private InvalidationListener updateListener = any -> refresh();
/**
* 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();
}
/**
* 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>
*
* @return A ReadOnlyBooleanProperty that holds the out-of-date state for
* this visualization.
*/
public ReadOnlyBooleanProperty outOfDateProperty() {
return outOfDate.getReadOnlyProperty();
}
public Pane getSpecificLabelPane() {
return specificLabelPane;
@ -335,13 +378,13 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
abstract protected void clearChartData();
/**
* Update this visualization based on current state of zoom / filters.
* Refresh this visualization based on current state of zoom / filters.
* Primarily this invokes the background VisualizationUpdateTask returned by
* getUpdateTask(), which derived classes must implement.
*
* TODO: replace this logic with a javafx Service ? -jm
*/
protected final synchronized void update() {
protected final synchronized void refresh() {
if (updateTask != null) {
updateTask.cancel(true);
updateTask = null;
@ -443,21 +486,10 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
TimeLineController.getTimeZone().addListener(updateListener);
//show tooltip text in status bar
hoverProperty().addListener(hoverProp -> controller.setStatus(isHover() ? DEFAULT_TOOLTIP.getText() : ""));
hoverProperty().addListener(hoverProp -> controller.setStatusMessage(isHover() ? DEFAULT_TOOLTIP.getText() : ""));
}
/**
* Handle a RefreshRequestedEvent from the events model by updating the
* visualization.
*
* @param event The RefreshRequestedEvent to handle.
*/
@Subscribe
public void handleRefreshRequested(RefreshRequestedEvent event) {
update();
}
/**
* Iterate through the list of tick-marks building a two level structure of
* replacement tick mark labels. (Visually) upper level has most
@ -646,13 +678,13 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
}
/**
* Base class for Tasks that update a visualization when the view settings
* Base class for Tasks that refresh a visualization when the view settings
* change.
*
* @param <AxisValuesType> The type of a single object that can represent
* the range of data displayed along the X-Axis.
*/
abstract protected class VisualizationUpdateTask<AxisValuesType> extends LoggedTask<Boolean> {
abstract protected class VisualizationRefreshTask<AxisValuesType> extends LoggedTask<Boolean> {
private final Node center;
@ -660,10 +692,10 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
* Constructor
*
* @param taskName The name of this task.
* @param logStateChanges Whether or not task state chanes should be
* @param logStateChanges Whether or not task state changes should be
* logged.
*/
protected VisualizationUpdateTask(String taskName, boolean logStateChanges) {
protected VisualizationRefreshTask(String taskName, boolean logStateChanges) {
super(taskName, logStateChanges);
this.center = getCenter();
}
@ -702,6 +734,7 @@ public abstract class AbstractVisualizationPane<X, Y, NodeType extends Node, Cha
@Override
protected void succeeded() {
super.succeeded();
outOfDate.set(false);
cleanup();
}

View File

@ -1,52 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.HBox?>
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<items>
<HBox fx:id="refreshBox" alignment="CENTER" spacing="5.0">
<children>
<Label fx:id="refreshLabel">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/information-frame.png" />
</image>
</ImageView>
</graphic>
</Label>
<Button fx:id="updateDBButton" mnemonicParsing="false" text="Update DB">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/database_refresh.png" />
</image>
</ImageView>
</graphic>
</Button>
</children>
</HBox>
<Separator orientation="VERTICAL" />
<Label fx:id="statusLabel" layoutX="10.0" layoutY="11.0">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/information-white.png" />
</image>
</ImageView>
</graphic>
</Label>
<Region fx:id="spacer" maxWidth="1.7976931348623157E308" />
<Separator orientation="VERTICAL" />
<Label fx:id="statusLabel" layoutX="10.0" layoutY="11.0" HBox.hgrow="ALWAYS">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/information-white.png" />
</image>
</ImageView>
</graphic>
</Label>
<Separator halignment="RIGHT" orientation="VERTICAL" />
<Label fx:id="taskLabel" contentDisplay="RIGHT">
<graphic>
<StackPane>

View File

@ -19,41 +19,26 @@
package org.sleuthkit.autopsy.timeline.ui;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.actions.RebuildDataBase;
/**
* Simple status bar that shows a possible message determined by
* TimeLineController.eventsDBStaleProperty() and the warning/button to update
* the db if it is stale
* TimeLineController.statusMessageProperty() and progress of background tasks.
*/
public class StatusBar extends ToolBar {
private final TimeLineController controller;
@FXML
private Label refreshLabel;
@FXML
private HBox refreshBox;
@FXML
private Button updateDBButton;
@FXML
private Label statusLabel;
@FXML
private ProgressBar progressBar;
@FXML
private Region spacer;
@FXML
private Label taskLabel;
@FXML
private Label messageLabel;
@ -64,28 +49,19 @@ public class StatusBar extends ToolBar {
}
@FXML
@NbBundle.Messages({"StatusBar.refreshLabel.text=The timeline DB may be out of date."})
void initialize() {
assert refreshLabel != null : "fx:id=\"refreshLabel\" was not injected: check your FXML file 'StatusBar.fxml'."; // NON-NLS
assert progressBar != null : "fx:id=\"progressBar\" was not injected: check your FXML file 'StatusBar.fxml'."; // NON-NLS
assert spacer != null : "fx:id=\"spacer\" was not injected: check your FXML file 'StatusBar.fxml'."; // NON-NLS
assert taskLabel != null : "fx:id=\"taskLabel\" was not injected: check your FXML file 'StatusBar.fxml'."; // NON-NLS
assert messageLabel != null : "fx:id=\"messageLabel\" was not injected: check your FXML file 'StatusBar.fxml'."; // NON-NLS
HBox.setHgrow(spacer, Priority.ALWAYS);
refreshLabel.setText(Bundle.StatusBar_refreshLabel_text());
refreshBox.visibleProperty().bind(this.controller.eventsDBStaleProperty());
refreshBox.managedProperty().bind(this.controller.eventsDBStaleProperty());
taskLabel.setVisible(false);
taskLabel.textProperty().bind(this.controller.taskTitleProperty());
messageLabel.textProperty().bind(this.controller.taskMessageProperty());
progressBar.progressProperty().bind(this.controller.taskProgressProperty());
taskLabel.visibleProperty().bind(this.controller.getTasks().emptyProperty().not());
statusLabel.textProperty().bind(this.controller.getStatusProperty());
statusLabel.visibleProperty().bind(statusLabel.textProperty().isNotEmpty());
messageLabel.textProperty().bind(this.controller.taskMessageProperty());
progressBar.progressProperty().bind(this.controller.taskProgressProperty());
ActionUtils.configureButton(new RebuildDataBase(controller), updateDBButton);
statusLabel.textProperty().bind(this.controller.statusMessageProperty());
statusLabel.visibleProperty().bind(statusLabel.textProperty().isNotEmpty());
}
}

View File

@ -19,99 +19,100 @@
<fx:root prefHeight="-1.0" prefWidth="-1.0" type="javafx.scene.layout.BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<top>
<HBox BorderPane.alignment="CENTER">
<children>
<ToolBar fx:id="toolBar" HBox.hgrow="ALWAYS">
<items>
<HBox alignment="CENTER" BorderPane.alignment="CENTER">
<children>
<Label fx:id="visualizationModeLabel" text="Visualisation Mode:">
<HBox.margin>
<Insets right="5.0" />
</HBox.margin>
<font>
<Font name="System Bold" size="14.0" />
</font>
</Label>
<ToolBar fx:id="toolBar" HBox.hgrow="ALWAYS">
<items>
<HBox alignment="CENTER_LEFT" BorderPane.alignment="CENTER" HBox.hgrow="NEVER">
<children>
<Label fx:id="visualizationModeLabel" text="Visualisation Mode:" textAlignment="CENTER" wrapText="true" HBox.hgrow="NEVER">
<HBox.margin>
<Insets right="5.0" />
</HBox.margin>
<font>
<Font name="System Bold" size="14.0" />
</font>
</Label>
<SegmentedButton maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity">
<buttons>
<ToggleButton fx:id="countsToggle" alignment="TOP_LEFT" mnemonicParsing="false" selected="true">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/chart_bar.png" />
</image>
</ImageView>
</graphic>
<font>
<Font name="System Bold" size="16.0" />
</font>
</ToggleButton>
<ToggleButton fx:id="detailsToggle" alignment="CENTER_RIGHT" layoutX="74.0" mnemonicParsing="false" selected="false">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true" rotate="0.0" smooth="true" style="-fx-background-color:white;" x="2.0" y="1.0">
<image>
<Image url="@../images/20140521121247760_easyicon_net_32_colorized.png" />
</image>
</ImageView>
</graphic>
<font>
<Font name="System Bold" size="16.0" />
</font>
</ToggleButton>
<ToggleButton fx:id="listToggle" alignment="CENTER_RIGHT" layoutX="74.0" mnemonicParsing="false" selected="false">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true" rotate="0.0" smooth="true" style="-fx-background-color:white;" x="2.0" y="1.0">
<image>
<Image url="@../images/table.png" />
</image>
</ImageView>
</graphic>
<font>
<Font name="System Bold" size="16.0" />
</font>
</ToggleButton>
</buttons>
</SegmentedButton>
</children>
<padding>
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
</padding>
<BorderPane.margin>
<Insets left="10.0" />
</BorderPane.margin>
</HBox>
<Separator orientation="VERTICAL" />
</items>
</ToolBar>
<ToolBar maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308">
<items>
<Separator orientation="VERTICAL" />
<Button fx:id="snapShotButton" mnemonicParsing="false">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/image.png" />
</image>
</ImageView>
</graphic>
</Button>
<Separator halignment="LEFT" maxWidth="1.7976931348623157E308" orientation="VERTICAL" />
<Button fx:id="refreshButton" alignment="CENTER_RIGHT" mnemonicParsing="false" text="Refresh">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/arrow-circle-double-135.png" />
</image>
</ImageView>
</graphic>
</Button>
</items>
</ToolBar>
</children>
</HBox>
<SegmentedButton fx:id="modeSegButton" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" HBox.hgrow="NEVER">
<buttons>
<ToggleButton fx:id="countsToggle" alignment="TOP_LEFT" mnemonicParsing="false" selected="true">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/chart_bar.png" />
</image>
</ImageView>
</graphic>
<font>
<Font name="System Bold" size="16.0" />
</font>
</ToggleButton>
<ToggleButton fx:id="detailsToggle" alignment="CENTER_RIGHT" layoutX="74.0" mnemonicParsing="false" selected="false">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true" rotate="0.0" smooth="true" style="-fx-background-color:white;" x="2.0" y="1.0">
<image>
<Image url="@../images/20140521121247760_easyicon_net_32_colorized.png" />
</image>
</ImageView>
</graphic>
<font>
<Font name="System Bold" size="16.0" />
</font>
</ToggleButton>
<ToggleButton fx:id="listToggle" alignment="CENTER_RIGHT" layoutX="74.0" mnemonicParsing="false" selected="false">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true" rotate="0.0" smooth="true" style="-fx-background-color:white;" x="2.0" y="1.0">
<image>
<Image url="@../images/table.png" />
</image>
</ImageView>
</graphic>
<font>
<Font name="System Bold" size="16.0" />
</font>
</ToggleButton>
</buttons>
</SegmentedButton>
</children>
<padding>
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
</padding>
<BorderPane.margin>
<Insets left="10.0" />
</BorderPane.margin>
</HBox>
<Separator halignment="LEFT" maxWidth="1.7976931348623157E308" orientation="VERTICAL" />
<Separator halignment="LEFT" maxWidth="1.7976931348623157E308" orientation="VERTICAL" HBox.hgrow="ALWAYS" />
<Button fx:id="snapShotButton" mnemonicParsing="false" text="Snapshot Report">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/image.png" />
</image>
</ImageView>
</graphic>
</Button>
<Separator maxWidth="1.7976931348623157E308" orientation="VERTICAL" />
<Button fx:id="refreshButton" alignment="CENTER_RIGHT" mnemonicParsing="false" text="Refresh Vis.">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/arrow-circle-double-135.png" />
</image>
</ImageView>
</graphic>
</Button>
<Button fx:id="updateDBButton" mnemonicParsing="false" text="Update DB">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/database_refresh.png" />
</image>
</ImageView>
</graphic>
</Button>
</items>
</ToolBar>
</top>
<bottom>
<VBox maxHeight="-Infinity">

View File

@ -29,7 +29,6 @@ import java.util.function.Supplier;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
@ -68,18 +67,22 @@ import org.controlsfx.control.action.ActionUtils;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshotAsReport;
import org.sleuthkit.autopsy.timeline.actions.UpdateDB;
import org.sleuthkit.autopsy.timeline.actions.ZoomIn;
import org.sleuthkit.autopsy.timeline.actions.ZoomOut;
import org.sleuthkit.autopsy.timeline.actions.ZoomToEvents;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.DBUpdatedEvent;
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
@ -100,6 +103,7 @@ final public class VisualizationPanel extends BorderPane {
private static final Logger LOGGER = Logger.getLogger(VisualizationPanel.class.getName());
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
private static final Image WARNING = new Image("org/sleuthkit/autopsy/timeline/images/warning_triangle.png", 16, 16, true, true); // NON-NLS
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
private static final Background GRAY_BACKGROUND = new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY));
@ -167,19 +171,14 @@ final public class VisualizationPanel extends BorderPane {
private Button snapShotButton;
@FXML
private Button refreshButton;
@FXML
private Button updateDBButton;
/*
* Wraps contained visualization so that we can show notifications over it.
*/
private final NotificationPane notificationPane = new NotificationPane();
/*
* Boolean property that holds true if the visualziation may not represent
* the current state of the DB, because, for example, tags have been updated
* but the vis. was not refreshed.
*/
private final ReadOnlyBooleanWrapper needsRefresh = new ReadOnlyBooleanWrapper(false);
private final TimeLineController controller;
private final FilteredEventsModel filteredEvents;
@ -205,7 +204,7 @@ final public class VisualizationPanel extends BorderPane {
/**
* hides the notification pane on any event
*/
private final InvalidationListener zoomListener = any -> setNeedsRefresh(false);
private final InvalidationListener zoomListener = any -> handleRefreshRequested(null);
/**
* listen to change in end time picker and push to controller
@ -263,7 +262,8 @@ final public class VisualizationPanel extends BorderPane {
"VisualizationPanel.countsToggle.text=Counts",
"VisualizationPanel.detailsToggle.text=Details",
"VisualizationPanel.zoomMenuButton.text=Zoom in/out to",
"VisualizationPanel.tagsAddedOrDeleted=Tags have been created and/or deleted. The visualization may not be up to date."})
"VisualizationPanel.tagsAddedOrDeleted=Tags have been created and/or deleted. The visualization may not be up to date."
})
void initialize() {
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
@ -274,15 +274,7 @@ final public class VisualizationPanel extends BorderPane {
//configure notification pane
notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
notificationPane.getActions().setAll(new Refresh());
setCenter(notificationPane);
needsRefresh.addListener(observable -> {
if (needsRefresh.get()) {
notificationPane.show(Bundle.VisualizationPanel_tagsAddedOrDeleted(), new ImageView(INFORMATION));
} else {
notificationPane.hide();
}
});
//configure visualization mode toggle
visualizationModeLabel.setText(Bundle.VisualizationPanel_visualizationModeLabel_text());
@ -292,9 +284,9 @@ final public class VisualizationPanel extends BorderPane {
if (newValue == null) {
countsToggle.getToggleGroup().selectToggle(oldValue != null ? oldValue : countsToggle);
} else if (newValue == countsToggle && oldValue != null) {
controller.setViewMode(VisualizationMode.COUNTS);
controller.setVisualizationMode(VisualizationMode.COUNTS);
} else if (newValue == detailsToggle && oldValue != null) {
controller.setViewMode(VisualizationMode.DETAIL);
controller.setVisualizationMode(VisualizationMode.DETAIL);
} else if (newValue == listToggle && oldValue != null) {
controller.setViewMode(VisualizationMode.LIST);
}
@ -303,7 +295,7 @@ final public class VisualizationPanel extends BorderPane {
if (countsToggle.getToggleGroup() != null) {
countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
} else {
countsToggle.toggleGroupProperty().addListener((Observable observable) -> {
countsToggle.toggleGroupProperty().addListener((Observable toggleGroup) -> {
countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
});
}
@ -311,9 +303,8 @@ final public class VisualizationPanel extends BorderPane {
controller.visualizationModeProperty().addListener(visualizationMode -> syncVisualizationMode());
syncVisualizationMode();
//configure snapshor button / action
ActionUtils.configureButton(new SaveSnapshotAsReport(controller, notificationPane::getContent), snapShotButton);
ActionUtils.configureButton(new Refresh(), refreshButton);
ActionUtils.configureButton(new UpdateDB(controller), updateDBButton);
/////configure start and end pickers
startLabel.setText(Bundle.VisualizationPanel_startLabel_text());
@ -371,20 +362,13 @@ final public class VisualizationPanel extends BorderPane {
filteredEvents.zoomParametersProperty().addListener(zoomListener);
refreshTimeUI(); //populate the viz
//this should use an event(EventBus) , not this weird observable pattern
controller.eventsDBStaleProperty().addListener(staleProperty -> {
if (controller.isEventsDBStale()) {
Platform.runLater(VisualizationPanel.this::refreshHistorgram);
}
});
refreshHistorgram();
}
/**
* Handle TagsUpdatedEvents.
*
* Mark that the visualization needs to be refreshed.
* Handle TagsUpdatedEvents by marking that the visualization needs to be
* refreshed.
*
* NOTE: This VisualizationPanel must be registered with the
* filteredEventsModel's EventBus in order for this handler to be invoked.
@ -392,14 +376,19 @@ final public class VisualizationPanel extends BorderPane {
* @param event The TagsUpdatedEvent to handle.
*/
@Subscribe
public void handleTimeLineTagEvent(TagsUpdatedEvent event) {
setNeedsRefresh(true);
public void handleTimeLineTagUpdate(TagsUpdatedEvent event) {
visualization.setOutOfDate();
Platform.runLater(() -> {
if (notificationPane.isShowing() == false) {
notificationPane.getActions().setAll(new Refresh());
notificationPane.show(Bundle.VisualizationPanel_tagsAddedOrDeleted(), new ImageView(INFORMATION));
}
});
}
/**
* Handle RefreshRequestedEvent.
*
* Mark that the visualization has been refreshed.
* Handle a RefreshRequestedEvent from the events model by refreshing the
* visualization.
*
* NOTE: This VisualizationPanel must be registered with the
* filteredEventsModel's EventBus in order for this handler to be invoked.
@ -407,19 +396,69 @@ final public class VisualizationPanel extends BorderPane {
* @param event The RefreshRequestedEvent to handle.
*/
@Subscribe
public void handleRefreshRequestedEvent(RefreshRequestedEvent event) {
setNeedsRefresh(false);
public void handleRefreshRequested(RefreshRequestedEvent event) {
visualization.refresh();
Platform.runLater(() -> {
if (Bundle.VisualizationPanel_tagsAddedOrDeleted().equals(notificationPane.getText())) {
notificationPane.hide();
}
});
}
/**
* Set whether the visualziation may not represent the current state of the
* DB, because, for example, tags have been updated.
* Handle a DBUpdatedEvent from the events model by refreshing the
* visualization.
*
* @param needsRefresh True if the visualization may not represent the
* current state of the DB.
* NOTE: This VisualizationPanel must be registered with the
* filteredEventsModel's EventBus in order for this handler to be invoked.
*
* @param event The DBUpdatedEvent to handle.
*/
private void setNeedsRefresh(Boolean needsRefresh) {
Platform.runLater(() -> VisualizationPanel.this.needsRefresh.set(needsRefresh));
@Subscribe
public void handleDBUpdated(DBUpdatedEvent event) {
visualization.refresh();
refreshHistorgram();
Platform.runLater(notificationPane::hide);
}
/**
* Handle a DataSourceAddedEvent from the events model by showing a
* notification.
*
* NOTE: This VisualizationPanel must be registered with the
* filteredEventsModel's EventBus in order for this handler to be invoked.
*
* @param event The DataSourceAddedEvent to handle.
*/
@Subscribe
@NbBundle.Messages({
"# {0} - datasource name",
"VisualizationPanel.notification.newDataSource={0} has been added as a new datasource. The Timeline DB may be out of date."})
public void handlDataSourceAdded(DataSourceAddedEvent event) {
Platform.runLater(() -> {
notificationPane.getActions().setAll(new UpdateDB(controller));
notificationPane.show(Bundle.VisualizationPanel_notification_newDataSource(event.getDataSource().getName()), new ImageView(WARNING));
});
}
/**
* Handle a DataSourceAnalysisCompletedEvent from the events modelby showing
* a notification.
*
* NOTE: This VisualizationPanel must be registered with the
* filteredEventsModel's EventBus in order for this handler to be invoked.
*
* @param event The DataSourceAnalysisCompletedEvent to handle.
*/
@Subscribe
@NbBundle.Messages({
"# {0} - datasource name",
"VisualizationPanel.notification.analysisComplete=Analysis has finished for {0}. The Timeline DB may be out of date."})
public void handleAnalysisCompleted(DataSourceAnalysisCompletedEvent event) {
Platform.runLater(() -> {
notificationPane.getActions().setAll(new UpdateDB(controller));
notificationPane.show(Bundle.VisualizationPanel_notification_analysisComplete(event.getDataSource().getName()), new ImageView(WARNING));
});
}
/**
@ -584,13 +623,15 @@ final public class VisualizationPanel extends BorderPane {
toolBar.getItems().removeAll(visualization.getSettingsNodes());
visualization.dispose();
}
//setup new vis.
visualization = vizPane;
visualization.update();
toolBar.getItems().addAll(vizPane.getSettingsNodes());
//setup new vis.
ActionUtils.configureButton(new Refresh(), refreshButton);//configure new refresh action for new visualization
visualization.refresh();
toolBar.getItems().addAll(2, vizPane.getSettingsNodes());
notificationPane.setContent(visualization);
//listen to has event sproperty and show "dialog" if it is false.
//listen to has events property and show "dialog" if it is false.
visualization.hasVisibleEventsProperty().addListener(hasEvents -> {
notificationPane.setContent(visualization.hasVisibleEvents()
? visualization
@ -601,7 +642,6 @@ final public class VisualizationPanel extends BorderPane {
);
});
});
setNeedsRefresh(false);
}
@NbBundle.Messages("NoEventsDialog.titledPane.text=No Visible Events")
@ -736,14 +776,14 @@ final public class VisualizationPanel extends BorderPane {
private class Refresh extends Action {
@NbBundle.Messages({
"VisualizationPanel.refresh.text=Refresh",
"VisualizationPanel.refresh.longText=Refresh the visualization to include information that is in the database but not visualized, such as newly updated tags."})
"VisualizationPanel.refresh.text=Refresh Vis.",
"VisualizationPanel.refresh.longText=Refresh the visualization to include information that is in the DB but not visualized, such as newly updated tags."})
Refresh() {
super(Bundle.VisualizationPanel_refresh_text());
setLongText(Bundle.VisualizationPanel_refresh_longText());
setGraphic(new ImageView(REFRESH));
setEventHandler(actionEvent -> filteredEvents.refresh());
disabledProperty().bind(needsRefresh.not());
setEventHandler(actionEvent -> filteredEvents.postRefreshRequest());
disabledProperty().bind(visualization.outOfDateProperty().not());
}
}
}

View File

@ -146,7 +146,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
countAxis.tickMarkVisibleProperty().bind(scaleIsLinear);
countAxis.minorTickVisibleProperty().bind(scaleIsLinear);
scaleProp.addListener(scale -> {
update();
refresh();
syncAxisScaleLabel();
});
syncAxisScaleLabel();
@ -320,7 +320,7 @@ public class CountsViewPane extends AbstractVisualizationPane<String, Number, No
@NbBundle.Messages({
"CountsViewPane.loggedTask.name=Updating Counts View",
"CountsViewPane.loggedTask.updatingCounts=Populating visualization"})
private class CountsUpdateTask extends VisualizationUpdateTask<List<String>> {
private class CountsUpdateTask extends VisualizationRefreshTask<List<String>> {
CountsUpdateTask() {
super(Bundle.CountsViewPane_loggedTask_name(), true);

View File

@ -403,7 +403,7 @@ final class EventCountsChart extends StackedBarChart<String, Number> implements
Bundle.CountsViewPane_detailSwitchMessage(),
Bundle.CountsViewPane_detailSwitchTitle(), JOptionPane.YES_NO_OPTION);
if (showConfirmDialog == JOptionPane.YES_OPTION) {
controller.setViewMode(VisualizationMode.DETAIL);
controller.setVisualizationMode(VisualizationMode.DETAIL);
}
/*

View File

@ -358,7 +358,7 @@ public class DetailViewPane extends AbstractVisualizationPane<DateTime, EventStr
"DetailViewPane.loggedTask.backButton=Back (Cancel)",
"# {0} - number of events",
"DetailViewPane.loggedTask.prompt=You are about to show details for {0} events. This might be very slow or even crash Autopsy.\n\nDo you want to continue?"})
private class DetailsUpdateTask extends VisualizationUpdateTask<Interval> {
private class DetailsUpdateTask extends VisualizationRefreshTask<Interval> {
DetailsUpdateTask() {
super(Bundle.DetailViewPane_loggedTask_name(), true);

View File

@ -15,10 +15,13 @@ import javafx.scene.control.ContextMenu;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.ui.IntervalSelector;
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
import org.sleuthkit.datamodel.TskData;
/**
*
@ -28,28 +31,26 @@ class ListChart extends TableView<Long> implements TimeLineChart<Long> {
private final TimeLineController controller;
private final TableColumn<Long, Long> idColumn = new TableColumn<>();
private final TableColumn<Long, Long> millisColumn = new TableColumn<>();
private final TableColumn<Long, Image> iconColumn = new TableColumn<>();
private final TableColumn<Long, String> descriptionColumn = new TableColumn<>();
private final TableColumn<Long, EventType> baseTypeColumn = new TableColumn<>();
private final TableColumn<Long, EventType> subTypeColumn = new TableColumn<>();
private final TableColumn<Long, TskData.FileKnown> knownColumn = new TableColumn<>();
ListChart(TimeLineController controller) {
this.controller = controller;
getColumns().addAll(Arrays.asList(idColumn, millisColumn));
getColumns().addAll(Arrays.asList(idColumn, iconColumn, millisColumn));
idColumn.setCellValueFactory(param -> new SimpleObjectProperty<>(param.getValue()));
millisColumn.setCellValueFactory(param -> {
return new SimpleObjectProperty<>(controller.getEventsModel().getEventById(param.getValue()).getStartMillis());
});
millisColumn.setCellFactory(col -> new TableCell<Long, Long>() {
@Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText("");
} else {
setText(TimeLineController.getZonedFormatter().print(item));
}
}
millisColumn.setCellFactory(col -> new EpochMillisCell());
iconColumn.setCellValueFactory(param -> {
return new SimpleObjectProperty<>(controller.getEventsModel().getEventById(param.getValue()).getEventType().getFXImage());
});
iconColumn.setCellFactory(col -> new ImageCell());
millisColumn.setSortType(TableColumn.SortType.DESCENDING);
millisColumn.setSortable(true);
@ -103,4 +104,31 @@ class ListChart extends TableView<Long> implements TimeLineChart<Long> {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
private static class ImageCell extends TableCell<Long, Image> {
@Override
protected void updateItem(Image item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
} else {
setGraphic(new ImageView(item));
}
}
}
private class EpochMillisCell extends TableCell<Long, Long> {
@Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText("");
} else {
setText(TimeLineController.getZonedFormatter().print(item));
}
}
}
}

View File

@ -97,7 +97,7 @@ public class ListViewPane extends AbstractVisualizationPane<Long, SingleEvent, N
}
}
private class ListUpdateTask extends VisualizationUpdateTask<Interval> {
private class ListUpdateTask extends VisualizationRefreshTask<Interval> {
ListUpdateTask() {
super("List update task", true);