mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-17 18:17:43 +00:00
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:
commit
f2b37fca09
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
||||
}
|
@ -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 {
|
||||
|
||||
|
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/warning.png
Normal file
BIN
Core/src/org/sleuthkit/autopsy/timeline/images/warning.png
Normal file
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 |
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user