mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-18 10:37:43 +00:00
Merge remote-tracking branch 'sleuthkit/develop' into develop
This commit is contained in:
commit
36a9017c03
@ -2,7 +2,8 @@ file.reference.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar
|
|||||||
file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar
|
file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar
|
||||||
file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone-2.7.0.jar
|
file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone-2.7.0.jar
|
||||||
file.reference.jython.jar-1=release/modules/ext/jython.jar
|
file.reference.jython.jar-1=release/modules/ext/jython.jar
|
||||||
file.reference.metadata-extractor-2.6.2.jar=release/modules/ext/metadata-extractor-2.6.2.jar
|
file.reference.metadata-extractor-2.8.1.jar=release/modules/ext/metadata-extractor-2.8.1.jar
|
||||||
|
file.reference.opencv-248.jar=release/modules/ext/opencv-248.jar
|
||||||
file.reference.Rejistry-1.0-SNAPSHOT.jar=release/modules/ext/Rejistry-1.0-SNAPSHOT.jar
|
file.reference.Rejistry-1.0-SNAPSHOT.jar=release/modules/ext/Rejistry-1.0-SNAPSHOT.jar
|
||||||
file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbinding-AllPlatforms.jar
|
file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbinding-AllPlatforms.jar
|
||||||
file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar
|
file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar
|
||||||
@ -10,12 +11,13 @@ file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar
|
|||||||
file.reference.StixLib.jar=release/modules/ext/StixLib.jar
|
file.reference.StixLib.jar=release/modules/ext/StixLib.jar
|
||||||
file.reference.tika-core-1.2.jar=release/modules/ext/tika-core-1.2.jar
|
file.reference.tika-core-1.2.jar=release/modules/ext/tika-core-1.2.jar
|
||||||
file.reference.Tsk_DataModel.jar=release/modules/ext/Tsk_DataModel.jar
|
file.reference.Tsk_DataModel.jar=release/modules/ext/Tsk_DataModel.jar
|
||||||
file.reference.xmpcore.jar=release/modules/ext/xmpcore.jar
|
file.reference.xmpcore-5.1.2.jar=release/modules/ext/xmpcore-5.1.2.jar
|
||||||
javac.source=1.8
|
javac.source=1.8
|
||||||
javac.compilerargs=-Xlint -Xlint:-serial
|
javac.compilerargs=-Xlint -Xlint:-serial
|
||||||
license.file=../LICENSE-2.0.txt
|
license.file=../LICENSE-2.0.txt
|
||||||
nbm.homepage=http://www.sleuthkit.org/
|
nbm.homepage=http://www.sleuthkit.org/
|
||||||
nbm.module.author=Brian Carrier
|
nbm.module.author=Brian Carrier
|
||||||
nbm.needs.restart=true
|
nbm.needs.restart=true
|
||||||
|
source.reference.metadata-extractor-2.8.1.jar=release/modules/ext/metadata-extractor-2.8.1-src.zip!/Source/
|
||||||
spec.version.base=10.3
|
spec.version.base=10.3
|
||||||
|
|
||||||
|
@ -203,10 +203,26 @@
|
|||||||
<package>org.sleuthkit.autopsy.report</package>
|
<package>org.sleuthkit.autopsy.report</package>
|
||||||
<package>org.sleuthkit.datamodel</package>
|
<package>org.sleuthkit.datamodel</package>
|
||||||
</public-packages>
|
</public-packages>
|
||||||
|
<class-path-extension>
|
||||||
|
<runtime-relative-path>ext/xmpcore-5.1.2.jar</runtime-relative-path>
|
||||||
|
<binary-origin>release/modules/ext/xmpcore-5.1.2.jar</binary-origin>
|
||||||
|
</class-path-extension>
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/jdom-2.0.5.jar</runtime-relative-path>
|
<runtime-relative-path>ext/jdom-2.0.5.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/jdom-2.0.5.jar</binary-origin>
|
<binary-origin>release/modules/ext/jdom-2.0.5.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
|
<class-path-extension>
|
||||||
|
<runtime-relative-path>ext/StixLib.jar</runtime-relative-path>
|
||||||
|
<binary-origin>release/modules/ext/StixLib.jar</binary-origin>
|
||||||
|
</class-path-extension>
|
||||||
|
<class-path-extension>
|
||||||
|
<runtime-relative-path>ext/sqlite-jdbc-3.8.11.jar</runtime-relative-path>
|
||||||
|
<binary-origin>release/modules/ext/sqlite-jdbc-3.8.11.jar</binary-origin>
|
||||||
|
</class-path-extension>
|
||||||
|
<class-path-extension>
|
||||||
|
<runtime-relative-path>ext/opencv-248.jar</runtime-relative-path>
|
||||||
|
<binary-origin>release/modules/ext/opencv-248.jar</binary-origin>
|
||||||
|
</class-path-extension>
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/Rejistry-1.0-SNAPSHOT.jar</runtime-relative-path>
|
<runtime-relative-path>ext/Rejistry-1.0-SNAPSHOT.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/Rejistry-1.0-SNAPSHOT.jar</binary-origin>
|
<binary-origin>release/modules/ext/Rejistry-1.0-SNAPSHOT.jar</binary-origin>
|
||||||
@ -219,34 +235,18 @@
|
|||||||
<runtime-relative-path>ext/jython-standalone-2.7.0.jar</runtime-relative-path>
|
<runtime-relative-path>ext/jython-standalone-2.7.0.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/jython-standalone-2.7.0.jar</binary-origin>
|
<binary-origin>release/modules/ext/jython-standalone-2.7.0.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
<class-path-extension>
|
|
||||||
<runtime-relative-path>ext/StixLib.jar</runtime-relative-path>
|
|
||||||
<binary-origin>release/modules/ext/StixLib.jar</binary-origin>
|
|
||||||
</class-path-extension>
|
|
||||||
<class-path-extension>
|
|
||||||
<runtime-relative-path>ext/opencv-248.jar</runtime-relative-path>
|
|
||||||
<binary-origin>release/modules/ext/opencv-248.jar</binary-origin>
|
|
||||||
</class-path-extension>
|
|
||||||
<class-path-extension>
|
|
||||||
<runtime-relative-path>ext/sqlite-jdbc-3.8.11.jar</runtime-relative-path>
|
|
||||||
<binary-origin>release/modules/ext/sqlite-jdbc-3.8.11.jar</binary-origin>
|
|
||||||
</class-path-extension>
|
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/sevenzipjbinding-AllPlatforms.jar</runtime-relative-path>
|
<runtime-relative-path>ext/sevenzipjbinding-AllPlatforms.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/sevenzipjbinding-AllPlatforms.jar</binary-origin>
|
<binary-origin>release/modules/ext/sevenzipjbinding-AllPlatforms.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
<class-path-extension>
|
|
||||||
<runtime-relative-path>ext/metadata-extractor-2.6.2.jar</runtime-relative-path>
|
|
||||||
<binary-origin>release/modules/ext/metadata-extractor-2.6.2.jar</binary-origin>
|
|
||||||
</class-path-extension>
|
|
||||||
<class-path-extension>
|
|
||||||
<runtime-relative-path>ext/xmpcore.jar</runtime-relative-path>
|
|
||||||
<binary-origin>release/modules/ext/xmpcore.jar</binary-origin>
|
|
||||||
</class-path-extension>
|
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/tika-core-1.2.jar</runtime-relative-path>
|
<runtime-relative-path>ext/tika-core-1.2.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/tika-core-1.2.jar</binary-origin>
|
<binary-origin>release/modules/ext/tika-core-1.2.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
|
<class-path-extension>
|
||||||
|
<runtime-relative-path>ext/metadata-extractor-2.8.1.jar</runtime-relative-path>
|
||||||
|
<binary-origin>release/modules/ext/metadata-extractor-2.8.1.jar</binary-origin>
|
||||||
|
</class-path-extension>
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/jdom-2.0.5-contrib.jar</runtime-relative-path>
|
<runtime-relative-path>ext/jdom-2.0.5-contrib.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/jdom-2.0.5-contrib.jar</binary-origin>
|
<binary-origin>release/modules/ext/jdom-2.0.5-contrib.jar</binary-origin>
|
||||||
|
Binary file not shown.
BIN
Core/release/modules/ext/metadata-extractor-2.8.1.jar
Executable file
BIN
Core/release/modules/ext/metadata-extractor-2.8.1.jar
Executable file
Binary file not shown.
BIN
Core/release/modules/ext/xmpcore-5.1.2.jar
Executable file
BIN
Core/release/modules/ext/xmpcore-5.1.2.jar
Executable file
Binary file not shown.
Binary file not shown.
@ -37,7 +37,7 @@ public class FileTypeExtensions {
|
|||||||
private final static List<String> TEXT_EXTENSIONS = Arrays.asList(".txt", ".rtf", ".log", ".text", ".xml"); //NON-NLS
|
private final static List<String> TEXT_EXTENSIONS = Arrays.asList(".txt", ".rtf", ".log", ".text", ".xml"); //NON-NLS
|
||||||
private final static List<String> WEB_EXTENSIONS = Arrays.asList(".html", ".htm", ".css", ".js", ".php", ".aspx"); //NON-NLS
|
private final static List<String> WEB_EXTENSIONS = Arrays.asList(".html", ".htm", ".css", ".js", ".php", ".aspx"); //NON-NLS
|
||||||
private final static List<String> PDF_EXTENSIONS = Arrays.asList(".pdf"); //NON-NLS
|
private final static List<String> PDF_EXTENSIONS = Arrays.asList(".pdf"); //NON-NLS
|
||||||
private final static List<String> ARCHIVE_EXTENSIONS = Arrays.asList(".zip", ".rar", ".7zip", ".7z", ".arj", ".tar", ".gzip", ".bzip", ".bzip2", ".cab", ".jar", ".cpio", ".ar", ".gz", ".tgz"); //NON-NLS
|
private final static List<String> ARCHIVE_EXTENSIONS = Arrays.asList(".zip", ".rar", ".7zip", ".7z", ".arj", ".tar", ".gzip", ".bzip", ".bzip2", ".cab", ".jar", ".cpio", ".ar", ".gz", ".tgz", ".bz2"); //NON-NLS
|
||||||
|
|
||||||
public static List<String> getImageExtensions() {
|
public static List<String> getImageExtensions() {
|
||||||
return IMAGE_EXTENSIONS;
|
return IMAGE_EXTENSIONS;
|
||||||
|
@ -37,3 +37,5 @@ EmbeddedFileExtractorIngestModule.ImageExtractor.xlsContainer.init.err=Xls conta
|
|||||||
EmbeddedFileExtractorIngestModule.ImageExtractor.xlsxContainer.init.err=Xlsx container could not be initialized while reading: {0}
|
EmbeddedFileExtractorIngestModule.ImageExtractor.xlsxContainer.init.err=Xlsx container could not be initialized while reading: {0}
|
||||||
EmbeddedFileExtractorIngestModule.ImageExtractor.extractImage.addToDB.exception.msg=Unable to add the derived files to the database.
|
EmbeddedFileExtractorIngestModule.ImageExtractor.extractImage.addToDB.exception.msg=Unable to add the derived files to the database.
|
||||||
EmbeddedFileExtractorIngestModule.ImageExtractor.getOutputFolderPath.exception.msg=Could not get path for image extraction from Abstract File: {0}
|
EmbeddedFileExtractorIngestModule.ImageExtractor.getOutputFolderPath.exception.msg=Could not get path for image extraction from Abstract File: {0}
|
||||||
|
EmbeddedFileExtractorIngestModule.ImageExtractor.getOutputFolderPath.exception.msg=Could not get path for image extraction from Abstract File: {0}
|
||||||
|
EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg=Unable to write content to disk. Not enough space.
|
@ -24,6 +24,8 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -40,7 +42,6 @@ import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
|
|||||||
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
|
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
|
||||||
import org.netbeans.api.progress.ProgressHandle;
|
import org.netbeans.api.progress.ProgressHandle;
|
||||||
import org.netbeans.api.progress.ProgressHandleFactory;
|
import org.netbeans.api.progress.ProgressHandleFactory;
|
||||||
import org.openide.util.Exceptions;
|
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
import org.sleuthkit.autopsy.casemodule.services.FileManager;
|
||||||
@ -98,7 +99,8 @@ class SevenZipExtractor {
|
|||||||
GZIP("application/gzip"),
|
GZIP("application/gzip"),
|
||||||
XGZIP("application/x-gzip"),
|
XGZIP("application/x-gzip"),
|
||||||
XBZIP2("application/x-bzip2"),
|
XBZIP2("application/x-bzip2"),
|
||||||
XTAR("application/x-tar");
|
XTAR("application/x-tar"),
|
||||||
|
XGTAR("application/x-gtar");
|
||||||
|
|
||||||
private final String mimeType;
|
private final String mimeType;
|
||||||
|
|
||||||
@ -121,9 +123,9 @@ class SevenZipExtractor {
|
|||||||
logger.log(Level.INFO, "7-Zip-JBinding library was initialized on supported platform: {0}", platform); //NON-NLS
|
logger.log(Level.INFO, "7-Zip-JBinding library was initialized on supported platform: {0}", platform); //NON-NLS
|
||||||
} catch (SevenZipNativeInitializationException e) {
|
} catch (SevenZipNativeInitializationException e) {
|
||||||
logger.log(Level.SEVERE, "Error initializing 7-Zip-JBinding library", e); //NON-NLS
|
logger.log(Level.SEVERE, "Error initializing 7-Zip-JBinding library", e); //NON-NLS
|
||||||
String msg = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.msg",
|
String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.msg",
|
||||||
EmbeddedFileExtractorModuleFactory.getModuleName());
|
EmbeddedFileExtractorModuleFactory.getModuleName());
|
||||||
String details = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errCantInitLib",
|
String details = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errCantInitLib",
|
||||||
e.getMessage());
|
e.getMessage());
|
||||||
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
|
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
|
||||||
throw new IngestModuleException(e.getMessage());
|
throw new IngestModuleException(e.getMessage());
|
||||||
@ -204,7 +206,7 @@ class SevenZipExtractor {
|
|||||||
if (cRatio >= MAX_COMPRESSION_RATIO) {
|
if (cRatio >= MAX_COMPRESSION_RATIO) {
|
||||||
String itemName = archiveFileItem.getPath();
|
String itemName = archiveFileItem.getPath();
|
||||||
logger.log(Level.INFO, "Possible zip bomb detected, compression ration: {0} for in archive item: {1}", new Object[]{cRatio, itemName}); //NON-NLS
|
logger.log(Level.INFO, "Possible zip bomb detected, compression ration: {0} for in archive item: {1}", new Object[]{cRatio, itemName}); //NON-NLS
|
||||||
String msg = NbBundle.getMessage(this.getClass(),
|
String msg = NbBundle.getMessage(SevenZipExtractor.class,
|
||||||
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName);
|
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName);
|
||||||
String path;
|
String path;
|
||||||
try {
|
try {
|
||||||
@ -212,7 +214,7 @@ class SevenZipExtractor {
|
|||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
path = archiveFile.getParentPath() + archiveFile.getName();
|
path = archiveFile.getParentPath() + archiveFile.getName();
|
||||||
}
|
}
|
||||||
String details = NbBundle.getMessage(this.getClass(),
|
String details = NbBundle.getMessage(SevenZipExtractor.class,
|
||||||
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path);
|
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path);
|
||||||
//MessageNotifyUtil.Notify.error(msg, details);
|
//MessageNotifyUtil.Notify.error(msg, details);
|
||||||
services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
|
services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
|
||||||
@ -311,9 +313,9 @@ class SevenZipExtractor {
|
|||||||
if (parentAr == null) {
|
if (parentAr == null) {
|
||||||
parentAr = archiveDepthCountTree.addArchive(null, archiveId);
|
parentAr = archiveDepthCountTree.addArchive(null, archiveId);
|
||||||
} else if (parentAr.getDepth() == MAX_DEPTH) {
|
} else if (parentAr.getDepth() == MAX_DEPTH) {
|
||||||
String msg = NbBundle.getMessage(this.getClass(),
|
String msg = NbBundle.getMessage(SevenZipExtractor.class,
|
||||||
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName());
|
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName());
|
||||||
String details = NbBundle.getMessage(this.getClass(),
|
String details = NbBundle.getMessage(SevenZipExtractor.class,
|
||||||
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
|
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
|
||||||
parentAr.getDepth(), archiveFilePath);
|
parentAr.getDepth(), archiveFilePath);
|
||||||
//MessageNotifyUtil.Notify.error(msg, details);
|
//MessageNotifyUtil.Notify.error(msg, details);
|
||||||
@ -328,7 +330,7 @@ class SevenZipExtractor {
|
|||||||
SevenZipContentReadStream stream = null;
|
SevenZipContentReadStream stream = null;
|
||||||
|
|
||||||
final ProgressHandle progress = ProgressHandleFactory.createHandle(
|
final ProgressHandle progress = ProgressHandleFactory.createHandle(
|
||||||
NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.moduleName"));
|
NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.moduleName"));
|
||||||
int processedItems = 0;
|
int processedItems = 0;
|
||||||
|
|
||||||
boolean progressStarted = false;
|
boolean progressStarted = false;
|
||||||
@ -400,7 +402,7 @@ class SevenZipExtractor {
|
|||||||
pathInArchive = "/" + useName;
|
pathInArchive = "/" + useName;
|
||||||
}
|
}
|
||||||
|
|
||||||
String msg = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg",
|
String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg",
|
||||||
archiveFilePath, pathInArchive);
|
archiveFilePath, pathInArchive);
|
||||||
logger.log(Level.WARNING, msg);
|
logger.log(Level.WARNING, msg);
|
||||||
|
|
||||||
@ -432,24 +434,19 @@ class SevenZipExtractor {
|
|||||||
fullEncryption = false;
|
fullEncryption = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Long size = item.getSize();
|
// NOTE: item.getSize() may return null in case of certain
|
||||||
if (size == null) {
|
// archiving formats. Eg: BZ2
|
||||||
// If the size property cannot be determined, out-of-disk-space
|
Long size = item.getSize();
|
||||||
// situations cannot be ascertained.
|
|
||||||
// Hence skip this file.
|
|
||||||
logger.log(Level.WARNING, "Size cannot be determined. Skipping file in archive: {0}", pathInArchive); //NON-NLS
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//check if unpacking this file will result in out of disk space
|
//check if unpacking this file will result in out of disk space
|
||||||
//this is additional to zip bomb prevention mechanism
|
//this is additional to zip bomb prevention mechanism
|
||||||
if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && size > 0) { //if known free space and file not empty
|
if (freeDiskSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN && size != null && size > 0) { //if free space is known and file is not empty.
|
||||||
long newDiskSpace = freeDiskSpace - size;
|
long newDiskSpace = freeDiskSpace - size;
|
||||||
if (newDiskSpace < MIN_FREE_DISK_SPACE) {
|
if (newDiskSpace < MIN_FREE_DISK_SPACE) {
|
||||||
String msg = NbBundle.getMessage(this.getClass(),
|
String msg = NbBundle.getMessage(SevenZipExtractor.class,
|
||||||
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
|
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
|
||||||
archiveFilePath, fileName);
|
archiveFilePath, fileName);
|
||||||
String details = NbBundle.getMessage(this.getClass(),
|
String details = NbBundle.getMessage(SevenZipExtractor.class,
|
||||||
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
|
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
|
||||||
//MessageNotifyUtil.Notify.error(msg, details);
|
//MessageNotifyUtil.Notify.error(msg, details);
|
||||||
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
|
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
|
||||||
@ -501,21 +498,31 @@ class SevenZipExtractor {
|
|||||||
final long modtime = writeTime == null ? 0L : writeTime.getTime() / 1000;
|
final long modtime = writeTime == null ? 0L : writeTime.getTime() / 1000;
|
||||||
final long accesstime = accessTime == null ? 0L : accessTime.getTime() / 1000;
|
final long accesstime = accessTime == null ? 0L : accessTime.getTime() / 1000;
|
||||||
|
|
||||||
//record derived data in unode, to be traversed later after unpacking the archive
|
|
||||||
unpackedNode.addDerivedInfo(size, !isDir,
|
|
||||||
0L, createtime, accesstime, modtime, localRelPath);
|
|
||||||
|
|
||||||
//unpack locally if a file
|
//unpack locally if a file
|
||||||
if (!isDir) {
|
|
||||||
SevenZipExtractor.UnpackStream unpackStream = null;
|
SevenZipExtractor.UnpackStream unpackStream = null;
|
||||||
|
if (!isDir) {
|
||||||
try {
|
try {
|
||||||
unpackStream = new SevenZipExtractor.UnpackStream(localAbsPath);
|
unpackStream = new SevenZipExtractor.UnpackStream(localAbsPath, freeDiskSpace, size == null);
|
||||||
item.extractSlow(unpackStream);
|
item.extractSlow(unpackStream);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
//could be something unexpected with this file, move on
|
//could be something unexpected with this file, move on
|
||||||
logger.log(Level.WARNING, "Could not extract file from archive: " + localAbsPath, e); //NON-NLS
|
logger.log(Level.WARNING, "Could not extract file from archive: " + localAbsPath, e); //NON-NLS
|
||||||
} finally {
|
} finally {
|
||||||
if (unpackStream != null) {
|
if (unpackStream != null) {
|
||||||
|
//record derived data in unode, to be traversed later after unpacking the archive
|
||||||
|
if (size != null) {
|
||||||
|
// unpackedNode.bytesWritten will not be set in
|
||||||
|
// this case. Use 'size' which has been set
|
||||||
|
// previously.
|
||||||
|
unpackedNode.addDerivedInfo(size, !isDir,
|
||||||
|
0L, createtime, accesstime, modtime, localRelPath);
|
||||||
|
} else {
|
||||||
|
// since size is unknown, use
|
||||||
|
// unpackStream.getNumberOfBytesWritten() to get
|
||||||
|
// the size.
|
||||||
|
unpackedNode.addDerivedInfo(unpackStream.getNumberOfBytesWritten(), !isDir,
|
||||||
|
0L, createtime, accesstime, modtime, localRelPath);
|
||||||
|
}
|
||||||
unpackStream.close();
|
unpackStream.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -549,9 +556,9 @@ class SevenZipExtractor {
|
|||||||
|
|
||||||
// print a message if the file is allocated
|
// print a message if the file is allocated
|
||||||
if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
|
if (archiveFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
|
||||||
String msg = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
|
String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.msg",
|
||||||
archiveFile.getName());
|
archiveFile.getName());
|
||||||
String details = NbBundle.getMessage(this.getClass(),
|
String details = NbBundle.getMessage(SevenZipExtractor.class,
|
||||||
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
|
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
|
||||||
archiveFilePath, ex.getMessage());
|
archiveFilePath, ex.getMessage());
|
||||||
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
|
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
|
||||||
@ -590,8 +597,8 @@ class SevenZipExtractor {
|
|||||||
logger.log(Level.SEVERE, "Error creating blackboard artifact for encryption detected for file: " + archiveFilePath, ex); //NON-NLS
|
logger.log(Level.SEVERE, "Error creating blackboard artifact for encryption detected for file: " + archiveFilePath, ex); //NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
String msg = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
|
String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
|
||||||
String details = NbBundle.getMessage(this.getClass(),
|
String details = NbBundle.getMessage(SevenZipExtractor.class,
|
||||||
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
|
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
|
||||||
archiveFile.getName(), EmbeddedFileExtractorModuleFactory.getModuleName());
|
archiveFile.getName(), EmbeddedFileExtractorModuleFactory.getModuleName());
|
||||||
services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
|
services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
|
||||||
@ -612,8 +619,15 @@ class SevenZipExtractor {
|
|||||||
|
|
||||||
private OutputStream output;
|
private OutputStream output;
|
||||||
private String localAbsPath;
|
private String localAbsPath;
|
||||||
|
private long freeDiskSpace;
|
||||||
|
private boolean sizeUnknown = false;
|
||||||
|
private boolean outOfSpace = false;
|
||||||
|
private long bytesWritten = 0;
|
||||||
|
|
||||||
UnpackStream(String localAbsPath) {
|
UnpackStream(String localAbsPath, long freeDiskSpace, boolean sizeUnknown) {
|
||||||
|
this.sizeUnknown = sizeUnknown;
|
||||||
|
this.freeDiskSpace = freeDiskSpace;
|
||||||
|
this.localAbsPath = localAbsPath;
|
||||||
try {
|
try {
|
||||||
output = new BufferedOutputStream(new FileOutputStream(localAbsPath));
|
output = new BufferedOutputStream(new FileOutputStream(localAbsPath));
|
||||||
} catch (FileNotFoundException ex) {
|
} catch (FileNotFoundException ex) {
|
||||||
@ -622,13 +636,38 @@ class SevenZipExtractor {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getNumberOfBytesWritten() {
|
||||||
|
return this.bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int write(byte[] bytes) throws SevenZipException {
|
public int write(byte[] bytes) throws SevenZipException {
|
||||||
try {
|
try {
|
||||||
|
if (!sizeUnknown) {
|
||||||
output.write(bytes);
|
output.write(bytes);
|
||||||
|
} else {
|
||||||
|
// If the content size is unknown, cautiously write to disk.
|
||||||
|
// Write only if byte array is less than 80% of the current
|
||||||
|
// free disk space.
|
||||||
|
if (freeDiskSpace == IngestMonitor.DISK_FREE_SPACE_UNKNOWN || bytes.length < 0.8 * freeDiskSpace) {
|
||||||
|
output.write(bytes);
|
||||||
|
// NOTE: this method is called multiple times for a
|
||||||
|
// single extractSlow() call. Update bytesWritten and
|
||||||
|
// freeDiskSpace after every write operation.
|
||||||
|
this.bytesWritten += bytes.length;
|
||||||
|
this.freeDiskSpace -= bytes.length;
|
||||||
|
} else {
|
||||||
|
this.outOfSpace = true;
|
||||||
|
logger.log(Level.INFO, NbBundle.getMessage(
|
||||||
|
SevenZipExtractor.class,
|
||||||
|
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
|
||||||
|
throw new SevenZipException(
|
||||||
|
NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.noSpace.msg"));
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new SevenZipException(
|
throw new SevenZipException(
|
||||||
NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
|
NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackStream.write.exception.msg",
|
||||||
localAbsPath), ex);
|
localAbsPath), ex);
|
||||||
}
|
}
|
||||||
return bytes.length;
|
return bytes.length;
|
||||||
@ -639,6 +678,9 @@ class SevenZipExtractor {
|
|||||||
try {
|
try {
|
||||||
output.flush();
|
output.flush();
|
||||||
output.close();
|
output.close();
|
||||||
|
if (this.outOfSpace) {
|
||||||
|
Files.delete(Paths.get(this.localAbsPath));
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.log(Level.SEVERE, "Error closing unpack stream for file: {0}", localAbsPath); //NON-NLS
|
logger.log(Level.SEVERE, "Error closing unpack stream for file: {0}", localAbsPath); //NON-NLS
|
||||||
}
|
}
|
||||||
@ -774,7 +816,7 @@ class SevenZipExtractor {
|
|||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
logger.log(Level.SEVERE, "Error adding a derived file to db:" + fileName, ex); //NON-NLS
|
logger.log(Level.SEVERE, "Error adding a derived file to db:" + fileName, ex); //NON-NLS
|
||||||
throw new TskCoreException(
|
throw new TskCoreException(
|
||||||
NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
|
NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
|
||||||
fileName), ex);
|
fileName), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,10 +22,21 @@ import com.drew.imaging.ImageMetadataReader;
|
|||||||
import com.drew.imaging.ImageProcessingException;
|
import com.drew.imaging.ImageProcessingException;
|
||||||
import com.drew.lang.GeoLocation;
|
import com.drew.lang.GeoLocation;
|
||||||
import com.drew.lang.Rational;
|
import com.drew.lang.Rational;
|
||||||
|
import com.drew.metadata.Directory;
|
||||||
import com.drew.metadata.Metadata;
|
import com.drew.metadata.Metadata;
|
||||||
|
import com.drew.metadata.MetadataException;
|
||||||
|
import com.drew.metadata.exif.makernotes.CanonMakernoteDirectory;
|
||||||
import com.drew.metadata.exif.ExifIFD0Directory;
|
import com.drew.metadata.exif.ExifIFD0Directory;
|
||||||
import com.drew.metadata.exif.ExifSubIFDDirectory;
|
import com.drew.metadata.exif.ExifSubIFDDirectory;
|
||||||
import com.drew.metadata.exif.GpsDirectory;
|
import com.drew.metadata.exif.GpsDirectory;
|
||||||
|
import com.drew.metadata.exif.makernotes.CasioType1MakernoteDirectory;
|
||||||
|
import com.drew.metadata.exif.makernotes.FujifilmMakernoteDirectory;
|
||||||
|
import com.drew.metadata.exif.makernotes.KodakMakernoteDirectory;
|
||||||
|
import com.drew.metadata.exif.makernotes.NikonType2MakernoteDirectory;
|
||||||
|
import com.drew.metadata.exif.makernotes.PanasonicMakernoteDirectory;
|
||||||
|
import com.drew.metadata.exif.makernotes.PentaxMakernoteDirectory;
|
||||||
|
import com.drew.metadata.exif.makernotes.SanyoMakernoteDirectory;
|
||||||
|
import com.drew.metadata.exif.makernotes.SonyType1MakernoteDirectory;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -33,6 +44,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.TimeZone;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
@ -47,6 +60,8 @@ import org.sleuthkit.datamodel.AbstractFile;
|
|||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
import org.sleuthkit.datamodel.Image;
|
||||||
import org.sleuthkit.datamodel.ReadContentInputStream;
|
import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
import org.sleuthkit.datamodel.TskData;
|
import org.sleuthkit.datamodel.TskData;
|
||||||
@ -63,10 +78,13 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
|
|||||||
private final IngestServices services = IngestServices.getInstance();
|
private final IngestServices services = IngestServices.getInstance();
|
||||||
private final AtomicInteger filesProcessed = new AtomicInteger(0);
|
private final AtomicInteger filesProcessed = new AtomicInteger(0);
|
||||||
private volatile boolean filesToFire = false;
|
private volatile boolean filesToFire = false;
|
||||||
|
private volatile boolean facesDetected = false;
|
||||||
|
private final List<BlackboardArtifact> listOfFacesDetectedArtifacts = new ArrayList<>();
|
||||||
private long jobId;
|
private long jobId;
|
||||||
private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
|
private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
|
||||||
private FileTypeDetector fileTypeDetector;
|
private FileTypeDetector fileTypeDetector;
|
||||||
private final HashSet<String> supportedMimeTypes = new HashSet<>();
|
private final HashSet<String> supportedMimeTypes = new HashSet<>();
|
||||||
|
private TimeZone timeZone = null;
|
||||||
|
|
||||||
ExifParserFileIngestModule() {
|
ExifParserFileIngestModule() {
|
||||||
supportedMimeTypes.add("audio/x-wav");
|
supportedMimeTypes.add("audio/x-wav");
|
||||||
@ -103,10 +121,17 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
|
|||||||
|
|
||||||
// update the tree every 1000 files if we have EXIF data that is not being being displayed
|
// update the tree every 1000 files if we have EXIF data that is not being being displayed
|
||||||
final int filesProcessedValue = filesProcessed.incrementAndGet();
|
final int filesProcessedValue = filesProcessed.incrementAndGet();
|
||||||
if ((filesToFire) && (filesProcessedValue % 1000 == 0)) {
|
if ((filesProcessedValue % 1000 == 0)) {
|
||||||
|
if (filesToFire) {
|
||||||
services.fireModuleDataEvent(new ModuleDataEvent(ExifParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF));
|
services.fireModuleDataEvent(new ModuleDataEvent(ExifParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF));
|
||||||
filesToFire = false;
|
filesToFire = false;
|
||||||
}
|
}
|
||||||
|
if (facesDetected) {
|
||||||
|
services.fireModuleDataEvent(new ModuleDataEvent(ExifParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_FACE_DETECTED, listOfFacesDetectedArtifacts));
|
||||||
|
listOfFacesDetectedArtifacts.clear();
|
||||||
|
facesDetected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//skip unsupported
|
//skip unsupported
|
||||||
if (!parsableFormat(content)) {
|
if (!parsableFormat(content)) {
|
||||||
@ -125,19 +150,32 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
|
|||||||
bin = new BufferedInputStream(in);
|
bin = new BufferedInputStream(in);
|
||||||
|
|
||||||
Collection<BlackboardAttribute> attributes = new ArrayList<>();
|
Collection<BlackboardAttribute> attributes = new ArrayList<>();
|
||||||
Metadata metadata = ImageMetadataReader.readMetadata(bin, true);
|
Metadata metadata = ImageMetadataReader.readMetadata(bin);
|
||||||
|
|
||||||
// Date
|
// Date
|
||||||
ExifSubIFDDirectory exifDir = metadata.getDirectory(ExifSubIFDDirectory.class);
|
ExifSubIFDDirectory exifDir = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
|
||||||
if (exifDir != null) {
|
if (exifDir != null) {
|
||||||
Date date = exifDir.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL);
|
|
||||||
|
// set the timeZone for the current datasource.
|
||||||
|
if (timeZone == null) {
|
||||||
|
try {
|
||||||
|
Content dataSource = f.getDataSource();
|
||||||
|
if ((dataSource != null) && (dataSource instanceof Image)) {
|
||||||
|
Image image = (Image) dataSource;
|
||||||
|
timeZone = TimeZone.getTimeZone(image.getTimeZone());
|
||||||
|
}
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
logger.log(Level.INFO, "Error getting time zones", ex); //NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Date date = exifDir.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL, timeZone);
|
||||||
if (date != null) {
|
if (date != null) {
|
||||||
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID(), ExifParserModuleFactory.getModuleName(), date.getTime() / 1000));
|
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID(), ExifParserModuleFactory.getModuleName(), date.getTime() / 1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GPS Stuff
|
// GPS Stuff
|
||||||
GpsDirectory gpsDir = metadata.getDirectory(GpsDirectory.class);
|
GpsDirectory gpsDir = metadata.getFirstDirectoryOfType(GpsDirectory.class);
|
||||||
if (gpsDir != null) {
|
if (gpsDir != null) {
|
||||||
GeoLocation loc = gpsDir.getGeoLocation();
|
GeoLocation loc = gpsDir.getGeoLocation();
|
||||||
if (loc != null) {
|
if (loc != null) {
|
||||||
@ -147,14 +185,14 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
|
|||||||
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID(), ExifParserModuleFactory.getModuleName(), longitude));
|
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID(), ExifParserModuleFactory.getModuleName(), longitude));
|
||||||
}
|
}
|
||||||
|
|
||||||
Rational altitude = gpsDir.getRational(GpsDirectory.TAG_GPS_ALTITUDE);
|
Rational altitude = gpsDir.getRational(GpsDirectory.TAG_ALTITUDE);
|
||||||
if (altitude != null) {
|
if (altitude != null) {
|
||||||
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID(), ExifParserModuleFactory.getModuleName(), altitude.doubleValue()));
|
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID(), ExifParserModuleFactory.getModuleName(), altitude.doubleValue()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Device info
|
// Device info
|
||||||
ExifIFD0Directory devDir = metadata.getDirectory(ExifIFD0Directory.class);
|
ExifIFD0Directory devDir = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
|
||||||
if (devDir != null) {
|
if (devDir != null) {
|
||||||
String model = devDir.getString(ExifIFD0Directory.TAG_MODEL);
|
String model = devDir.getString(ExifIFD0Directory.TAG_MODEL);
|
||||||
if (model != null && !model.isEmpty()) {
|
if (model != null && !model.isEmpty()) {
|
||||||
@ -167,6 +205,11 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (containsFace(metadata)) {
|
||||||
|
listOfFacesDetectedArtifacts.add(f.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_FACE_DETECTED));
|
||||||
|
facesDetected = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Add the attributes, if there are any, to a new artifact
|
// Add the attributes, if there are any, to a new artifact
|
||||||
if (!attributes.isEmpty()) {
|
if (!attributes.isEmpty()) {
|
||||||
BlackboardArtifact bba = f.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF);
|
BlackboardArtifact bba = f.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF);
|
||||||
@ -199,6 +242,121 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if this metadata contains any tags related to facial information.
|
||||||
|
* NOTE: Cases with this metadata containing tags like enabled red-eye
|
||||||
|
* reduction settings, portrait settings, etc are also assumed to contain
|
||||||
|
* facial information. The method returns true. The return value of this
|
||||||
|
* method does NOT guarantee actual presence of face.
|
||||||
|
*
|
||||||
|
* @param metadata the metadata which needs to be parsed for possible facial
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* @return returns true if the metadata contains any tags related to facial
|
||||||
|
* information.
|
||||||
|
*/
|
||||||
|
private boolean containsFace(Metadata metadata) {
|
||||||
|
Directory d = metadata.getFirstDirectoryOfType(CanonMakernoteDirectory.class);
|
||||||
|
if (d != null) {
|
||||||
|
if (d.containsTag(CanonMakernoteDirectory.TAG_FACE_DETECT_ARRAY_1)
|
||||||
|
&& d.getString(CanonMakernoteDirectory.TAG_FACE_DETECT_ARRAY_1) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (d.containsTag(CanonMakernoteDirectory.TAG_FACE_DETECT_ARRAY_2)
|
||||||
|
&& d.getString(CanonMakernoteDirectory.TAG_FACE_DETECT_ARRAY_2) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = metadata.getFirstDirectoryOfType(CasioType1MakernoteDirectory.class);
|
||||||
|
if (d != null) {
|
||||||
|
try {
|
||||||
|
if (d.containsTag(CasioType1MakernoteDirectory.TAG_FLASH_MODE)
|
||||||
|
&& d.getInt(CasioType1MakernoteDirectory.TAG_FLASH_MODE) == 0x04) { //0x04 = "Red eye reduction"
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (MetadataException ex) {
|
||||||
|
// move on and check next directory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = metadata.getFirstDirectoryOfType(FujifilmMakernoteDirectory.class);
|
||||||
|
if (d != null) {
|
||||||
|
if (d.containsTag(FujifilmMakernoteDirectory.TAG_FACES_DETECTED)
|
||||||
|
&& d.getString(FujifilmMakernoteDirectory.TAG_FACES_DETECTED) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = metadata.getFirstDirectoryOfType(KodakMakernoteDirectory.class);
|
||||||
|
if (d != null) {
|
||||||
|
try {
|
||||||
|
if (d.containsTag(KodakMakernoteDirectory.TAG_FLASH_MODE)
|
||||||
|
&& d.getInt(KodakMakernoteDirectory.TAG_FLASH_MODE) == 0x03) { //0x03 = "Red Eye"
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (MetadataException ex) {
|
||||||
|
/// move on and check next directory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = metadata.getFirstDirectoryOfType(NikonType2MakernoteDirectory.class);
|
||||||
|
if (d != null) {
|
||||||
|
if (d.containsTag(NikonType2MakernoteDirectory.TAG_SCENE_MODE)
|
||||||
|
&& d.getString(NikonType2MakernoteDirectory.TAG_SCENE_MODE) != null
|
||||||
|
&& (d.getString(NikonType2MakernoteDirectory.TAG_SCENE_MODE).equals("BEST FACE") // NON-NLS
|
||||||
|
|| (d.getString(NikonType2MakernoteDirectory.TAG_SCENE_MODE).equals("SMILE")))) { // NON-NLS
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = metadata.getFirstDirectoryOfType(PanasonicMakernoteDirectory.class);
|
||||||
|
if (d != null) {
|
||||||
|
if (d.containsTag(PanasonicMakernoteDirectory.TAG_FACES_DETECTED)
|
||||||
|
&& d.getString(PanasonicMakernoteDirectory.TAG_FACES_DETECTED) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = metadata.getFirstDirectoryOfType(PentaxMakernoteDirectory.class);
|
||||||
|
if (d != null) {
|
||||||
|
try {
|
||||||
|
if (d.containsTag(PentaxMakernoteDirectory.TAG_FLASH_MODE)
|
||||||
|
&& d.getInt(PentaxMakernoteDirectory.TAG_FLASH_MODE) == 6) { // 6 = Red-eye Reduction
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (MetadataException ex) {
|
||||||
|
// move on and check next directory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = metadata.getFirstDirectoryOfType(SanyoMakernoteDirectory.class);
|
||||||
|
if (d != null) {
|
||||||
|
if (d.containsTag(SanyoMakernoteDirectory.TAG_MANUAL_FOCUS_DISTANCE_OR_FACE_INFO)
|
||||||
|
&& d.getString(SanyoMakernoteDirectory.TAG_MANUAL_FOCUS_DISTANCE_OR_FACE_INFO) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = metadata.getFirstDirectoryOfType(SonyType1MakernoteDirectory.class);
|
||||||
|
if (d != null) {
|
||||||
|
try {
|
||||||
|
if (d.containsTag(SonyType1MakernoteDirectory.TAG_AF_MODE)
|
||||||
|
&& d.getInt(SonyType1MakernoteDirectory.TAG_AF_MODE) == 15) { //15 = "Face Detected"
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (d.containsTag(SonyType1MakernoteDirectory.TAG_EXPOSURE_MODE)
|
||||||
|
&& d.getInt(SonyType1MakernoteDirectory.TAG_EXPOSURE_MODE) == 14) { //14 = "Smile shutter"
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (MetadataException ex) {
|
||||||
|
// move on and check next directory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if should try to attempt to extract exif. Currently checks if JPEG
|
* Checks if should try to attempt to extract exif. Currently checks if JPEG
|
||||||
* image (by signature)
|
* image (by signature)
|
||||||
@ -225,10 +383,15 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
|
|||||||
public void shutDown() {
|
public void shutDown() {
|
||||||
// We only need to check for this final event on the last module per job
|
// We only need to check for this final event on the last module per job
|
||||||
if (refCounter.decrementAndGet(jobId) == 0) {
|
if (refCounter.decrementAndGet(jobId) == 0) {
|
||||||
|
timeZone = null;
|
||||||
if (filesToFire) {
|
if (filesToFire) {
|
||||||
//send the final new data event
|
//send the final new data event
|
||||||
services.fireModuleDataEvent(new ModuleDataEvent(ExifParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF));
|
services.fireModuleDataEvent(new ModuleDataEvent(ExifParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF));
|
||||||
}
|
}
|
||||||
|
if (facesDetected) {
|
||||||
|
//send the final new data event
|
||||||
|
services.fireModuleDataEvent(new ModuleDataEvent(ExifParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_FACE_DETECTED, listOfFacesDetectedArtifacts));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
OpenIDE-Module-Name=Timeline
|
|
||||||
CTL_MakeTimeline="Timeline"
|
CTL_MakeTimeline="Timeline"
|
||||||
CTL_TimeLineTopComponentAction=TimeLineTopComponent
|
CTL_TimeLineTopComponentAction=TimeLineTopComponent
|
||||||
CTL_TimeLineTopComponent=Timeline Window
|
CTL_TimeLineTopComponent=Timeline Window
|
||||||
@ -11,10 +10,6 @@ Timeline.goToButton.text=Go To\:
|
|||||||
Timeline.yearBarChart.x.years=Years
|
Timeline.yearBarChart.x.years=Years
|
||||||
Timeline.resultPanel.loading=Loading...
|
Timeline.resultPanel.loading=Loading...
|
||||||
Timeline.node.root=Root
|
Timeline.node.root=Root
|
||||||
Timeline.propChg.confDlg.timelineOOD.msg=The event data is out of date. Would you like to regenerate it?
|
|
||||||
Timeline.propChg.confDlg.timelineOOD.details=Timeline
|
|
||||||
Timeline.initTimeline.confDlg.genBeforeIngest.msg=You are trying to generate a timeline before ingest has been completed. The timeline may be incomplete. Do you want to continue?
|
|
||||||
Timeline.initTimeline.confDlg.genBeforeIngest.details=Timeline
|
|
||||||
TimelineFrame.title=Timeline
|
TimelineFrame.title=Timeline
|
||||||
TimelinePanel.jButton1.text=6m
|
TimelinePanel.jButton1.text=6m
|
||||||
TimelinePanel.jButton13.text=all
|
TimelinePanel.jButton13.text=all
|
||||||
@ -29,18 +24,10 @@ TimelinePanel.jButton7.text=3d
|
|||||||
TimelinePanel.jButton2.text=1m
|
TimelinePanel.jButton2.text=1m
|
||||||
TimelinePanel.jButton3.text=3m
|
TimelinePanel.jButton3.text=3m
|
||||||
TimelinePanel.jButton4.text=2w
|
TimelinePanel.jButton4.text=2w
|
||||||
ProgressWindow.progressHeader.text=\
|
|
||||||
TimeLineTopComponent.eventsTab.name=Events
|
TimeLineTopComponent.eventsTab.name=Events
|
||||||
TimeLineTopComponent.filterTab.name=Filters
|
TimeLineTopComponent.filterTab.name=Filters
|
||||||
Timeline.showLastPopulatedWhileIngestingConf.confDlg.details=Timeline
|
|
||||||
Timeline.do_repopulate.msg=The Timeline events database was previously populated while ingest was running.\nSome events may not have been populated or may have been populated inaccurately.\nDo you want to repopulate the events database now?
|
|
||||||
Timeline.pushDescrLOD.confdlg.msg=You are about to show details for {0} events. This might be very slow or even crash Autopsy.\n\nDo you want to continue?
|
|
||||||
Timeline.pushDescrLOD.confdlg.details=
|
|
||||||
OpenTimelineAction.title=Timeline
|
OpenTimelineAction.title=Timeline
|
||||||
Timeline.ProgressWindow.cancel.confdlg.msg=Do you want to cancel timeline creation?
|
|
||||||
Timeline.ProgressWindow.cancel.confdlg.detail=Cancel timeline creation?
|
|
||||||
Timeline.progressWindow.name=Timeline
|
|
||||||
Timeline.progressWindow.title=Generating Timeline data
|
|
||||||
OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources.
|
OpenTimeLineAction.msgdlg.text=Could not create timeline, there are no data sources.
|
||||||
TimeLineTopComponent.timeZonePanel.text=Display Times In\:
|
TimeLineTopComponent.timeZonePanel.text=Display Times In\:
|
||||||
datasource.missing.confirmation=The Timeline events database was previously populated with an old version of Autopsy.\nThe data source filter will be unavailable unless you update the events database.\nDo you want to update the events database now?
|
ProgressWindow.progressHeader.text=\
|
||||||
|
|
||||||
|
@ -54,7 +54,10 @@
|
|||||||
<Component class="javax.swing.JLabel" name="progressHeader">
|
<Component class="javax.swing.JLabel" name="progressHeader">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||||
<ResourceString bundle="org/sleuthkit/autopsy/advancedtimeline/Bundle.properties" key="ProgressWindow.progressHeader.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
<ResourceString bundle="org/sleuthkit/autopsy/timeline/Bundle.properties" key="ProgressWindow.progressHeader.text" replaceFormat="NbBundle.getMessage({sourceFileName}.class, "{key}")"/>
|
||||||
|
</Property>
|
||||||
|
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||||
|
<Dimension value="[10, 14]"/>
|
||||||
</Property>
|
</Property>
|
||||||
</Properties>
|
</Properties>
|
||||||
</Component>
|
</Component>
|
||||||
|
@ -21,25 +21,19 @@ package org.sleuthkit.autopsy.timeline;
|
|||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.WindowAdapter;
|
|
||||||
import java.awt.event.WindowEvent;
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
import javax.swing.ActionMap;
|
import javax.swing.ActionMap;
|
||||||
import javax.swing.GroupLayout;
|
|
||||||
import javax.swing.InputMap;
|
import javax.swing.InputMap;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JFrame;
|
import javax.swing.JFrame;
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JProgressBar;
|
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
import javax.swing.LayoutStyle;
|
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
import org.openide.awt.Mnemonics;
|
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.openide.windows.WindowManager;
|
import org.openide.windows.WindowManager;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog with progress bar that pops up when timeline is being generated
|
* Dialog with progress bar that pops up when timeline is being generated
|
||||||
@ -51,6 +45,8 @@ public class ProgressWindow extends JFrame {
|
|||||||
/**
|
/**
|
||||||
* Creates new form TimelineProgressDialog
|
* Creates new form TimelineProgressDialog
|
||||||
*/
|
*/
|
||||||
|
@NbBundle.Messages({"Timeline.progressWindow.name=Timeline",
|
||||||
|
"Timeline.progressWindow.title=Generating Timeline data"})
|
||||||
public ProgressWindow(Component parent, boolean modal, SwingWorker<?, ?> worker) {
|
public ProgressWindow(Component parent, boolean modal, SwingWorker<?, ?> worker) {
|
||||||
super();
|
super();
|
||||||
initComponents();
|
initComponents();
|
||||||
@ -64,9 +60,8 @@ public class ProgressWindow extends JFrame {
|
|||||||
setIconImage(WindowManager.getDefault().getMainWindow().getIconImage());
|
setIconImage(WindowManager.getDefault().getMainWindow().getIconImage());
|
||||||
});
|
});
|
||||||
|
|
||||||
//progressBar.setIndeterminate(true);
|
setName(Bundle.Timeline_progressWindow_name());
|
||||||
setName(NbBundle.getMessage(TimeLineTopComponent.class, "Timeline.progressWindow.name"));
|
setTitle(Bundle.Timeline_progressWindow_title());
|
||||||
setTitle(NbBundle.getMessage(TimeLineTopComponent.class, "Timeline.progressWindow.title"));
|
|
||||||
// Close the dialog when Esc is pressed
|
// Close the dialog when Esc is pressed
|
||||||
String cancelName = "cancel"; // NON-NLS
|
String cancelName = "cancel"; // NON-NLS
|
||||||
InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
||||||
@ -83,46 +78,6 @@ public class ProgressWindow extends JFrame {
|
|||||||
this.worker = worker;
|
this.worker = worker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateProgress(final int progress) {
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
progressBar.setValue(progress);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateProgress(final int progress, final String message) {
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
progressBar.setValue(progress);
|
|
||||||
progressBar.setString(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateProgress(final String message) {
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
progressBar.setString(message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setProgressTotal(final int total) {
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
progressBar.setIndeterminate(false);
|
|
||||||
progressBar.setMaximum(total);
|
|
||||||
progressBar.setStringPainted(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateHeaderMessage(final String headerMessage) {
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
progressHeader.setText(headerMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIndeterminate() {
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
progressBar.setIndeterminate(true);
|
|
||||||
progressBar.setStringPainted(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called from within the constructor to initialize the form.
|
* This method is called from within the constructor to initialize the form.
|
||||||
* WARNING: Do NOT modify this code. The content of this method is always
|
* WARNING: Do NOT modify this code. The content of this method is always
|
||||||
@ -132,38 +87,39 @@ public class ProgressWindow extends JFrame {
|
|||||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
|
|
||||||
progressBar = new JProgressBar();
|
progressBar = new javax.swing.JProgressBar();
|
||||||
progressHeader = new JLabel();
|
progressHeader = new javax.swing.JLabel();
|
||||||
|
|
||||||
addWindowListener(new WindowAdapter() {
|
addWindowListener(new java.awt.event.WindowAdapter() {
|
||||||
public void windowClosing(WindowEvent evt) {
|
public void windowClosing(java.awt.event.WindowEvent evt) {
|
||||||
closeDialog(evt);
|
closeDialog(evt);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Mnemonics.setLocalizedText(progressHeader, NbBundle.getMessage(ProgressWindow.class, "ProgressWindow.progressHeader.text")); // NOI18N
|
org.openide.awt.Mnemonics.setLocalizedText(progressHeader, NbBundle.getMessage(ProgressWindow.class, "ProgressWindow.progressHeader.text")); // NOI18N
|
||||||
|
progressHeader.setMinimumSize(new java.awt.Dimension(10, 14));
|
||||||
|
|
||||||
GroupLayout layout = new GroupLayout(getContentPane());
|
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
|
||||||
getContentPane().setLayout(layout);
|
getContentPane().setLayout(layout);
|
||||||
layout.setHorizontalGroup(
|
layout.setHorizontalGroup(
|
||||||
layout.createParallelGroup(GroupLayout.Alignment.LEADING)
|
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
.addGroup(layout.createSequentialGroup()
|
.addGroup(layout.createSequentialGroup()
|
||||||
.addContainerGap()
|
.addContainerGap()
|
||||||
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
|
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
.addComponent(progressBar, GroupLayout.DEFAULT_SIZE, 504, Short.MAX_VALUE)
|
.addComponent(progressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 504, Short.MAX_VALUE)
|
||||||
.addGroup(layout.createSequentialGroup()
|
.addGroup(layout.createSequentialGroup()
|
||||||
.addComponent(progressHeader)
|
.addComponent(progressHeader, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
.addGap(0, 0, Short.MAX_VALUE)))
|
.addGap(0, 0, Short.MAX_VALUE)))
|
||||||
.addContainerGap())
|
.addContainerGap())
|
||||||
);
|
);
|
||||||
layout.setVerticalGroup(
|
layout.setVerticalGroup(
|
||||||
layout.createParallelGroup(GroupLayout.Alignment.LEADING)
|
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||||
.addGroup(layout.createSequentialGroup()
|
.addGroup(layout.createSequentialGroup()
|
||||||
.addContainerGap()
|
.addContainerGap()
|
||||||
.addComponent(progressHeader)
|
.addComponent(progressHeader, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
|
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||||
.addComponent(progressBar, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
|
.addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||||
.addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||||
);
|
);
|
||||||
|
|
||||||
pack();
|
pack();
|
||||||
@ -176,14 +132,14 @@ public class ProgressWindow extends JFrame {
|
|||||||
cancel();
|
cancel();
|
||||||
}//GEN-LAST:event_closeDialog
|
}//GEN-LAST:event_closeDialog
|
||||||
|
|
||||||
|
@NbBundle.Messages({"Timeline.ProgressWindow.cancel.confdlg.msg=Do you want to cancel timeline creation?",
|
||||||
|
"Timeline.ProgressWindow.cancel.confdlg.detail=Cancel timeline creation?"})
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
if (isVisible()) {
|
if (isVisible()) {
|
||||||
int showConfirmDialog = JOptionPane.showConfirmDialog(ProgressWindow.this,
|
int showConfirmDialog = JOptionPane.showConfirmDialog(ProgressWindow.this,
|
||||||
NbBundle.getMessage(TimeLineTopComponent.class,
|
Bundle.Timeline_ProgressWindow_cancel_confdlg_msg(),
|
||||||
"Timeline.ProgressWindow.cancel.confdlg.msg"),
|
Bundle.Timeline_ProgressWindow_cancel_confdlg_detail(),
|
||||||
NbBundle.getMessage(TimeLineTopComponent.class,
|
|
||||||
"Timeline.ProgressWindow.cancel.confdlg.detail"),
|
|
||||||
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||||
if (showConfirmDialog == JOptionPane.YES_OPTION) {
|
if (showConfirmDialog == JOptionPane.YES_OPTION) {
|
||||||
close();
|
close();
|
||||||
@ -200,18 +156,23 @@ public class ProgressWindow extends JFrame {
|
|||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||||
private JProgressBar progressBar;
|
private javax.swing.JProgressBar progressBar;
|
||||||
private JLabel progressHeader;
|
private javax.swing.JLabel progressHeader;
|
||||||
// End of variables declaration//GEN-END:variables
|
// End of variables declaration//GEN-END:variables
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||||
public void update(ProgressUpdate chunk) {
|
public void update(ProgressUpdate chunk) {
|
||||||
updateHeaderMessage(chunk.getHeaderMessage());
|
progressHeader.setText(chunk.getHeaderMessage());
|
||||||
if (chunk.getTotal() >= 0) {
|
if (chunk.getTotal() >= 0) {
|
||||||
setProgressTotal(chunk.getTotal());
|
progressBar.setIndeterminate(false);
|
||||||
updateProgress(chunk.getProgress(), chunk.getDetailMessage());
|
progressBar.setMaximum(chunk.getTotal());
|
||||||
|
progressBar.setStringPainted(true);
|
||||||
|
progressBar.setValue(chunk.getProgress());
|
||||||
|
progressBar.setString(chunk.getDetailMessage());
|
||||||
} else {
|
} else {
|
||||||
setIndeterminate();
|
progressBar.setIndeterminate(true);
|
||||||
updateProgress(chunk.getDetailMessage());
|
progressBar.setStringPainted(true);
|
||||||
|
progressBar.setString(chunk.getDetailMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.InvalidationListener;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||||
@ -66,14 +67,15 @@ import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
|
|||||||
import org.sleuthkit.autopsy.coreutils.History;
|
import org.sleuthkit.autopsy.coreutils.History;
|
||||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.events.BlackBoardArtifactTagAddedEvent;
|
import org.sleuthkit.autopsy.events.BlackBoardArtifactTagAddedEvent;
|
||||||
import org.sleuthkit.autopsy.events.BlackBoardArtifactTagDeletedEvent;
|
import org.sleuthkit.autopsy.events.BlackBoardArtifactTagDeletedEvent;
|
||||||
import org.sleuthkit.autopsy.events.ContentTagAddedEvent;
|
import org.sleuthkit.autopsy.events.ContentTagAddedEvent;
|
||||||
import org.sleuthkit.autopsy.events.ContentTagDeletedEvent;
|
import org.sleuthkit.autopsy.events.ContentTagDeletedEvent;
|
||||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.events.db.EventsRepository;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import org.sleuthkit.autopsy.timeline.db.EventsRepository;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
|
import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
|
||||||
@ -99,13 +101,11 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
* </li>
|
* </li>
|
||||||
* <ul>
|
* <ul>
|
||||||
*/
|
*/
|
||||||
|
@NbBundle.Messages({"Timeline.confirmation.dialogs.title=Update Timeline database?"})
|
||||||
public class TimeLineController {
|
public class TimeLineController {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(TimeLineController.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(TimeLineController.class.getName());
|
||||||
|
|
||||||
private static final String DO_REPOPULATE_MESSAGE = NbBundle.getMessage(TimeLineController.class,
|
|
||||||
"Timeline.do_repopulate.msg");
|
|
||||||
|
|
||||||
private static final ReadOnlyObjectWrapper<TimeZone> timeZone = new ReadOnlyObjectWrapper<>(TimeZone.getDefault());
|
private static final ReadOnlyObjectWrapper<TimeZone> timeZone = new ReadOnlyObjectWrapper<>(TimeZone.getDefault());
|
||||||
|
|
||||||
public static ZoneId getTimeZoneID() {
|
public static ZoneId getTimeZoneID() {
|
||||||
@ -189,6 +189,9 @@ public class TimeLineController {
|
|||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
private final History<ZoomParams> historyManager = new History<>();
|
private final History<ZoomParams> historyManager = new History<>();
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private final ReadOnlyObjectWrapper<ZoomParams> currentParams = new ReadOnlyObjectWrapper<>();
|
||||||
|
|
||||||
//all members should be access with the intrinsict lock of this object held
|
//all members should be access with the intrinsict lock of this object held
|
||||||
//selected events (ie shown in the result viewer)
|
//selected events (ie shown in the result viewer)
|
||||||
@GuardedBy("this")
|
@GuardedBy("this")
|
||||||
@ -231,16 +234,30 @@ public class TimeLineController {
|
|||||||
private final ReadOnlyBooleanWrapper newEventsFlag = new ReadOnlyBooleanWrapper(false);
|
private final ReadOnlyBooleanWrapper newEventsFlag = new ReadOnlyBooleanWrapper(false);
|
||||||
|
|
||||||
public TimeLineController(Case autoCase) {
|
public TimeLineController(Case autoCase) {
|
||||||
this.autoCase = autoCase; //initalize repository and filteredEvents on creation
|
this.autoCase = autoCase;
|
||||||
eventsRepository = new EventsRepository(autoCase, historyManager.currentState());
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* as the history manager's current state changes, modify the tags
|
||||||
|
* filter to be in sync, and expose that as propery from
|
||||||
|
* TimeLineController. Do we need to do this with datasource or hash hit
|
||||||
|
* filters?
|
||||||
|
*/
|
||||||
|
historyManager.currentState().addListener(new InvalidationListener() {
|
||||||
|
public void invalidated(Observable observable) {
|
||||||
|
ZoomParams historyManagerParams = historyManager.getCurrentState();
|
||||||
|
eventsRepository.syncTagsFilter(historyManagerParams.getFilter().getTagsFilter());
|
||||||
|
currentParams.set(historyManagerParams);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
eventsRepository = new EventsRepository(autoCase, currentParams.getReadOnlyProperty());
|
||||||
filteredEvents = eventsRepository.getEventsModel();
|
filteredEvents = eventsRepository.getEventsModel();
|
||||||
|
|
||||||
InitialZoomState = new ZoomParams(filteredEvents.getSpanningInterval(),
|
InitialZoomState = new ZoomParams(filteredEvents.getSpanningInterval(),
|
||||||
EventTypeZoomLevel.BASE_TYPE,
|
EventTypeZoomLevel.BASE_TYPE,
|
||||||
filteredEvents.filter().get(),
|
filteredEvents.filterProperty().get(),
|
||||||
DescriptionLOD.SHORT);
|
DescriptionLOD.SHORT);
|
||||||
historyManager.advance(InitialZoomState);
|
historyManager.advance(InitialZoomState);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -256,7 +273,7 @@ public class TimeLineController {
|
|||||||
|
|
||||||
public void zoomOutToActivity() {
|
public void zoomOutToActivity() {
|
||||||
Interval boundingEventsInterval = filteredEvents.getBoundingEventsInterval();
|
Interval boundingEventsInterval = filteredEvents.getBoundingEventsInterval();
|
||||||
advance(filteredEvents.getRequestedZoomParamters().get().withTimeRange(boundingEventsInterval));
|
advance(filteredEvents.zoomParametersProperty().get().withTimeRange(boundingEventsInterval));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -287,7 +304,6 @@ public class TimeLineController {
|
|||||||
//TODO: verify this locking is correct? -jm
|
//TODO: verify this locking is correct? -jm
|
||||||
synchronized (eventsRepository) {
|
synchronized (eventsRepository) {
|
||||||
eventsRepository.rebuildRepository(() -> {
|
eventsRepository.rebuildRepository(() -> {
|
||||||
|
|
||||||
synchronized (eventsRepository) {
|
synchronized (eventsRepository) {
|
||||||
eventsRepository.recordLastObjID(lastObjId);
|
eventsRepository.recordLastObjID(lastObjId);
|
||||||
eventsRepository.recordLastArtifactID(lastArtfID);
|
eventsRepository.recordLastArtifactID(lastArtfID);
|
||||||
@ -303,7 +319,7 @@ public class TimeLineController {
|
|||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
//TODO: should this be an event?
|
//TODO: should this be an event?
|
||||||
newEventsFlag.set(false);
|
newEventsFlag.set(false);
|
||||||
historyManager.reset(filteredEvents.getRequestedZoomParamters().get());
|
historyManager.reset(filteredEvents.zoomParametersProperty().get());
|
||||||
TimeLineController.this.showFullRange();
|
TimeLineController.this.showFullRange();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -315,6 +331,28 @@ public class TimeLineController {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since tags might have changed while TimeLine wasn't listening, drop the
|
||||||
|
* tags table and rebuild it by querying for all the tags and inserting them
|
||||||
|
* in to the TimeLine DB.
|
||||||
|
*/
|
||||||
|
void rebuildTagsTable() {
|
||||||
|
LOGGER.log(Level.INFO, "starting to rebuild tags table"); // NON-NLS
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
if (isWindowOpen()) {
|
||||||
|
mainFrame.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
synchronized (eventsRepository) {
|
||||||
|
eventsRepository.rebuildTags(() -> {
|
||||||
|
showWindow();
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
showFullRange();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void showFullRange() {
|
public void showFullRange() {
|
||||||
synchronized (filteredEvents) {
|
synchronized (filteredEvents) {
|
||||||
pushTimeRange(filteredEvents.getSpanningInterval());
|
pushTimeRange(filteredEvents.getSpanningInterval());
|
||||||
@ -337,7 +375,6 @@ public class TimeLineController {
|
|||||||
* show the timeline window and prompt for rebuilding database if necessary.
|
* show the timeline window and prompt for rebuilding database if necessary.
|
||||||
*/
|
*/
|
||||||
synchronized void openTimeLine() {
|
synchronized void openTimeLine() {
|
||||||
|
|
||||||
// listen for case changes (specifically images being added, and case changes).
|
// listen for case changes (specifically images being added, and case changes).
|
||||||
if (Case.isCaseOpen() && !listeningToAutopsy) {
|
if (Case.isCaseOpen() && !listeningToAutopsy) {
|
||||||
IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener);
|
IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener);
|
||||||
@ -347,13 +384,16 @@ public class TimeLineController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
boolean repoRebuilt = false; //has the repo been rebuilt
|
||||||
long timeLineLastObjectId = eventsRepository.getLastObjID();
|
long timeLineLastObjectId = eventsRepository.getLastObjID();
|
||||||
|
|
||||||
boolean repoRebuilt = false;
|
//if the repo is empty rebuild it
|
||||||
if (timeLineLastObjectId == -1) {
|
if (timeLineLastObjectId == -1) {
|
||||||
repoRebuilt = rebuildRepo();
|
repoRebuilt = rebuildRepo();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repoRebuilt == false) {
|
if (repoRebuilt == false) {
|
||||||
|
//if ingest was running uring last rebuild, prompt to rebuild
|
||||||
if (eventsRepository.getWasIngestRunning()) {
|
if (eventsRepository.getWasIngestRunning()) {
|
||||||
if (confirmLastBuiltDuringIngestRebuild()) {
|
if (confirmLastBuiltDuringIngestRebuild()) {
|
||||||
repoRebuilt = rebuildRepo();
|
repoRebuilt = rebuildRepo();
|
||||||
@ -363,6 +403,7 @@ public class TimeLineController {
|
|||||||
|
|
||||||
if (repoRebuilt == false) {
|
if (repoRebuilt == false) {
|
||||||
final SleuthkitCase sleuthkitCase = autoCase.getSleuthkitCase();
|
final SleuthkitCase sleuthkitCase = autoCase.getSleuthkitCase();
|
||||||
|
//if the last artifact and object ids don't match between skc and tldb, prompt to rebuild
|
||||||
if (sleuthkitCase.getLastObjectId() != timeLineLastObjectId
|
if (sleuthkitCase.getLastObjectId() != timeLineLastObjectId
|
||||||
|| getCaseLastArtifactID(sleuthkitCase) != eventsRepository.getLastArtfactID()) {
|
|| getCaseLastArtifactID(sleuthkitCase) != eventsRepository.getLastArtfactID()) {
|
||||||
if (confirmOutOfDateRebuild()) {
|
if (confirmOutOfDateRebuild()) {
|
||||||
@ -372,8 +413,8 @@ public class TimeLineController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (repoRebuilt == false) {
|
if (repoRebuilt == false) {
|
||||||
boolean hasDSInfo = eventsRepository.hasDataSourceInfo();
|
// if the TLDB schema has been upgraded since last time TL ran, prompt for rebuild
|
||||||
if (hasDSInfo == false) {
|
if (eventsRepository.hasNewColumns() == false) {
|
||||||
if (confirmDataSourceIDsMissingRebuild()) {
|
if (confirmDataSourceIDsMissingRebuild()) {
|
||||||
repoRebuilt = rebuildRepo();
|
repoRebuilt = rebuildRepo();
|
||||||
}
|
}
|
||||||
@ -381,12 +422,11 @@ public class TimeLineController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* if the repo was not rebuilt show the UI. If the repo was rebuild
|
* if the repo was not rebuilt at minimum rebuild the tags which may
|
||||||
* it will be displayed as part of that process
|
* have been updated without our knowing it.
|
||||||
*/
|
*/
|
||||||
if (repoRebuilt == false) {
|
if (repoRebuilt == false) {
|
||||||
showWindow();
|
rebuildTagsTable();
|
||||||
showFullRange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
@ -419,13 +459,13 @@ public class TimeLineController {
|
|||||||
*/
|
*/
|
||||||
synchronized public void pushPeriod(ReadablePeriod period) {
|
synchronized public void pushPeriod(ReadablePeriod period) {
|
||||||
synchronized (filteredEvents) {
|
synchronized (filteredEvents) {
|
||||||
final DateTime middleOf = IntervalUtils.middleOf(filteredEvents.timeRange().get());
|
final DateTime middleOf = IntervalUtils.middleOf(filteredEvents.timeRangeProperty().get());
|
||||||
pushTimeRange(IntervalUtils.getIntervalAround(middleOf, period));
|
pushTimeRange(IntervalUtils.getIntervalAround(middleOf, period));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public void pushZoomOutTime() {
|
synchronized public void pushZoomOutTime() {
|
||||||
final Interval timeRange = filteredEvents.timeRange().get();
|
final Interval timeRange = filteredEvents.timeRangeProperty().get();
|
||||||
long toDurationMillis = timeRange.toDurationMillis() / 4;
|
long toDurationMillis = timeRange.toDurationMillis() / 4;
|
||||||
DateTime start = timeRange.getStart().minus(toDurationMillis);
|
DateTime start = timeRange.getStart().minus(toDurationMillis);
|
||||||
DateTime end = timeRange.getEnd().plus(toDurationMillis);
|
DateTime end = timeRange.getEnd().plus(toDurationMillis);
|
||||||
@ -433,7 +473,7 @@ public class TimeLineController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
synchronized public void pushZoomInTime() {
|
synchronized public void pushZoomInTime() {
|
||||||
final Interval timeRange = filteredEvents.timeRange().get();
|
final Interval timeRange = filteredEvents.timeRangeProperty().get();
|
||||||
long toDurationMillis = timeRange.toDurationMillis() / 4;
|
long toDurationMillis = timeRange.toDurationMillis() / 4;
|
||||||
DateTime start = timeRange.getStart().plus(toDurationMillis);
|
DateTime start = timeRange.getStart().plus(toDurationMillis);
|
||||||
DateTime end = timeRange.getEnd().minus(toDurationMillis);
|
DateTime end = timeRange.getEnd().minus(toDurationMillis);
|
||||||
@ -481,7 +521,7 @@ public class TimeLineController {
|
|||||||
if (mainFrame == null) {
|
if (mainFrame == null) {
|
||||||
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
|
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
|
||||||
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
|
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
|
||||||
NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponentAction"));
|
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
|
||||||
if (mainFrame == null) {
|
if (mainFrame == null) {
|
||||||
mainFrame = new TimeLineTopComponent();
|
mainFrame = new TimeLineTopComponent();
|
||||||
}
|
}
|
||||||
@ -495,7 +535,7 @@ public class TimeLineController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) {
|
synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) {
|
||||||
ZoomParams currentZoom = filteredEvents.getRequestedZoomParamters().get();
|
ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
|
||||||
if (currentZoom == null) {
|
if (currentZoom == null) {
|
||||||
advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
|
advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
|
||||||
} else if (currentZoom.hasTypeZoomLevel(typeZoomeLevel) == false) {
|
} else if (currentZoom.hasTypeZoomLevel(typeZoomeLevel) == false) {
|
||||||
@ -505,7 +545,7 @@ public class TimeLineController {
|
|||||||
|
|
||||||
synchronized public void pushTimeRange(Interval timeRange) {
|
synchronized public void pushTimeRange(Interval timeRange) {
|
||||||
// timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
|
// timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
|
||||||
ZoomParams currentZoom = filteredEvents.getRequestedZoomParamters().get();
|
ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
|
||||||
if (currentZoom == null) {
|
if (currentZoom == null) {
|
||||||
advance(InitialZoomState.withTimeRange(timeRange));
|
advance(InitialZoomState.withTimeRange(timeRange));
|
||||||
} else if (currentZoom.hasTimeRange(timeRange) == false) {
|
} else if (currentZoom.hasTimeRange(timeRange) == false) {
|
||||||
@ -513,26 +553,27 @@ public class TimeLineController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NbBundle.Messages({"# {0} - the number of events",
|
||||||
|
"Timeline.pushDescrLOD.confdlg.msg=You are about to show details for {0} events."
|
||||||
|
+ " This might be very slow or even crash Autopsy.\n\nDo you want to continue?"})
|
||||||
synchronized public boolean pushDescrLOD(DescriptionLOD newLOD) {
|
synchronized public boolean pushDescrLOD(DescriptionLOD newLOD) {
|
||||||
Map<EventType, Long> eventCounts = filteredEvents.getEventCounts(filteredEvents.getRequestedZoomParamters().get().getTimeRange());
|
Map<EventType, Long> eventCounts = filteredEvents.getEventCounts(filteredEvents.zoomParametersProperty().get().getTimeRange());
|
||||||
final Long count = eventCounts.values().stream().reduce(0l, Long::sum);
|
final Long count = eventCounts.values().stream().reduce(0l, Long::sum);
|
||||||
|
|
||||||
boolean shouldContinue = true;
|
boolean shouldContinue = true;
|
||||||
if (newLOD == DescriptionLOD.FULL && count > 10_000) {
|
if (newLOD == DescriptionLOD.FULL && count > 10_000) {
|
||||||
|
String format = NumberFormat.getInstance().format(count);
|
||||||
|
|
||||||
int showConfirmDialog = JOptionPane.showConfirmDialog(mainFrame,
|
int showConfirmDialog = JOptionPane.showConfirmDialog(mainFrame,
|
||||||
NbBundle.getMessage(this.getClass(),
|
Bundle.Timeline_pushDescrLOD_confdlg_msg(format),
|
||||||
"Timeline.pushDescrLOD.confdlg.msg",
|
Bundle.Timeline_confirmation_dialogs_title(),
|
||||||
NumberFormat.getInstance().format(count)),
|
|
||||||
NbBundle.getMessage(TimeLineTopComponent.class,
|
|
||||||
"Timeline.pushDescrLOD.confdlg.details"),
|
|
||||||
JOptionPane.YES_NO_OPTION);
|
JOptionPane.YES_NO_OPTION);
|
||||||
|
|
||||||
shouldContinue = (showConfirmDialog == JOptionPane.YES_OPTION);
|
shouldContinue = (showConfirmDialog == JOptionPane.YES_OPTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldContinue) {
|
if (shouldContinue) {
|
||||||
ZoomParams currentZoom = filteredEvents.getRequestedZoomParamters().get();
|
ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
|
||||||
if (currentZoom == null) {
|
if (currentZoom == null) {
|
||||||
advance(InitialZoomState.withDescrLOD(newLOD));
|
advance(InitialZoomState.withDescrLOD(newLOD));
|
||||||
} else if (currentZoom.hasDescrLOD(newLOD) == false) {
|
} else if (currentZoom.hasDescrLOD(newLOD) == false) {
|
||||||
@ -544,7 +585,7 @@ public class TimeLineController {
|
|||||||
|
|
||||||
synchronized public void pushTimeAndType(Interval timeRange, EventTypeZoomLevel typeZoom) {
|
synchronized public void pushTimeAndType(Interval timeRange, EventTypeZoomLevel typeZoom) {
|
||||||
// timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
|
// timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
|
||||||
ZoomParams currentZoom = filteredEvents.getRequestedZoomParamters().get();
|
ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
|
||||||
if (currentZoom == null) {
|
if (currentZoom == null) {
|
||||||
advance(InitialZoomState.withTimeAndType(timeRange, typeZoom));
|
advance(InitialZoomState.withTimeAndType(timeRange, typeZoom));
|
||||||
} else if (currentZoom.hasTimeRange(timeRange) == false && currentZoom.hasTypeZoomLevel(typeZoom) == false) {
|
} else if (currentZoom.hasTimeRange(timeRange) == false && currentZoom.hasTypeZoomLevel(typeZoom) == false) {
|
||||||
@ -557,7 +598,7 @@ public class TimeLineController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
synchronized public void pushFilters(RootFilter filter) {
|
synchronized public void pushFilters(RootFilter filter) {
|
||||||
ZoomParams currentZoom = filteredEvents.getRequestedZoomParamters().get();
|
ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
|
||||||
if (currentZoom == null) {
|
if (currentZoom == null) {
|
||||||
advance(InitialZoomState.withFilter(filter.copyOf()));
|
advance(InitialZoomState.withFilter(filter.copyOf()));
|
||||||
} else if (currentZoom.hasFilter(filter) == false) {
|
} else if (currentZoom.hasFilter(filter) == false) {
|
||||||
@ -565,17 +606,17 @@ public class TimeLineController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public ZoomParams advance() {
|
synchronized public void advance() {
|
||||||
return historyManager.advance();
|
historyManager.advance();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public ZoomParams retreat() {
|
synchronized public void retreat() {
|
||||||
return historyManager.retreat();
|
historyManager.retreat();
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized private void advance(ZoomParams newState) {
|
synchronized private void advance(ZoomParams newState) {
|
||||||
historyManager.advance(newState);
|
historyManager.advance(newState);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectTimeAndType(Interval interval, EventType type) {
|
public void selectTimeAndType(Interval interval, EventType type) {
|
||||||
@ -683,16 +724,36 @@ public class TimeLineController {
|
|||||||
return mainFrame != null && mainFrame.isOpened() && mainFrame.isVisible();
|
return mainFrame != null && mainFrame.isOpened() && mainFrame.isVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* prompt the user to rebuild the db because the db is out of date and
|
||||||
|
* doesn't include things from subsequent ingests ONLY IF THE TIMELINE
|
||||||
|
* WINDOW IS OPEN
|
||||||
|
*
|
||||||
|
* @return true if they agree to rebuild
|
||||||
|
*/
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||||
|
private void confirmOutOfDateRebuildIfWindowOpen() throws MissingResourceException, HeadlessException {
|
||||||
|
if (isWindowOpen()) {
|
||||||
|
if (confirmOutOfDateRebuild()) {
|
||||||
|
rebuildRepo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* prompt the user to rebuild the db because that datasource_ids are missing
|
* prompt the user to rebuild the db because that datasource_ids are missing
|
||||||
* from the database and that the datasource filter will not work
|
* from the database and that the datasource filter will not work
|
||||||
*
|
*
|
||||||
* @return true if they agree to rebuild
|
* @return true if they agree to rebuild
|
||||||
*/
|
*/
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||||
|
@NbBundle.Messages({"datasource.missing.confirmation=The Timeline events database was previously populated with an old version of Autopsy."
|
||||||
|
+ "\nThe data source filter will be unavailable unless you update the events database."
|
||||||
|
+ "\nDo you want to update the events database now?"})
|
||||||
synchronized boolean confirmDataSourceIDsMissingRebuild() {
|
synchronized boolean confirmDataSourceIDsMissingRebuild() {
|
||||||
return JOptionPane.showConfirmDialog(mainFrame,
|
return JOptionPane.showConfirmDialog(mainFrame,
|
||||||
NbBundle.getMessage(TimeLineController.class, "datasource.missing.confirmation"),
|
Bundle.datasource_missing_confirmation(),
|
||||||
"Update Timeline database?",
|
Bundle.Timeline_confirmation_dialogs_title(),
|
||||||
JOptionPane.YES_NO_OPTION,
|
JOptionPane.YES_NO_OPTION,
|
||||||
JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
|
JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
|
||||||
}
|
}
|
||||||
@ -703,11 +764,14 @@ public class TimeLineController {
|
|||||||
*
|
*
|
||||||
* @return true if they agree to rebuild
|
* @return true if they agree to rebuild
|
||||||
*/
|
*/
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||||
|
@NbBundle.Messages({"Timeline.do_repopulate.msg=The Timeline events database was previously populated while ingest was running."
|
||||||
|
+ "\nSome events may not have been populated or may have been populated inaccurately."
|
||||||
|
+ "\nDo you want to repopulate the events database now?"})
|
||||||
synchronized boolean confirmLastBuiltDuringIngestRebuild() {
|
synchronized boolean confirmLastBuiltDuringIngestRebuild() {
|
||||||
return JOptionPane.showConfirmDialog(mainFrame,
|
return JOptionPane.showConfirmDialog(mainFrame,
|
||||||
DO_REPOPULATE_MESSAGE,
|
Bundle.Timeline_do_repopulate_msg(),
|
||||||
NbBundle.getMessage(TimeLineTopComponent.class,
|
Bundle.Timeline_confirmation_dialogs_title(),
|
||||||
"Timeline.showLastPopulatedWhileIngestingConf.confDlg.details"),
|
|
||||||
JOptionPane.YES_NO_OPTION,
|
JOptionPane.YES_NO_OPTION,
|
||||||
JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
|
JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
|
||||||
}
|
}
|
||||||
@ -718,12 +782,12 @@ public class TimeLineController {
|
|||||||
*
|
*
|
||||||
* @return true if they agree to rebuild
|
* @return true if they agree to rebuild
|
||||||
*/
|
*/
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||||
|
@NbBundle.Messages({"Timeline.propChg.confDlg.timelineOOD.msg=The event data is out of date. Would you like to regenerate it?",})
|
||||||
synchronized boolean confirmOutOfDateRebuild() throws MissingResourceException, HeadlessException {
|
synchronized boolean confirmOutOfDateRebuild() throws MissingResourceException, HeadlessException {
|
||||||
return JOptionPane.showConfirmDialog(mainFrame,
|
return JOptionPane.showConfirmDialog(mainFrame,
|
||||||
NbBundle.getMessage(TimeLineController.class,
|
Bundle.Timeline_propChg_confDlg_timelineOOD_msg(),
|
||||||
"Timeline.propChg.confDlg.timelineOOD.msg"),
|
Bundle.Timeline_confirmation_dialogs_title(),
|
||||||
NbBundle.getMessage(TimeLineController.class,
|
|
||||||
"Timeline.propChg.confDlg.timelineOOD.details"),
|
|
||||||
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
|
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -733,12 +797,13 @@ public class TimeLineController {
|
|||||||
*
|
*
|
||||||
* @return true if they want to continue anyways
|
* @return true if they want to continue anyways
|
||||||
*/
|
*/
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||||
|
@NbBundle.Messages({"Timeline.initTimeline.confDlg.genBeforeIngest.msg=You are trying to generate a timeline before ingest has been completed. "
|
||||||
|
+ "The timeline may be incomplete. Do you want to continue?"})
|
||||||
synchronized boolean confirmRebuildDuringIngest() throws MissingResourceException, HeadlessException {
|
synchronized boolean confirmRebuildDuringIngest() throws MissingResourceException, HeadlessException {
|
||||||
return JOptionPane.showConfirmDialog(mainFrame,
|
return JOptionPane.showConfirmDialog(mainFrame,
|
||||||
NbBundle.getMessage(TimeLineController.class,
|
Bundle.Timeline_initTimeline_confDlg_genBeforeIngest_msg(),
|
||||||
"Timeline.initTimeline.confDlg.genBeforeIngest.msg"),
|
Bundle.Timeline_confirmation_dialogs_title(),
|
||||||
NbBundle.getMessage(TimeLineController.class,
|
|
||||||
"Timeline.initTimeline.confDlg.genBeforeIngest.details"),
|
|
||||||
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
|
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -748,16 +813,9 @@ public class TimeLineController {
|
|||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
|
switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
|
||||||
case CONTENT_CHANGED:
|
case CONTENT_CHANGED:
|
||||||
// ((ModuleContentEvent)evt.getOldValue())????
|
|
||||||
//ModuleContentEvent doesn't seem to provide any usefull information...
|
|
||||||
break;
|
|
||||||
case DATA_ADDED:
|
case DATA_ADDED:
|
||||||
// Collection<BlackboardArtifact> artifacts = ((ModuleDataEvent) evt.getOldValue()).getArtifacts();
|
|
||||||
//new artifacts, insert them into db
|
|
||||||
break;
|
break;
|
||||||
case FILE_DONE:
|
case FILE_DONE:
|
||||||
// Long fileID = (Long) evt.getOldValue();
|
|
||||||
//update file (known status) for file with id
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
newEventsFlag.set(true);
|
newEventsFlag.set(true);
|
||||||
});
|
});
|
||||||
@ -774,14 +832,7 @@ public class TimeLineController {
|
|||||||
switch (IngestManager.IngestJobEvent.valueOf(evt.getPropertyName())) {
|
switch (IngestManager.IngestJobEvent.valueOf(evt.getPropertyName())) {
|
||||||
case CANCELLED:
|
case CANCELLED:
|
||||||
case COMPLETED:
|
case COMPLETED:
|
||||||
//if we are doing incremental updates, drop this
|
SwingUtilities.invokeLater(TimeLineController.this::confirmOutOfDateRebuildIfWindowOpen);
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
if (isWindowOpen()) {
|
|
||||||
if (confirmOutOfDateRebuild()) {
|
|
||||||
rebuildRepo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -793,27 +844,27 @@ public class TimeLineController {
|
|||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
switch (Case.Events.valueOf(evt.getPropertyName())) {
|
switch (Case.Events.valueOf(evt.getPropertyName())) {
|
||||||
case BLACKBOARD_ARTIFACT_TAG_ADDED:
|
case BLACKBOARD_ARTIFACT_TAG_ADDED:
|
||||||
filteredEvents.handleTagAdded((BlackBoardArtifactTagAddedEvent) evt);
|
executor.submit(() -> {
|
||||||
|
filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt);
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case BLACKBOARD_ARTIFACT_TAG_DELETED:
|
case BLACKBOARD_ARTIFACT_TAG_DELETED:
|
||||||
filteredEvents.handleTagDeleted((BlackBoardArtifactTagDeletedEvent) evt);
|
executor.submit(() -> {
|
||||||
|
filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt);
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case CONTENT_TAG_ADDED:
|
case CONTENT_TAG_ADDED:
|
||||||
filteredEvents.handleTagAdded((ContentTagAddedEvent) evt);
|
executor.submit(() -> {
|
||||||
|
filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt);
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case CONTENT_TAG_DELETED:
|
case CONTENT_TAG_DELETED:
|
||||||
filteredEvents.handleTagDeleted((ContentTagDeletedEvent) evt);
|
executor.submit(() -> {
|
||||||
|
filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt);
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case DATA_SOURCE_ADDED:
|
case DATA_SOURCE_ADDED:
|
||||||
// Content content = (Content) evt.getNewValue();
|
SwingUtilities.invokeLater(TimeLineController.this::confirmOutOfDateRebuildIfWindowOpen);
|
||||||
//if we are doing incremental updates, drop this
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
|
||||||
if (isWindowOpen()) {
|
|
||||||
if (confirmOutOfDateRebuild()) {
|
|
||||||
rebuildRepo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case CURRENT_CASE:
|
case CURRENT_CASE:
|
||||||
OpenTimelineAction.invalidateController();
|
OpenTimelineAction.invalidateController();
|
||||||
|
@ -24,7 +24,7 @@ package org.sleuthkit.autopsy.timeline;
|
|||||||
* Most implementations should install the relevant listeners in their
|
* Most implementations should install the relevant listeners in their
|
||||||
* {@link #setController} and {@link #setModel} methods
|
* {@link #setController} and {@link #setModel} methods
|
||||||
*/
|
*/
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
|
|
||||||
public interface TimeLineView extends TimeLineUI {
|
public interface TimeLineView extends TimeLineUI {
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import javafx.event.ActionEvent;
|
|||||||
import org.controlsfx.control.action.Action;
|
import org.controlsfx.control.action.Action;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action that resets the filters to their initial/default state.
|
* Action that resets the filters to their initial/default state.
|
||||||
@ -37,12 +37,12 @@ public class ResetFilters extends Action {
|
|||||||
eventsModel = controller.getEventsModel();
|
eventsModel = controller.getEventsModel();
|
||||||
disabledProperty().bind(new BooleanBinding() {
|
disabledProperty().bind(new BooleanBinding() {
|
||||||
{
|
{
|
||||||
bind(eventsModel.getRequestedZoomParamters());
|
bind(eventsModel.zoomParametersProperty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean computeValue() {
|
protected boolean computeValue() {
|
||||||
return eventsModel.getRequestedZoomParamters().getValue().getFilter().equals(eventsModel.getDefaultFilter());
|
return eventsModel.zoomParametersProperty().getValue().getFilter().equals(eventsModel.getDefaultFilter());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setEventHandler((ActionEvent t) -> {
|
setEventHandler((ActionEvent t) -> {
|
||||||
|
@ -82,9 +82,9 @@ public class SaveSnapshot extends Action {
|
|||||||
|
|
||||||
reportMetaData.add(new Pair<>("Case", Case.getCurrentCase().getName())); // NON-NLS
|
reportMetaData.add(new Pair<>("Case", Case.getCurrentCase().getName())); // NON-NLS
|
||||||
|
|
||||||
ZoomParams get = controller.getEventsModel().getRequestedZoomParamters().get();
|
ZoomParams get = controller.getEventsModel().zoomParametersProperty().get();
|
||||||
reportMetaData.add(new Pair<>("Time Range", get.getTimeRange().toString())); // NON-NLS
|
reportMetaData.add(new Pair<>("Time Range", get.getTimeRange().toString())); // NON-NLS
|
||||||
reportMetaData.add(new Pair<>("Description Level of Detail", get.getDescrLOD().getDisplayName())); // NON-NLS
|
reportMetaData.add(new Pair<>("Description Level of Detail", get.getDescriptionLOD().getDisplayName())); // NON-NLS
|
||||||
reportMetaData.add(new Pair<>("Event Type Zoom Level", get.getTypeZoomLevel().getDisplayName())); // NON-NLS
|
reportMetaData.add(new Pair<>("Event Type Zoom Level", get.getTypeZoomLevel().getDisplayName())); // NON-NLS
|
||||||
reportMetaData.add(new Pair<>("Filters", get.getFilter().getHTMLReportString())); // NON-NLS
|
reportMetaData.add(new Pair<>("Filters", get.getFilter().getHTMLReportString())); // NON-NLS
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ import javafx.event.ActionEvent;
|
|||||||
import org.controlsfx.control.action.Action;
|
import org.controlsfx.control.action.Action;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -40,12 +40,12 @@ public class ZoomOut extends Action {
|
|||||||
eventsModel = controller.getEventsModel();
|
eventsModel = controller.getEventsModel();
|
||||||
disabledProperty().bind(new BooleanBinding() {
|
disabledProperty().bind(new BooleanBinding() {
|
||||||
{
|
{
|
||||||
bind(eventsModel.getRequestedZoomParamters());
|
bind(eventsModel.zoomParametersProperty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean computeValue() {
|
protected boolean computeValue() {
|
||||||
return eventsModel.getRequestedZoomParamters().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
|
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setEventHandler((ActionEvent t) -> {
|
setEventHandler((ActionEvent t) -> {
|
||||||
|
@ -16,14 +16,14 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events;
|
package org.sleuthkit.autopsy.timeline.datamodel;
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import org.joda.time.Interval;
|
import org.joda.time.Interval;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
|
import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
||||||
|
|
||||||
@ -138,44 +138,4 @@ public class AggregateEvent {
|
|||||||
|
|
||||||
return new AggregateEvent(IntervalUtils.span(aggEvent1.span, ag2.span), aggEvent1.getType(), idsUnion, hashHitsUnion, taggedUnion, aggEvent1.getDescription(), aggEvent1.lod);
|
return new AggregateEvent(IntervalUtils.span(aggEvent1.span, ag2.span), aggEvent1.getType(), idsUnion, hashHitsUnion, taggedUnion, aggEvent1.getDescription(), aggEvent1.lod);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* get an AggregateEvent the same as this one but with the given eventIDs
|
|
||||||
* removed from the list of tagged events
|
|
||||||
*
|
|
||||||
* @param unTaggedIDs
|
|
||||||
*
|
|
||||||
* @return a new Aggregate event that is the same as this one but with the
|
|
||||||
* given event Ids removed from the list of tagged ids, or, this
|
|
||||||
* AggregateEvent if no event ids would be removed
|
|
||||||
*/
|
|
||||||
public AggregateEvent withTagsRemoved(Set<Long> unTaggedIDs) {
|
|
||||||
Sets.SetView<Long> stillTagged = Sets.difference(tagged, unTaggedIDs);
|
|
||||||
if (stillTagged.size() < tagged.size()) {
|
|
||||||
return new AggregateEvent(span, type, eventIDs, hashHits, stillTagged.immutableCopy(), description, lod);
|
|
||||||
}
|
|
||||||
return this; //no change
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get an AggregateEvent the same as this one but with the given eventIDs
|
|
||||||
* added to the list of tagged events if there are part of this Aggregate
|
|
||||||
*
|
|
||||||
* @param taggedIDs
|
|
||||||
*
|
|
||||||
* @return a new Aggregate event that is the same as this one but with the
|
|
||||||
* given event Ids added to the list of tagged ids, or, this
|
|
||||||
* AggregateEvent if no event ids would be added
|
|
||||||
*/
|
|
||||||
public AggregateEvent withTagsAdded(Set<Long> taggedIDs) {
|
|
||||||
Sets.SetView<Long> taggedIdsInAgg = Sets.intersection(eventIDs, taggedIDs);//events that are in this aggregate and (newly) marked as tagged
|
|
||||||
if (taggedIdsInAgg.size() > 0) {
|
|
||||||
Sets.SetView<Long> notYetIncludedTagged = Sets.difference(taggedIdsInAgg, tagged); // events that are tagged, but not already marked as tagged in this Agg
|
|
||||||
if (notYetIncludedTagged.size() > 0) {
|
|
||||||
return new AggregateEvent(span, type, eventIDs, hashHits, Sets.union(tagged, taggedIdsInAgg).immutableCopy(), description, lod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this; //no change
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -16,7 +16,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events;
|
package org.sleuthkit.autopsy.timeline.datamodel;
|
||||||
|
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -27,6 +27,7 @@ import java.util.logging.Level;
|
|||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.MapChangeListener;
|
import javafx.collections.MapChangeListener;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import org.joda.time.DateTimeZone;
|
import org.joda.time.DateTimeZone;
|
||||||
@ -38,9 +39,11 @@ import org.sleuthkit.autopsy.events.BlackBoardArtifactTagDeletedEvent;
|
|||||||
import org.sleuthkit.autopsy.events.ContentTagAddedEvent;
|
import org.sleuthkit.autopsy.events.ContentTagAddedEvent;
|
||||||
import org.sleuthkit.autopsy.events.ContentTagDeletedEvent;
|
import org.sleuthkit.autopsy.events.ContentTagDeletedEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||||
import org.sleuthkit.autopsy.timeline.events.db.EventsRepository;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
|
import org.sleuthkit.autopsy.timeline.db.EventsRepository;
|
||||||
|
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||||
|
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter;
|
import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter;
|
import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
||||||
@ -48,13 +51,18 @@ import org.sleuthkit.autopsy.timeline.filters.HashHitsFilter;
|
|||||||
import org.sleuthkit.autopsy.timeline.filters.HashSetFilter;
|
import org.sleuthkit.autopsy.timeline.filters.HashSetFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.HideKnownFilter;
|
import org.sleuthkit.autopsy.timeline.filters.HideKnownFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.TagNameFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||||
import org.sleuthkit.datamodel.Content;
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
import org.sleuthkit.datamodel.ContentTag;
|
||||||
|
import org.sleuthkit.datamodel.TagName;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,7 +91,6 @@ public final class FilteredEventsModel {
|
|||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(FilteredEventsModel.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(FilteredEventsModel.class.getName());
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* time range that spans the filtered events
|
* time range that spans the filtered events
|
||||||
*/
|
*/
|
||||||
@ -113,40 +120,25 @@ public final class FilteredEventsModel {
|
|||||||
private final EventsRepository repo;
|
private final EventsRepository repo;
|
||||||
private final Case autoCase;
|
private final Case autoCase;
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the default filter used at startup
|
|
||||||
*/
|
|
||||||
public RootFilter getDefaultFilter() {
|
|
||||||
DataSourcesFilter dataSourcesFilter = new DataSourcesFilter();
|
|
||||||
|
|
||||||
repo.getDatasourcesMap().entrySet().stream().forEach((Map.Entry<Long, String> t) -> {
|
|
||||||
DataSourceFilter dataSourceFilter = new DataSourceFilter(t.getValue(), t.getKey());
|
|
||||||
dataSourceFilter.setSelected(Boolean.TRUE);
|
|
||||||
dataSourcesFilter.addDataSourceFilter(dataSourceFilter);
|
|
||||||
});
|
|
||||||
|
|
||||||
HashHitsFilter hashHitsFilter = new HashHitsFilter();
|
|
||||||
repo.getHashSetMap().entrySet().stream().forEach((Map.Entry<Long, String> t) -> {
|
|
||||||
HashSetFilter hashSourceFilter = new HashSetFilter(t.getValue(), t.getKey());
|
|
||||||
hashSourceFilter.setSelected(Boolean.TRUE);
|
|
||||||
hashHitsFilter.addHashSetFilter(hashSourceFilter);
|
|
||||||
});
|
|
||||||
return new RootFilter(new HideKnownFilter(), hashHitsFilter, new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FilteredEventsModel(EventsRepository repo, ReadOnlyObjectProperty<ZoomParams> currentStateProperty) {
|
public FilteredEventsModel(EventsRepository repo, ReadOnlyObjectProperty<ZoomParams> currentStateProperty) {
|
||||||
this.repo = repo;
|
this.repo = repo;
|
||||||
this.autoCase = repo.getAutoCase();
|
this.autoCase = repo.getAutoCase();
|
||||||
repo.getDatasourcesMap().addListener((MapChangeListener.Change<? extends Long, ? extends String> change) -> {
|
repo.getDatasourcesMap().addListener((MapChangeListener.Change<? extends Long, ? extends String> change) -> {
|
||||||
DataSourceFilter dataSourceFilter = new DataSourceFilter(change.getValueAdded(), change.getKey());
|
DataSourceFilter dataSourceFilter = new DataSourceFilter(change.getValueAdded(), change.getKey());
|
||||||
RootFilter rootFilter = filter().get();
|
RootFilter rootFilter = filterProperty().get();
|
||||||
rootFilter.getDataSourcesFilter().addDataSourceFilter(dataSourceFilter);
|
rootFilter.getDataSourcesFilter().addSubFilter(dataSourceFilter);
|
||||||
requestedFilter.set(rootFilter.copyOf());
|
requestedFilter.set(rootFilter.copyOf());
|
||||||
});
|
});
|
||||||
repo.getHashSetMap().addListener((MapChangeListener.Change<? extends Long, ? extends String> change) -> {
|
repo.getHashSetMap().addListener((MapChangeListener.Change<? extends Long, ? extends String> change) -> {
|
||||||
HashSetFilter hashSetFilter = new HashSetFilter(change.getValueAdded(), change.getKey());
|
HashSetFilter hashSetFilter = new HashSetFilter(change.getValueAdded(), change.getKey());
|
||||||
RootFilter rootFilter = filter().get();
|
RootFilter rootFilter = filterProperty().get();
|
||||||
rootFilter.getHashHitsFilter().addHashSetFilter(hashSetFilter);
|
rootFilter.getHashHitsFilter().addSubFilter(hashSetFilter);
|
||||||
|
requestedFilter.set(rootFilter.copyOf());
|
||||||
|
});
|
||||||
|
repo.getTagNames().addListener((ListChangeListener.Change<? extends TagName> c) -> {
|
||||||
|
RootFilter rootFilter = filterProperty().get();
|
||||||
|
TagsFilter tagsFilter = rootFilter.getTagsFilter();
|
||||||
|
repo.syncTagsFilter(tagsFilter);
|
||||||
requestedFilter.set(rootFilter.copyOf());
|
requestedFilter.set(rootFilter.copyOf());
|
||||||
});
|
});
|
||||||
requestedFilter.set(getDefaultFilter());
|
requestedFilter.set(getDefaultFilter());
|
||||||
@ -156,14 +148,14 @@ public final class FilteredEventsModel {
|
|||||||
|
|
||||||
if (zoomParams != null) {
|
if (zoomParams != null) {
|
||||||
if (zoomParams.getTypeZoomLevel().equals(requestedTypeZoom.get()) == false
|
if (zoomParams.getTypeZoomLevel().equals(requestedTypeZoom.get()) == false
|
||||||
|| zoomParams.getDescrLOD().equals(requestedLOD.get()) == false
|
|| zoomParams.getDescriptionLOD().equals(requestedLOD.get()) == false
|
||||||
|| zoomParams.getFilter().equals(requestedFilter.get()) == false
|
|| zoomParams.getFilter().equals(requestedFilter.get()) == false
|
||||||
|| zoomParams.getTimeRange().equals(requestedTimeRange.get()) == false) {
|
|| zoomParams.getTimeRange().equals(requestedTimeRange.get()) == false) {
|
||||||
|
|
||||||
requestedTypeZoom.set(zoomParams.getTypeZoomLevel());
|
requestedTypeZoom.set(zoomParams.getTypeZoomLevel());
|
||||||
requestedFilter.set(zoomParams.getFilter().copyOf());
|
requestedFilter.set(zoomParams.getFilter().copyOf());
|
||||||
requestedTimeRange.set(zoomParams.getTimeRange());
|
requestedTimeRange.set(zoomParams.getTimeRange());
|
||||||
requestedLOD.set(zoomParams.getDescrLOD());
|
requestedLOD.set(zoomParams.getDescriptionLOD());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -171,12 +163,75 @@ public final class FilteredEventsModel {
|
|||||||
requestedZoomParamters.bind(currentStateProperty);
|
requestedZoomParamters.bind(currentStateProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Interval getBoundingEventsInterval() {
|
synchronized public ReadOnlyObjectProperty<ZoomParams> zoomParametersProperty() {
|
||||||
return repo.getBoundingEventsInterval(getRequestedZoomParamters().get().getTimeRange(), getRequestedZoomParamters().get().getFilter());
|
return requestedZoomParamters.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public ReadOnlyObjectProperty<ZoomParams> getRequestedZoomParamters() {
|
/**
|
||||||
return requestedZoomParamters.getReadOnlyProperty();
|
* @return a read only view of the time range requested via
|
||||||
|
* {@link #requestTimeRange(org.joda.time.Interval)}
|
||||||
|
*/
|
||||||
|
synchronized public ReadOnlyObjectProperty<Interval> timeRangeProperty() {
|
||||||
|
if (requestedTimeRange.get() == null) {
|
||||||
|
requestedTimeRange.set(getSpanningInterval());
|
||||||
|
}
|
||||||
|
return requestedTimeRange.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public ReadOnlyObjectProperty<DescriptionLOD> descriptionLODProperty() {
|
||||||
|
return requestedLOD.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public ReadOnlyObjectProperty<RootFilter> filterProperty() {
|
||||||
|
return requestedFilter.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public ReadOnlyObjectProperty<EventTypeZoomLevel> eventTypeZoomProperty() {
|
||||||
|
return requestedTypeZoom.getReadOnlyProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public DescriptionLOD getDescriptionLOD() {
|
||||||
|
return requestedLOD.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public RootFilter getFilter() {
|
||||||
|
return requestedFilter.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public EventTypeZoomLevel getEventTypeZoom() {
|
||||||
|
return requestedTypeZoom.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the default filter used at startup
|
||||||
|
*/
|
||||||
|
public RootFilter getDefaultFilter() {
|
||||||
|
DataSourcesFilter dataSourcesFilter = new DataSourcesFilter();
|
||||||
|
|
||||||
|
repo.getDatasourcesMap().entrySet().stream().forEach((Map.Entry<Long, String> t) -> {
|
||||||
|
DataSourceFilter dataSourceFilter = new DataSourceFilter(t.getValue(), t.getKey());
|
||||||
|
dataSourceFilter.setSelected(Boolean.TRUE);
|
||||||
|
dataSourcesFilter.addSubFilter(dataSourceFilter);
|
||||||
|
});
|
||||||
|
|
||||||
|
HashHitsFilter hashHitsFilter = new HashHitsFilter();
|
||||||
|
repo.getHashSetMap().entrySet().stream().forEach((Map.Entry<Long, String> t) -> {
|
||||||
|
HashSetFilter hashSetFilter = new HashSetFilter(t.getValue(), t.getKey());
|
||||||
|
hashSetFilter.setSelected(Boolean.TRUE);
|
||||||
|
hashHitsFilter.addSubFilter(hashSetFilter);
|
||||||
|
});
|
||||||
|
|
||||||
|
TagsFilter tagsFilter = new TagsFilter();
|
||||||
|
repo.getTagNames().stream().forEach(t -> {
|
||||||
|
TagNameFilter tagNameFilter = new TagNameFilter(t, autoCase);
|
||||||
|
tagNameFilter.setSelected(Boolean.TRUE);
|
||||||
|
tagsFilter.addSubFilter(tagNameFilter);
|
||||||
|
});
|
||||||
|
return new RootFilter(new HideKnownFilter(), tagsFilter, hashHitsFilter, new TextFilter(), new TypeFilter(RootEventType.getInstance()), dataSourcesFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Interval getBoundingEventsInterval() {
|
||||||
|
return repo.getBoundingEventsInterval(zoomParametersProperty().get().getTimeRange(), zoomParametersProperty().get().getFilter());
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeLineEvent getEventById(Long eventID) {
|
public TimeLineEvent getEventById(Long eventID) {
|
||||||
@ -187,6 +242,18 @@ public final class FilteredEventsModel {
|
|||||||
return repo.getEventsById(eventIDs);
|
return repo.getEventsById(eventIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get a count of tagnames applied to the given event ids as a map from
|
||||||
|
* tagname displayname to count of tag applications
|
||||||
|
*
|
||||||
|
* @param eventIDsWithTags the event ids to get the tag counts map for
|
||||||
|
*
|
||||||
|
* @return a map from tagname displayname to count of applications
|
||||||
|
*/
|
||||||
|
public Map<String, Long> getTagCountsByTagName(Set<Long> eventIDsWithTags) {
|
||||||
|
return repo.getTagCountsByTagName(eventIDsWithTags);
|
||||||
|
}
|
||||||
|
|
||||||
public Set<Long> getEventIDs(Interval timeRange, Filter filter) {
|
public Set<Long> getEventIDs(Interval timeRange, Filter filter) {
|
||||||
final Interval overlap;
|
final Interval overlap;
|
||||||
final RootFilter intersect;
|
final RootFilter intersect;
|
||||||
@ -219,25 +286,6 @@ public final class FilteredEventsModel {
|
|||||||
return repo.countEvents(new ZoomParams(timeRange, typeZoom, filter, null));
|
return repo.countEvents(new ZoomParams(timeRange, typeZoom, filter, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a read only view of the time range requested via
|
|
||||||
* {@link #requestTimeRange(org.joda.time.Interval)}
|
|
||||||
*/
|
|
||||||
synchronized public ReadOnlyObjectProperty<Interval> timeRange() {
|
|
||||||
if (requestedTimeRange.get() == null) {
|
|
||||||
requestedTimeRange.set(getSpanningInterval());
|
|
||||||
}
|
|
||||||
return requestedTimeRange.getReadOnlyProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public ReadOnlyObjectProperty<DescriptionLOD> descriptionLOD() {
|
|
||||||
return requestedLOD.getReadOnlyProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public ReadOnlyObjectProperty<RootFilter> filter() {
|
|
||||||
return requestedFilter.getReadOnlyProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the smallest interval spanning all the events from the
|
* @return the smallest interval spanning all the events from the
|
||||||
* repository, ignoring any filters or requested ranges
|
* repository, ignoring any filters or requested ranges
|
||||||
@ -303,58 +351,52 @@ public final class FilteredEventsModel {
|
|||||||
return repo.getAggregatedEvents(params);
|
return repo.getAggregatedEvents(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public ReadOnlyObjectProperty<EventTypeZoomLevel> eventTypeZoom() {
|
synchronized public boolean handleContentTagAdded(ContentTagAddedEvent evt) {
|
||||||
return requestedTypeZoom.getReadOnlyProperty();
|
ContentTag contentTag = evt.getTag();
|
||||||
|
Content content = contentTag.getContent();
|
||||||
|
Set<Long> updatedEventIDs = repo.addTag(content.getId(), null, contentTag);
|
||||||
|
return postTagsUpdated(updatedEventIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public EventTypeZoomLevel getEventTypeZoom() {
|
synchronized public boolean handleArtifactTagAdded(BlackBoardArtifactTagAddedEvent evt) {
|
||||||
return requestedTypeZoom.get();
|
BlackboardArtifactTag artifactTag = evt.getTag();
|
||||||
|
BlackboardArtifact artifact = artifactTag.getArtifact();
|
||||||
|
Set<Long> updatedEventIDs = repo.addTag(artifact.getObjectID(), artifact.getArtifactID(), artifactTag);;
|
||||||
|
return postTagsUpdated(updatedEventIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public DescriptionLOD getDescriptionLOD() {
|
synchronized public boolean handleContentTagDeleted(ContentTagDeletedEvent evt) {
|
||||||
return requestedLOD.get();
|
ContentTag contentTag = evt.getTag();
|
||||||
}
|
Content content = contentTag.getContent();
|
||||||
|
|
||||||
synchronized public void handleTagAdded(BlackBoardArtifactTagAddedEvent e) {
|
|
||||||
BlackboardArtifact artifact = e.getTag().getArtifact();
|
|
||||||
Set<Long> updatedEventIDs = repo.markEventsTagged(artifact.getObjectID(), artifact.getArtifactID(), true);
|
|
||||||
if (!updatedEventIDs.isEmpty()) {
|
|
||||||
eventbus.post(new EventsTaggedEvent(updatedEventIDs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public void handleTagDeleted(BlackBoardArtifactTagDeletedEvent e) {
|
|
||||||
BlackboardArtifact artifact = e.getTag().getArtifact();
|
|
||||||
try {
|
|
||||||
boolean tagged = autoCase.getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact).isEmpty() == false;
|
|
||||||
Set<Long> updatedEventIDs = repo.markEventsTagged(artifact.getObjectID(), artifact.getArtifactID(), tagged);
|
|
||||||
if (!updatedEventIDs.isEmpty()) {
|
|
||||||
eventbus.post(new EventsUnTaggedEvent(updatedEventIDs));
|
|
||||||
}
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
LOGGER.log(Level.SEVERE, "unable to determine tagged status of attribute.", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public void handleTagAdded(ContentTagAddedEvent e) {
|
|
||||||
Content content = e.getTag().getContent();
|
|
||||||
Set<Long> updatedEventIDs = repo.markEventsTagged(content.getId(), null, true);
|
|
||||||
if (!updatedEventIDs.isEmpty()) {
|
|
||||||
eventbus.post(new EventsTaggedEvent(updatedEventIDs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public void handleTagDeleted(ContentTagDeletedEvent e) {
|
|
||||||
Content content = e.getTag().getContent();
|
|
||||||
try {
|
try {
|
||||||
boolean tagged = autoCase.getServices().getTagsManager().getContentTagsByContent(content).isEmpty() == false;
|
boolean tagged = autoCase.getServices().getTagsManager().getContentTagsByContent(content).isEmpty() == false;
|
||||||
Set<Long> updatedEventIDs = repo.markEventsTagged(content.getId(), null, tagged);
|
Set<Long> updatedEventIDs = repo.deleteTag(content.getId(), null, contentTag, tagged);
|
||||||
if (!updatedEventIDs.isEmpty()) {
|
return postTagsUpdated(updatedEventIDs);
|
||||||
eventbus.post(new EventsUnTaggedEvent(updatedEventIDs));
|
|
||||||
}
|
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "unable to determine tagged status of content.", ex);
|
LOGGER.log(Level.SEVERE, "unable to determine tagged status of content.", ex);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public boolean handleArtifactTagDeleted(BlackBoardArtifactTagDeletedEvent evt) {
|
||||||
|
BlackboardArtifactTag artifactTag = evt.getTag();
|
||||||
|
BlackboardArtifact artifact = artifactTag.getArtifact();
|
||||||
|
try {
|
||||||
|
boolean tagged = autoCase.getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact).isEmpty() == false;
|
||||||
|
Set<Long> updatedEventIDs = repo.deleteTag(artifact.getObjectID(), artifact.getArtifactID(), artifactTag, tagged);
|
||||||
|
return postTagsUpdated(updatedEventIDs);
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "unable to determine tagged status of artifact.", ex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean postTagsUpdated(Set<Long> updatedEventIDs) {
|
||||||
|
boolean tagsUpdated = !updatedEventIDs.isEmpty();
|
||||||
|
if (tagsUpdated) {
|
||||||
|
eventbus.post(new TagsUpdatedEvent(updatedEventIDs));
|
||||||
|
}
|
||||||
|
return tagsUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public void registerForEvents(Object o) {
|
synchronized public void registerForEvents(Object o) {
|
||||||
@ -364,4 +406,9 @@ public final class FilteredEventsModel {
|
|||||||
synchronized public void unRegisterForEvents(Object o) {
|
synchronized public void unRegisterForEvents(Object o) {
|
||||||
eventbus.unregister(0);
|
eventbus.unregister(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
eventbus.post(new RefreshRequestedEvent());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -16,47 +16,49 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events;
|
package org.sleuthkit.autopsy.timeline.datamodel;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
|
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
||||||
import org.sleuthkit.datamodel.TskData;
|
import org.sleuthkit.datamodel.TskData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* A single event.
|
||||||
*/
|
*/
|
||||||
|
@Immutable
|
||||||
public class TimeLineEvent {
|
public class TimeLineEvent {
|
||||||
|
|
||||||
private final Long eventID;
|
private final long eventID;
|
||||||
|
private final long fileID;
|
||||||
private final Long fileID;
|
|
||||||
|
|
||||||
private final Long time;
|
|
||||||
|
|
||||||
private final Long artifactID;
|
private final Long artifactID;
|
||||||
|
private final long dataSourceID;
|
||||||
|
|
||||||
|
private final long time;
|
||||||
private final EventType subType;
|
private final EventType subType;
|
||||||
|
private final ImmutableMap<DescriptionLOD, String> descriptions;
|
||||||
private final String fullDescription, medDescription, shortDescription;
|
|
||||||
|
|
||||||
private final TskData.FileKnown known;
|
private final TskData.FileKnown known;
|
||||||
|
|
||||||
private final boolean hashHit;
|
private final boolean hashHit;
|
||||||
private final boolean tagged;
|
private final boolean tagged;
|
||||||
|
|
||||||
public TimeLineEvent(Long eventID, Long objID, @Nullable Long artifactID, Long time, EventType type, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known, boolean hashHit, boolean tagged) {
|
public TimeLineEvent(long eventID, long dataSourceID, long objID, @Nullable Long artifactID, long time, EventType type, String fullDescription, String medDescription, String shortDescription, TskData.FileKnown known, boolean hashHit, boolean tagged) {
|
||||||
this.eventID = eventID;
|
this.eventID = eventID;
|
||||||
this.fileID = objID;
|
this.fileID = objID;
|
||||||
this.artifactID = artifactID;
|
this.artifactID = artifactID == 0 ? null : artifactID;
|
||||||
this.time = time;
|
this.time = time;
|
||||||
this.subType = type;
|
this.subType = type;
|
||||||
|
descriptions = ImmutableMap.<DescriptionLOD, String>of(
|
||||||
|
DescriptionLOD.FULL, fullDescription,
|
||||||
|
DescriptionLOD.MEDIUM, medDescription,
|
||||||
|
DescriptionLOD.SHORT, shortDescription);
|
||||||
|
|
||||||
this.fullDescription = fullDescription;
|
|
||||||
this.medDescription = medDescription;
|
|
||||||
this.shortDescription = shortDescription;
|
|
||||||
this.known = known;
|
this.known = known;
|
||||||
this.hashHit = hashHit;
|
this.hashHit = hashHit;
|
||||||
this.tagged = tagged;
|
this.tagged = tagged;
|
||||||
|
this.dataSourceID = dataSourceID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTagged() {
|
public boolean isTagged() {
|
||||||
@ -72,18 +74,18 @@ public class TimeLineEvent {
|
|||||||
return artifactID;
|
return artifactID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getEventID() {
|
public long getEventID() {
|
||||||
return eventID;
|
return eventID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getFileID() {
|
public long getFileID() {
|
||||||
return fileID;
|
return fileID;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the time in seconds from unix epoch
|
* @return the time in seconds from unix epoch
|
||||||
*/
|
*/
|
||||||
public Long getTime() {
|
public long getTime() {
|
||||||
return time;
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,18 +94,26 @@ public class TimeLineEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getFullDescription() {
|
public String getFullDescription() {
|
||||||
return fullDescription;
|
return getDescription(DescriptionLOD.FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMedDescription() {
|
public String getMedDescription() {
|
||||||
return medDescription;
|
return getDescription(DescriptionLOD.MEDIUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getShortDescription() {
|
public String getShortDescription() {
|
||||||
return shortDescription;
|
return getDescription(DescriptionLOD.SHORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TskData.FileKnown getKnown() {
|
public TskData.FileKnown getKnown() {
|
||||||
return known;
|
return known;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDescription(DescriptionLOD lod) {
|
||||||
|
return descriptions.get(lod);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getDataSourceID() {
|
||||||
|
return dataSourceID;
|
||||||
|
}
|
||||||
}
|
}
|
@ -16,7 +16,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events.type;
|
package org.sleuthkit.autopsy.timeline.datamodel.eventtype;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
@ -16,7 +16,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events.type;
|
package org.sleuthkit.autopsy.timeline.datamodel.eventtype;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -70,8 +70,9 @@ public enum BaseTypes implements EventType {
|
|||||||
|
|
||||||
private final String iconBase;
|
private final String iconBase;
|
||||||
|
|
||||||
private Image image;
|
private final Image image;
|
||||||
|
|
||||||
|
@Override
|
||||||
public Image getFXImage() {
|
public Image getFXImage() {
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
@ -16,7 +16,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events.type;
|
package org.sleuthkit.autopsy.timeline.datamodel.eventtype;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
@ -16,7 +16,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events.type;
|
package org.sleuthkit.autopsy.timeline.datamodel.eventtype;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
@ -16,7 +16,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events.type;
|
package org.sleuthkit.autopsy.timeline.datamodel.eventtype;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
@ -16,7 +16,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events.type;
|
package org.sleuthkit.autopsy.timeline.datamodel.eventtype;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
@ -16,7 +16,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events.type;
|
package org.sleuthkit.autopsy.timeline.datamodel.eventtype;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
@ -16,7 +16,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events.db;
|
package org.sleuthkit.autopsy.timeline.db;
|
||||||
|
|
||||||
import com.google.common.collect.HashMultimap;
|
import com.google.common.collect.HashMultimap;
|
||||||
import com.google.common.collect.SetMultimap;
|
import com.google.common.collect.SetMultimap;
|
||||||
@ -32,6 +32,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -44,37 +45,30 @@ import java.util.concurrent.locks.Lock;
|
|||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.joda.time.DateTimeZone;
|
import org.joda.time.DateTimeZone;
|
||||||
import org.joda.time.Interval;
|
import org.joda.time.Interval;
|
||||||
import org.joda.time.Period;
|
import org.joda.time.Period;
|
||||||
import org.openide.util.Exceptions;
|
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.events.TimeLineEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.BaseTypes;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.BaseTypes;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||||
|
import static org.sleuthkit.autopsy.timeline.db.SQLHelper.useHashHitTablesHelper;
|
||||||
|
import static org.sleuthkit.autopsy.timeline.db.SQLHelper.useTagTablesHelper;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
||||||
import static org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD.FULL;
|
|
||||||
import static org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD.MEDIUM;
|
|
||||||
import static org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD.SHORT;
|
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.TimeUnits;
|
|
||||||
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.DAYS;
|
|
||||||
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.HOURS;
|
|
||||||
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.MINUTES;
|
|
||||||
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.MONTHS;
|
|
||||||
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.SECONDS;
|
|
||||||
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.YEARS;
|
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
|
import org.sleuthkit.datamodel.Tag;
|
||||||
import org.sleuthkit.datamodel.TskData;
|
import org.sleuthkit.datamodel.TskData;
|
||||||
import org.sqlite.SQLiteJDBCLoader;
|
import org.sqlite.SQLiteJDBCLoader;
|
||||||
|
|
||||||
@ -88,7 +82,7 @@ import org.sqlite.SQLiteJDBCLoader;
|
|||||||
public class EventDB {
|
public class EventDB {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
*
|
||||||
* enum to represent keys stored in db_info table
|
* enum to represent keys stored in db_info table
|
||||||
*/
|
*/
|
||||||
private enum DBInfoKey {
|
private enum DBInfoKey {
|
||||||
@ -150,17 +144,21 @@ public class EventDB {
|
|||||||
private PreparedStatement getMaxTimeStmt;
|
private PreparedStatement getMaxTimeStmt;
|
||||||
private PreparedStatement getMinTimeStmt;
|
private PreparedStatement getMinTimeStmt;
|
||||||
private PreparedStatement getDataSourceIDsStmt;
|
private PreparedStatement getDataSourceIDsStmt;
|
||||||
|
private PreparedStatement getHashSetNamesStmt;
|
||||||
private PreparedStatement insertRowStmt;
|
private PreparedStatement insertRowStmt;
|
||||||
private PreparedStatement recordDBInfoStmt;
|
private PreparedStatement recordDBInfoStmt;
|
||||||
private PreparedStatement insertHashSetStmt;
|
private PreparedStatement insertHashSetStmt;
|
||||||
private PreparedStatement insertHashHitStmt;
|
private PreparedStatement insertHashHitStmt;
|
||||||
|
private PreparedStatement insertTagStmt;
|
||||||
|
private PreparedStatement deleteTagStmt;
|
||||||
private PreparedStatement selectHashSetStmt;
|
private PreparedStatement selectHashSetStmt;
|
||||||
private PreparedStatement countAllEventsStmt;
|
private PreparedStatement countAllEventsStmt;
|
||||||
private PreparedStatement dropEventsTableStmt;
|
private PreparedStatement dropEventsTableStmt;
|
||||||
private PreparedStatement dropHashSetHitsTableStmt;
|
private PreparedStatement dropHashSetHitsTableStmt;
|
||||||
private PreparedStatement dropHashSetsTableStmt;
|
private PreparedStatement dropHashSetsTableStmt;
|
||||||
|
private PreparedStatement dropTagsTableStmt;
|
||||||
private PreparedStatement dropDBInfoTableStmt;
|
private PreparedStatement dropDBInfoTableStmt;
|
||||||
private PreparedStatement selectEventsFromOBjectAndArtifactStmt;
|
private PreparedStatement selectEventIDsFromOBjectAndArtifactStmt;
|
||||||
|
|
||||||
private final Set<PreparedStatement> preparedStatements = new HashSet<>();
|
private final Set<PreparedStatement> preparedStatements = new HashSet<>();
|
||||||
|
|
||||||
@ -196,9 +194,9 @@ public class EventDB {
|
|||||||
public Interval getSpanningInterval(Collection<Long> eventIDs) {
|
public Interval getSpanningInterval(Collection<Long> eventIDs) {
|
||||||
DBLock.lock();
|
DBLock.lock();
|
||||||
try (Statement stmt = con.createStatement();
|
try (Statement stmt = con.createStatement();
|
||||||
ResultSet rs = stmt.executeQuery("select Min(time), Max(time) from events where event_id in (" + StringUtils.join(eventIDs, ", ") + ")");) { // NON-NLS
|
ResultSet rs = stmt.executeQuery("SELECT Min(time), Max(time) FROM events WHERE event_id IN (" + StringUtils.join(eventIDs, ", ") + ")");) { // NON-NLS
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
return new Interval(rs.getLong("Min(time)"), rs.getLong("Max(time)") + 1, DateTimeZone.UTC); // NON-NLS
|
return new Interval(rs.getLong("Min(time)") * 1000, (rs.getLong("Max(time)") + 1) * 1000, DateTimeZone.UTC); // NON-NLS
|
||||||
}
|
}
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "Error executing get spanning interval query.", ex); // NON-NLS
|
LOGGER.log(Level.SEVERE, "Error executing get spanning interval query.", ex); // NON-NLS
|
||||||
@ -212,11 +210,11 @@ public class EventDB {
|
|||||||
return new EventTransaction();
|
return new EventTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
void commitTransaction(EventTransaction tr, Boolean notify) {
|
void commitTransaction(EventTransaction tr) {
|
||||||
if (tr.isClosed()) {
|
if (tr.isClosed()) {
|
||||||
throw new IllegalArgumentException("can't close already closed transaction"); // NON-NLS
|
throw new IllegalArgumentException("can't close already closed transaction"); // NON-NLS
|
||||||
}
|
}
|
||||||
tr.commit(notify);
|
tr.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -248,7 +246,7 @@ public class EventDB {
|
|||||||
*/
|
*/
|
||||||
Map<EventType, Long> countEventsByType(ZoomParams params) {
|
Map<EventType, Long> countEventsByType(ZoomParams params) {
|
||||||
if (params.getTimeRange() != null) {
|
if (params.getTimeRange() != null) {
|
||||||
return countEvents(params.getTimeRange().getStartMillis() / 1000,
|
return countEventsByType(params.getTimeRange().getStartMillis() / 1000,
|
||||||
params.getTimeRange().getEndMillis() / 1000,
|
params.getTimeRange().getEndMillis() / 1000,
|
||||||
params.getFilter(), params.getTypeZoomLevel());
|
params.getFilter(), params.getTypeZoomLevel());
|
||||||
} else {
|
} else {
|
||||||
@ -256,6 +254,33 @@ public class EventDB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get a count of tagnames applied to the given event ids as a map from
|
||||||
|
* tagname displayname to count of tag applications
|
||||||
|
*
|
||||||
|
* @param eventIDsWithTags the event ids to get the tag counts map for
|
||||||
|
*
|
||||||
|
* @return a map from tagname displayname to count of applications
|
||||||
|
*/
|
||||||
|
Map<String, Long> getTagCountsByTagName(Set<Long> eventIDsWithTags) {
|
||||||
|
HashMap<String, Long> counts = new HashMap<>();
|
||||||
|
DBLock.lock();
|
||||||
|
try (Statement createStatement = con.createStatement();
|
||||||
|
ResultSet rs = createStatement.executeQuery("SELECT tag_name_display_name, COUNT(DISTINCT tag_id) AS count FROM tags"
|
||||||
|
+ " WHERE event_id IN (" + StringUtils.join(eventIDsWithTags, ", ") + ")"
|
||||||
|
+ " GROUP BY tag_name_id"
|
||||||
|
+ " ORDER BY tag_name_display_name");) {
|
||||||
|
while (rs.next()) {
|
||||||
|
counts.put(rs.getString("tag_name_display_name"), rs.getLong("count"));
|
||||||
|
}
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Failed to get tag counts by tag name.", ex);
|
||||||
|
} finally {
|
||||||
|
DBLock.unlock();
|
||||||
|
}
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* drop the tables from this database and recreate them in order to start
|
* drop the tables from this database and recreate them in order to start
|
||||||
* over.
|
* over.
|
||||||
@ -266,10 +291,27 @@ public class EventDB {
|
|||||||
dropEventsTableStmt.executeUpdate();
|
dropEventsTableStmt.executeUpdate();
|
||||||
dropHashSetHitsTableStmt.executeUpdate();
|
dropHashSetHitsTableStmt.executeUpdate();
|
||||||
dropHashSetsTableStmt.executeUpdate();
|
dropHashSetsTableStmt.executeUpdate();
|
||||||
|
dropTagsTableStmt.executeUpdate();
|
||||||
dropDBInfoTableStmt.executeUpdate();
|
dropDBInfoTableStmt.executeUpdate();
|
||||||
initializeDB();;
|
initializeDB();
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "could not drop old tables table", ex); // NON-NLS
|
LOGGER.log(Level.SEVERE, "could not drop old tables", ex); // NON-NLS
|
||||||
|
} finally {
|
||||||
|
DBLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* drop only the tags table and rebuild it incase the tags have changed
|
||||||
|
* while TL was not listening,
|
||||||
|
*/
|
||||||
|
void reInitializeTags() {
|
||||||
|
DBLock.lock();
|
||||||
|
try {
|
||||||
|
dropTagsTableStmt.executeUpdate();
|
||||||
|
initializeTagsTable();
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "could not drop old tags table", ex); // NON-NLS
|
||||||
} finally {
|
} finally {
|
||||||
DBLock.unlock();
|
DBLock.unlock();
|
||||||
}
|
}
|
||||||
@ -281,8 +323,8 @@ public class EventDB {
|
|||||||
final String sqlWhere = SQLHelper.getSQLWhere(filter);
|
final String sqlWhere = SQLHelper.getSQLWhere(filter);
|
||||||
DBLock.lock();
|
DBLock.lock();
|
||||||
try (Statement stmt = con.createStatement(); //can't use prepared statement because of complex where clause
|
try (Statement stmt = con.createStatement(); //can't use prepared statement because of complex where clause
|
||||||
ResultSet rs = stmt.executeQuery(" select (select Max(time) from events" + useHashHitTablesHelper(filter) + " where time <=" + start + " and " + sqlWhere + ") as start,"
|
ResultSet rs = stmt.executeQuery(" SELECT (SELECT Max(time) FROM events " + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time <=" + start + " AND " + sqlWhere + ") AS start,"
|
||||||
+ "(select Min(time) from from events" + useHashHitTablesHelper(filter) + " where time >= " + end + " and " + sqlWhere + ") as end")) { // NON-NLS
|
+ "(SELECT Min(time) FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time >= " + end + " AND " + sqlWhere + ") AS end")) { // NON-NLS
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
|
|
||||||
long start2 = rs.getLong("start"); // NON-NLS
|
long start2 = rs.getLong("start"); // NON-NLS
|
||||||
@ -332,10 +374,9 @@ public class EventDB {
|
|||||||
Set<Long> resultIDs = new HashSet<>();
|
Set<Long> resultIDs = new HashSet<>();
|
||||||
|
|
||||||
DBLock.lock();
|
DBLock.lock();
|
||||||
final String query = "select event_id from from events" + useHashHitTablesHelper(filter) + " where time >= " + startTime + " and time <" + endTime + " and " + SQLHelper.getSQLWhere(filter); // NON-NLS
|
final String query = "SELECT events.event_id AS event_id FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time >= " + startTime + " AND time <" + endTime + " AND " + SQLHelper.getSQLWhere(filter); // NON-NLS
|
||||||
try (Statement stmt = con.createStatement();
|
try (Statement stmt = con.createStatement();
|
||||||
ResultSet rs = stmt.executeQuery(query)) {
|
ResultSet rs = stmt.executeQuery(query)) {
|
||||||
|
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
resultIDs.add(rs.getLong("event_id"));
|
resultIDs.add(rs.getLong("event_id"));
|
||||||
}
|
}
|
||||||
@ -357,11 +398,11 @@ public class EventDB {
|
|||||||
return getDBInfo(DBInfoKey.LAST_OBJECT_ID, -1);
|
return getDBInfo(DBInfoKey.LAST_OBJECT_ID, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasNewColumns() {
|
/**
|
||||||
/*
|
|
||||||
* this relies on the fact that no tskObj has ID 0 but 0 is the default
|
* this relies on the fact that no tskObj has ID 0 but 0 is the default
|
||||||
* value for the datasource_id column in the events table.
|
* value for the datasource_id column in the events table.
|
||||||
*/
|
*/
|
||||||
|
boolean hasNewColumns() {
|
||||||
return hasHashHitColumn() && hasDataSourceIDColumn() && hasTaggedColumn()
|
return hasHashHitColumn() && hasDataSourceIDColumn() && hasTaggedColumn()
|
||||||
&& (getDataSourceIDs().isEmpty() == false);
|
&& (getDataSourceIDs().isEmpty() == false);
|
||||||
}
|
}
|
||||||
@ -388,7 +429,7 @@ public class EventDB {
|
|||||||
Map<Long, String> getHashSetNames() {
|
Map<Long, String> getHashSetNames() {
|
||||||
Map<Long, String> hashSets = new HashMap<>();
|
Map<Long, String> hashSets = new HashMap<>();
|
||||||
DBLock.lock();
|
DBLock.lock();
|
||||||
try (ResultSet rs = con.createStatement().executeQuery("select * from hash_sets")) {
|
try (ResultSet rs = getHashSetNamesStmt.executeQuery();) {
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
long hashSetID = rs.getLong("hash_set_id");
|
long hashSetID = rs.getLong("hash_set_id");
|
||||||
String hashSetName = rs.getString("hash_set_name");
|
String hashSetName = rs.getString("hash_set_name");
|
||||||
@ -443,7 +484,6 @@ public class EventDB {
|
|||||||
/**
|
/**
|
||||||
* create the table and indices if they don't already exist
|
* create the table and indices if they don't already exist
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @return the number of rows in the table , count > 0 indicating an
|
* @return the number of rows in the table , count > 0 indicating an
|
||||||
* existing table
|
* existing table
|
||||||
*/
|
*/
|
||||||
@ -488,8 +528,9 @@ public class EventDB {
|
|||||||
+ " full_description TEXT, " // NON-NLS
|
+ " full_description TEXT, " // NON-NLS
|
||||||
+ " med_description TEXT, " // NON-NLS
|
+ " med_description TEXT, " // NON-NLS
|
||||||
+ " short_description TEXT, " // NON-NLS
|
+ " short_description TEXT, " // NON-NLS
|
||||||
+ " known_state INTEGER,"
|
+ " known_state INTEGER," //boolean // NON-NLS
|
||||||
+ " hash_hit INTEGER)"; //boolean // NON-NLS
|
+ " hash_hit INTEGER," //boolean // NON-NLS
|
||||||
|
+ " tagged INTEGER)"; //boolean // NON-NLS
|
||||||
stmt.execute(sql);
|
stmt.execute(sql);
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "problem creating database table", ex); // NON-NLS
|
LOGGER.log(Level.SEVERE, "problem creating database table", ex); // NON-NLS
|
||||||
@ -500,7 +541,6 @@ public class EventDB {
|
|||||||
String sql = "ALTER TABLE events ADD COLUMN datasource_id INTEGER"; // NON-NLS
|
String sql = "ALTER TABLE events ADD COLUMN datasource_id INTEGER"; // NON-NLS
|
||||||
stmt.execute(sql);
|
stmt.execute(sql);
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
|
|
||||||
LOGGER.log(Level.SEVERE, "problem upgrading events table", ex); // NON-NLS
|
LOGGER.log(Level.SEVERE, "problem upgrading events table", ex); // NON-NLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -509,7 +549,6 @@ public class EventDB {
|
|||||||
String sql = "ALTER TABLE events ADD COLUMN tagged INTEGER"; // NON-NLS
|
String sql = "ALTER TABLE events ADD COLUMN tagged INTEGER"; // NON-NLS
|
||||||
stmt.execute(sql);
|
stmt.execute(sql);
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
|
|
||||||
LOGGER.log(Level.SEVERE, "problem upgrading events table", ex); // NON-NLS
|
LOGGER.log(Level.SEVERE, "problem upgrading events table", ex); // NON-NLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -542,8 +581,11 @@ public class EventDB {
|
|||||||
LOGGER.log(Level.SEVERE, "problem creating hash_set_hits table", ex);
|
LOGGER.log(Level.SEVERE, "problem creating hash_set_hits table", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeTagsTable();
|
||||||
|
|
||||||
createIndex("events", Arrays.asList("file_id"));
|
createIndex("events", Arrays.asList("file_id"));
|
||||||
createIndex("events", Arrays.asList("artifact_id"));
|
createIndex("events", Arrays.asList("artifact_id"));
|
||||||
|
createIndex("events", Arrays.asList("time"));
|
||||||
createIndex("events", Arrays.asList("sub_type", "time"));
|
createIndex("events", Arrays.asList("sub_type", "time"));
|
||||||
createIndex("events", Arrays.asList("base_type", "time"));
|
createIndex("events", Arrays.asList("base_type", "time"));
|
||||||
createIndex("events", Arrays.asList("known_state"));
|
createIndex("events", Arrays.asList("known_state"));
|
||||||
@ -552,7 +594,7 @@ public class EventDB {
|
|||||||
insertRowStmt = prepareStatement(
|
insertRowStmt = prepareStatement(
|
||||||
"INSERT INTO events (datasource_id,file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description, known_state, hash_hit, tagged) " // NON-NLS
|
"INSERT INTO events (datasource_id,file_id ,artifact_id, time, sub_type, base_type, full_description, med_description, short_description, known_state, hash_hit, tagged) " // NON-NLS
|
||||||
+ "VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"); // NON-NLS
|
+ "VALUES (?,?,?,?,?,?,?,?,?,?,?,?)"); // NON-NLS
|
||||||
|
getHashSetNamesStmt = prepareStatement("SELECT hash_set_id, hash_set_name FROM hash_sets"); // NON-NLS
|
||||||
getDataSourceIDsStmt = prepareStatement("SELECT DISTINCT datasource_id FROM events"); // NON-NLS
|
getDataSourceIDsStmt = prepareStatement("SELECT DISTINCT datasource_id FROM events"); // NON-NLS
|
||||||
getMaxTimeStmt = prepareStatement("SELECT Max(time) AS max FROM events"); // NON-NLS
|
getMaxTimeStmt = prepareStatement("SELECT Max(time) AS max FROM events"); // NON-NLS
|
||||||
getMinTimeStmt = prepareStatement("SELECT Min(time) AS min FROM events"); // NON-NLS
|
getMinTimeStmt = prepareStatement("SELECT Min(time) AS min FROM events"); // NON-NLS
|
||||||
@ -562,21 +604,41 @@ public class EventDB {
|
|||||||
insertHashSetStmt = prepareStatement("INSERT OR IGNORE INTO hash_sets (hash_set_name) values (?)");
|
insertHashSetStmt = prepareStatement("INSERT OR IGNORE INTO hash_sets (hash_set_name) values (?)");
|
||||||
selectHashSetStmt = prepareStatement("SELECT hash_set_id FROM hash_sets WHERE hash_set_name = ?");
|
selectHashSetStmt = prepareStatement("SELECT hash_set_id FROM hash_sets WHERE hash_set_name = ?");
|
||||||
insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, event_id) values (?,?)");
|
insertHashHitStmt = prepareStatement("INSERT OR IGNORE INTO hash_set_hits (hash_set_id, event_id) values (?,?)");
|
||||||
|
insertTagStmt = prepareStatement("INSERT OR IGNORE INTO tags (tag_id, tag_name_id,tag_name_display_name, event_id) values (?,?,?,?)");
|
||||||
|
deleteTagStmt = prepareStatement("DELETE FROM tags WHERE tag_id = ?");
|
||||||
countAllEventsStmt = prepareStatement("SELECT count(*) AS count FROM events");
|
countAllEventsStmt = prepareStatement("SELECT count(*) AS count FROM events");
|
||||||
dropEventsTableStmt = prepareStatement("DROP TABLE IF EXISTS events");
|
dropEventsTableStmt = prepareStatement("DROP TABLE IF EXISTS events");
|
||||||
dropHashSetHitsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_set_hits");
|
dropHashSetHitsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_set_hits");
|
||||||
dropHashSetsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_sets");
|
dropHashSetsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_sets");
|
||||||
|
dropTagsTableStmt = prepareStatement("DROP TABLE IF EXISTS tags");
|
||||||
dropDBInfoTableStmt = prepareStatement("DROP TABLE IF EXISTS db_ino");
|
dropDBInfoTableStmt = prepareStatement("DROP TABLE IF EXISTS db_ino");
|
||||||
selectEventsFromOBjectAndArtifactStmt = prepareStatement("SELECT event_id FROM events WHERE file_id == ? AND artifact_id IS ?");
|
selectEventIDsFromOBjectAndArtifactStmt = prepareStatement("SELECT event_id FROM events WHERE file_id == ? AND artifact_id IS ?");
|
||||||
} catch (SQLException sQLException) {
|
} catch (SQLException sQLException) {
|
||||||
LOGGER.log(Level.SEVERE, "failed to prepareStatment", sQLException); // NON-NLS
|
LOGGER.log(Level.SEVERE, "failed to prepareStatment", sQLException); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
DBLock.unlock();
|
DBLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create the tags table if it doesn't already exist. This is broken out as
|
||||||
|
* a separate method so it can be used by {@link #reInitializeTags() }
|
||||||
|
*/
|
||||||
|
private void initializeTagsTable() {
|
||||||
|
try (Statement stmt = con.createStatement()) {
|
||||||
|
String sql = "CREATE TABLE IF NOT EXISTS tags "
|
||||||
|
+ "(tag_id INTEGER NOT NULL,"
|
||||||
|
+ " tag_name_id INTEGER NOT NULL, "
|
||||||
|
+ " tag_name_display_name TEXT NOT NULL, "
|
||||||
|
+ " event_id INTEGER REFERENCES events(event_id) NOT NULL, "
|
||||||
|
+ " PRIMARY KEY (event_id, tag_name_id))";
|
||||||
|
stmt.execute(sql);
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "problem creating tags table", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param tableName the value of tableName
|
* @param tableName the value of tableName
|
||||||
@ -628,11 +690,11 @@ public class EventDB {
|
|||||||
|
|
||||||
void insertEvent(long time, EventType type, long datasourceID, long objID,
|
void insertEvent(long time, EventType type, long datasourceID, long objID,
|
||||||
Long artifactID, String fullDescription, String medDescription,
|
Long artifactID, String fullDescription, String medDescription,
|
||||||
String shortDescription, TskData.FileKnown known, Set<String> hashSets, boolean tagged) {
|
String shortDescription, TskData.FileKnown known, Set<String> hashSets, List<? extends Tag> tags) {
|
||||||
|
|
||||||
EventTransaction transaction = beginTransaction();
|
EventTransaction transaction = beginTransaction();
|
||||||
insertEvent(time, type, datasourceID, objID, artifactID, fullDescription, medDescription, shortDescription, known, hashSets, tagged, transaction);
|
insertEvent(time, type, datasourceID, objID, artifactID, fullDescription, medDescription, shortDescription, known, hashSets, tags, transaction);
|
||||||
commitTransaction(transaction, true);
|
commitTransaction(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -644,17 +706,13 @@ public class EventDB {
|
|||||||
void insertEvent(long time, EventType type, long datasourceID, long objID,
|
void insertEvent(long time, EventType type, long datasourceID, long objID,
|
||||||
Long artifactID, String fullDescription, String medDescription,
|
Long artifactID, String fullDescription, String medDescription,
|
||||||
String shortDescription, TskData.FileKnown known, Set<String> hashSetNames,
|
String shortDescription, TskData.FileKnown known, Set<String> hashSetNames,
|
||||||
boolean tagged,
|
List<? extends Tag> tags, EventTransaction transaction) {
|
||||||
EventTransaction transaction) {
|
|
||||||
|
|
||||||
if (transaction.isClosed()) {
|
if (transaction.isClosed()) {
|
||||||
throw new IllegalArgumentException("can't update database with closed transaction"); // NON-NLS
|
throw new IllegalArgumentException("can't update database with closed transaction"); // NON-NLS
|
||||||
}
|
}
|
||||||
int typeNum;
|
int typeNum = RootEventType.allTypes.indexOf(type);
|
||||||
int superTypeNum;
|
int superTypeNum = type.getSuperType().ordinal();
|
||||||
|
|
||||||
typeNum = RootEventType.allTypes.indexOf(type);
|
|
||||||
superTypeNum = type.getSuperType().ordinal();
|
|
||||||
|
|
||||||
DBLock.lock();
|
DBLock.lock();
|
||||||
try {
|
try {
|
||||||
@ -684,7 +742,7 @@ public class EventDB {
|
|||||||
insertRowStmt.setByte(10, known == null ? TskData.FileKnown.UNKNOWN.getFileKnownValue() : known.getFileKnownValue());
|
insertRowStmt.setByte(10, known == null ? TskData.FileKnown.UNKNOWN.getFileKnownValue() : known.getFileKnownValue());
|
||||||
|
|
||||||
insertRowStmt.setInt(11, hashSetNames.isEmpty() ? 0 : 1);
|
insertRowStmt.setInt(11, hashSetNames.isEmpty() ? 0 : 1);
|
||||||
insertRowStmt.setInt(12, tagged ? 1 : 0);
|
insertRowStmt.setInt(12, tags.isEmpty() ? 0 : 1);
|
||||||
|
|
||||||
insertRowStmt.executeUpdate();
|
insertRowStmt.executeUpdate();
|
||||||
|
|
||||||
@ -697,7 +755,7 @@ public class EventDB {
|
|||||||
insertHashSetStmt.setString(1, name);
|
insertHashSetStmt.setString(1, name);
|
||||||
insertHashSetStmt.executeUpdate();
|
insertHashSetStmt.executeUpdate();
|
||||||
|
|
||||||
//TODO: use nested select to get hash_set_id rather than seperate statement/query
|
//TODO: use nested select to get hash_set_id rather than seperate statement/query ?
|
||||||
//"select hash_set_id from hash_sets where hash_set_name = ?"
|
//"select hash_set_id from hash_sets where hash_set_name = ?"
|
||||||
selectHashSetStmt.setString(1, name);
|
selectHashSetStmt.setString(1, name);
|
||||||
try (ResultSet rs = selectHashSetStmt.executeQuery()) {
|
try (ResultSet rs = selectHashSetStmt.executeQuery()) {
|
||||||
@ -711,9 +769,13 @@ public class EventDB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (Tag tag : tags) {
|
||||||
|
//could this be one insert? is there a performance win?
|
||||||
|
insertTag(tag, eventID);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "failed to insert event", ex); // NON-NLS
|
LOGGER.log(Level.SEVERE, "failed to insert event", ex); // NON-NLS
|
||||||
@ -722,33 +784,133 @@ public class EventDB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<Long> markEventsTagged(long objectID, Long artifactID, boolean tagged) {
|
/**
|
||||||
HashSet<Long> eventIDs = new HashSet<>();
|
* mark any events with the given object and artifact ids as tagged, and
|
||||||
|
* record the tag it self.
|
||||||
|
*
|
||||||
|
* @param objectID the obj_id that this tag applies to, the id of the
|
||||||
|
* content that the artifact is derived from for artifact
|
||||||
|
* tags
|
||||||
|
* @param artifactID the artifact_id that this tag applies to, or null if
|
||||||
|
* this is a content tag
|
||||||
|
* @param tag the tag that should be inserted
|
||||||
|
*
|
||||||
|
* @return the event ids that match the object/artifact pair
|
||||||
|
*/
|
||||||
|
Set<Long> addTag(long objectID, @Nullable Long artifactID, Tag tag) {
|
||||||
DBLock.lock();
|
DBLock.lock();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
selectEventsFromOBjectAndArtifactStmt.clearParameters();
|
Set<Long> eventIDs = markEventsTagged(objectID, artifactID, true);
|
||||||
selectEventsFromOBjectAndArtifactStmt.setLong(1, objectID);
|
for (Long eventID : eventIDs) {
|
||||||
if (Objects.isNull(artifactID)) {
|
insertTag(tag, eventID);
|
||||||
selectEventsFromOBjectAndArtifactStmt.setNull(2, Types.NULL);
|
|
||||||
} else {
|
|
||||||
selectEventsFromOBjectAndArtifactStmt.setLong(2, artifactID);
|
|
||||||
}
|
}
|
||||||
try (ResultSet executeQuery = selectEventsFromOBjectAndArtifactStmt.executeQuery();) {
|
return eventIDs;
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "failed to add tag to event", ex); // NON-NLS
|
||||||
|
} finally {
|
||||||
|
DBLock.unlock();
|
||||||
|
}
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* insert this tag into the db
|
||||||
|
* <p>
|
||||||
|
* NOTE: does not lock the db, must be called form inside a
|
||||||
|
* DBLock.lock/unlock pair
|
||||||
|
*
|
||||||
|
* @param tag the tag to insert
|
||||||
|
* @param eventID the event id that this tag is applied to.
|
||||||
|
*
|
||||||
|
* @throws SQLException if there was a problem executing insert
|
||||||
|
*/
|
||||||
|
private void insertTag(Tag tag, long eventID) throws SQLException {
|
||||||
|
|
||||||
|
//"INSERT OR IGNORE INTO tags (tag_id, tag_name_id,tag_name_display_name, event_id) values (?,?,?,?)"
|
||||||
|
insertTagStmt.clearParameters();
|
||||||
|
insertTagStmt.setLong(1, tag.getId());
|
||||||
|
insertTagStmt.setLong(2, tag.getName().getId());
|
||||||
|
insertTagStmt.setString(3, tag.getName().getDisplayName());
|
||||||
|
insertTagStmt.setLong(4, eventID);
|
||||||
|
insertTagStmt.executeUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mark any events with the given object and artifact ids as tagged, and
|
||||||
|
* record the tag it self.
|
||||||
|
*
|
||||||
|
* @param objectID the obj_id that this tag applies to, the id of the
|
||||||
|
* content that the artifact is derived from for artifact
|
||||||
|
* tags
|
||||||
|
* @param artifactID the artifact_id that this tag applies to, or null if
|
||||||
|
* this is a content tag
|
||||||
|
* @param tag the tag that should be deleted
|
||||||
|
* @param stillTagged true if there are other tags still applied to this
|
||||||
|
* event in autopsy
|
||||||
|
*
|
||||||
|
* @return the event ids that match the object/artifact pair
|
||||||
|
*/
|
||||||
|
Set<Long> deleteTag(long objectID, @Nullable Long artifactID, Tag tag, boolean stillTagged) {
|
||||||
|
DBLock.lock();
|
||||||
|
try {
|
||||||
|
//"DELETE FROM tags WHERE tag_id = ?
|
||||||
|
deleteTagStmt.clearParameters();
|
||||||
|
deleteTagStmt.setLong(1, tag.getId());
|
||||||
|
deleteTagStmt.executeUpdate();
|
||||||
|
|
||||||
|
Set<Long> eventIDs = markEventsTagged(objectID, artifactID, stillTagged);
|
||||||
|
return eventIDs;
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "failed to add tag to event", ex); // NON-NLS
|
||||||
|
} finally {
|
||||||
|
DBLock.unlock();
|
||||||
|
}
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mark any events with the given object and artifact ids as tagged, and
|
||||||
|
* record the tag it self.
|
||||||
|
* <p>
|
||||||
|
* NOTE: does not lock the db, must be called form inside a
|
||||||
|
* DBLock.lock/unlock pair
|
||||||
|
*
|
||||||
|
* @param objectID the obj_id that this tag applies to, the id of the
|
||||||
|
* content that the artifact is derived from for artifact
|
||||||
|
* tags
|
||||||
|
* @param artifactID the artifact_id that this tag applies to, or null if
|
||||||
|
* this is a content tag
|
||||||
|
* @param tagged true to mark the matching events tagged, false to mark
|
||||||
|
* them as untagged
|
||||||
|
*
|
||||||
|
* @return the event ids that match the object/artifact pair
|
||||||
|
*
|
||||||
|
* @throws SQLException if there is an error marking the events as
|
||||||
|
* (un)taggedS
|
||||||
|
*/
|
||||||
|
private Set<Long> markEventsTagged(long objectID, @Nullable Long artifactID, boolean tagged) throws SQLException {
|
||||||
|
//first select the matching event ids
|
||||||
|
selectEventIDsFromOBjectAndArtifactStmt.clearParameters();
|
||||||
|
selectEventIDsFromOBjectAndArtifactStmt.setLong(1, objectID);
|
||||||
|
if (Objects.isNull(artifactID)) {
|
||||||
|
selectEventIDsFromOBjectAndArtifactStmt.setNull(2, Types.NULL);
|
||||||
|
} else {
|
||||||
|
selectEventIDsFromOBjectAndArtifactStmt.setLong(2, artifactID);
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<Long> eventIDs = new HashSet<>();
|
||||||
|
try (ResultSet executeQuery = selectEventIDsFromOBjectAndArtifactStmt.executeQuery();) {
|
||||||
while (executeQuery.next()) {
|
while (executeQuery.next()) {
|
||||||
eventIDs.add(executeQuery.getLong("event_id"));
|
eventIDs.add(executeQuery.getLong("event_id"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//then update tagged state for all event with selected ids
|
||||||
try (Statement updateStatement = con.createStatement();) {
|
try (Statement updateStatement = con.createStatement();) {
|
||||||
updateStatement.executeUpdate("UPDATE events SET tagged = " + (tagged ? 1 : 0)
|
updateStatement.executeUpdate("UPDATE events SET tagged = " + (tagged ? 1 : 0)
|
||||||
+ " WHERE event_id IN (" + StringUtils.join(eventIDs, ",") + ")");
|
+ " WHERE event_id IN (" + StringUtils.join(eventIDs, ",") + ")");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (SQLException ex) {
|
|
||||||
LOGGER.log(Level.SEVERE, "failed to mark events as " + (tagged ? "" : "(un)") + tagged, ex); // NON-NLS
|
|
||||||
} finally {
|
|
||||||
DBLock.unlock();
|
|
||||||
}
|
|
||||||
return eventIDs;
|
return eventIDs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -768,19 +930,6 @@ public class EventDB {
|
|||||||
trans.rollback();
|
trans.rollback();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean tableExists() {
|
|
||||||
//TODO: use prepared statement - jm
|
|
||||||
try (Statement createStatement = con.createStatement();
|
|
||||||
ResultSet executeQuery = createStatement.executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='events'")) { // NON-NLS
|
|
||||||
if (executeQuery.getString("name").equals("events") == false) { // NON-NLS
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (SQLException ex) {
|
|
||||||
Exceptions.printStackTrace(ex);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeStatements() throws SQLException {
|
private void closeStatements() throws SQLException {
|
||||||
for (PreparedStatement pStmt : preparedStatements) {
|
for (PreparedStatement pStmt : preparedStatements) {
|
||||||
pStmt.close();
|
pStmt.close();
|
||||||
@ -789,7 +938,7 @@ public class EventDB {
|
|||||||
|
|
||||||
private void configureDB() throws SQLException {
|
private void configureDB() throws SQLException {
|
||||||
DBLock.lock();
|
DBLock.lock();
|
||||||
//this should match Sleuthkit db setupt
|
//this should match Sleuthkit db setup
|
||||||
try (Statement statement = con.createStatement()) {
|
try (Statement statement = con.createStatement()) {
|
||||||
//reduce i/o operations, we have no OS crash recovery anyway
|
//reduce i/o operations, we have no OS crash recovery anyway
|
||||||
statement.execute("PRAGMA synchronous = OFF;"); // NON-NLS
|
statement.execute("PRAGMA synchronous = OFF;"); // NON-NLS
|
||||||
@ -813,11 +962,13 @@ public class EventDB {
|
|||||||
SQLiteJDBCLoader.getVersion(), SQLiteJDBCLoader.isNativeMode()
|
SQLiteJDBCLoader.getVersion(), SQLiteJDBCLoader.isNativeMode()
|
||||||
? "native" : "pure-java")); // NON-NLS
|
? "native" : "pure-java")); // NON-NLS
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Failed to determine if sqlite-jdbc is loaded in native or pure-java mode.", exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TimeLineEvent constructTimeLineEvent(ResultSet rs) throws SQLException {
|
private TimeLineEvent constructTimeLineEvent(ResultSet rs) throws SQLException {
|
||||||
return new TimeLineEvent(rs.getLong("event_id"),
|
return new TimeLineEvent(rs.getLong("event_id"),
|
||||||
|
rs.getLong("datasource_id"),
|
||||||
rs.getLong("file_id"),
|
rs.getLong("file_id"),
|
||||||
rs.getLong("artifact_id"),
|
rs.getLong("artifact_id"),
|
||||||
rs.getLong("time"), RootEventType.allTypes.get(rs.getInt("sub_type")),
|
rs.getLong("time"), RootEventType.allTypes.get(rs.getInt("sub_type")),
|
||||||
@ -833,7 +984,6 @@ public class EventDB {
|
|||||||
* count all the events with the given options and return a map organizing
|
* count all the events with the given options and return a map organizing
|
||||||
* the counts in a hierarchy from date > eventtype> count
|
* the counts in a hierarchy from date > eventtype> count
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param startTime events before this time will be excluded (seconds from
|
* @param startTime events before this time will be excluded (seconds from
|
||||||
* unix epoch)
|
* unix epoch)
|
||||||
* @param endTime events at or after this time will be excluded (seconds
|
* @param endTime events at or after this time will be excluded (seconds
|
||||||
@ -846,7 +996,7 @@ public class EventDB {
|
|||||||
* @return a map organizing the counts in a hierarchy from date > eventtype>
|
* @return a map organizing the counts in a hierarchy from date > eventtype>
|
||||||
* count
|
* count
|
||||||
*/
|
*/
|
||||||
private Map<EventType, Long> countEvents(Long startTime, Long endTime, RootFilter filter, EventTypeZoomLevel zoomLevel) {
|
private Map<EventType, Long> countEventsByType(Long startTime, Long endTime, RootFilter filter, EventTypeZoomLevel zoomLevel) {
|
||||||
if (Objects.equals(startTime, endTime)) {
|
if (Objects.equals(startTime, endTime)) {
|
||||||
endTime++;
|
endTime++;
|
||||||
}
|
}
|
||||||
@ -857,9 +1007,9 @@ public class EventDB {
|
|||||||
final boolean useSubTypes = (zoomLevel == EventTypeZoomLevel.SUB_TYPE);
|
final boolean useSubTypes = (zoomLevel == EventTypeZoomLevel.SUB_TYPE);
|
||||||
|
|
||||||
//get some info about the range of dates requested
|
//get some info about the range of dates requested
|
||||||
final String queryString = "select count(*), " + useSubTypeHelper(useSubTypes)
|
final String queryString = "SELECT count(DISTINCT events.event_id) AS count, " + typeColumnHelper(useSubTypes)
|
||||||
+ " from events" + useHashHitTablesHelper(filter) + " where time >= " + startTime + " and time < " + endTime + " and " + SQLHelper.getSQLWhere(filter) // NON-NLS
|
+ " FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time >= " + startTime + " AND time < " + endTime + " AND " + SQLHelper.getSQLWhere(filter) // NON-NLS
|
||||||
+ " GROUP BY " + useSubTypeHelper(useSubTypes); // NON-NLS
|
+ " GROUP BY " + typeColumnHelper(useSubTypes); // NON-NLS
|
||||||
|
|
||||||
DBLock.lock();
|
DBLock.lock();
|
||||||
try (Statement stmt = con.createStatement();
|
try (Statement stmt = con.createStatement();
|
||||||
@ -869,7 +1019,7 @@ public class EventDB {
|
|||||||
? RootEventType.allTypes.get(rs.getInt("sub_type"))
|
? RootEventType.allTypes.get(rs.getInt("sub_type"))
|
||||||
: BaseTypes.values()[rs.getInt("base_type")];
|
: BaseTypes.values()[rs.getInt("base_type")];
|
||||||
|
|
||||||
typeMap.put(type, rs.getLong("count(*)")); // NON-NLS
|
typeMap.put(type, rs.getLong("count")); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@ -880,119 +1030,140 @@ public class EventDB {
|
|||||||
return typeMap;
|
return typeMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<AggregateEvent> getAggregatedEvents(ZoomParams params) {
|
|
||||||
return getAggregatedEvents(params.getTimeRange(), params.getFilter(), params.getTypeZoomLevel(), params.getDescrLOD());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* //TODO: update javadoc //TODO: split this into helper methods
|
* get a list of {@link AggregateEvent}s, clustered according to the given
|
||||||
*
|
* zoom paramaters.
|
||||||
* get a list of {@link AggregateEvent}s.
|
|
||||||
*
|
|
||||||
* General algorithm is as follows:
|
|
||||||
*
|
|
||||||
* 1)get all aggregate events, via one db query. 2) sort them into a map
|
|
||||||
* from (type, description)-> aggevent 3) for each key in map, merge the
|
|
||||||
* events and accumulate them in a list to return
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param timeRange the Interval within in which all returned aggregate
|
|
||||||
* events will be.
|
|
||||||
* @param filter only events that pass the filter will be included in
|
|
||||||
* aggregates events returned
|
|
||||||
* @param zoomLevel only events of this level will be included
|
|
||||||
* @param lod description level of detail to use when grouping events
|
|
||||||
*
|
*
|
||||||
|
* @param params the zoom params that determine the zooming, filtering and
|
||||||
|
* clustering.
|
||||||
*
|
*
|
||||||
* @return a list of aggregate events within the given timerange, that pass
|
* @return a list of aggregate events within the given timerange, that pass
|
||||||
* the supplied filter, aggregated according to the given event type
|
* the supplied filter, aggregated according to the given event type
|
||||||
* and description zoom levels
|
* and description zoom levels
|
||||||
*/
|
*/
|
||||||
private List<AggregateEvent> getAggregatedEvents(Interval timeRange, RootFilter filter, EventTypeZoomLevel zoomLevel, DescriptionLOD lod) {
|
List<AggregateEvent> getAggregatedEvents(ZoomParams params) {
|
||||||
String descriptionColumn = getDescriptionColumn(lod);
|
//unpack params
|
||||||
final boolean useSubTypes = (zoomLevel.equals(EventTypeZoomLevel.SUB_TYPE));
|
Interval timeRange = params.getTimeRange();
|
||||||
|
RootFilter filter = params.getFilter();
|
||||||
|
DescriptionLOD descriptionLOD = params.getDescriptionLOD();
|
||||||
|
EventTypeZoomLevel typeZoomLevel = params.getTypeZoomLevel();
|
||||||
|
|
||||||
//get some info about the time range requested
|
//ensure length of querried interval is not 0
|
||||||
RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(timeRange);
|
long start = timeRange.getStartMillis() / 1000;
|
||||||
//use 'rounded out' range
|
long end = timeRange.getEndMillis() / 1000;
|
||||||
long start = timeRange.getStartMillis() / 1000;//.getLowerBound();
|
if (start == end) {
|
||||||
long end = timeRange.getEndMillis() / 1000;//Millis();//rangeInfo.getUpperBound();
|
|
||||||
if (Objects.equals(start, end)) {
|
|
||||||
end++;
|
end++;
|
||||||
}
|
}
|
||||||
|
//get some info about the time range requested
|
||||||
|
RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(timeRange);
|
||||||
|
|
||||||
//get a sqlite srtftime format string
|
//build dynamic parts of query
|
||||||
String strfTimeFormat = getStrfTimeFormat(rangeInfo.getPeriodSize());
|
String strfTimeFormat = SQLHelper.getStrfTimeFormat(rangeInfo);
|
||||||
|
String descriptionColumn = SQLHelper.getDescriptionColumn(descriptionLOD);
|
||||||
|
final boolean useSubTypes = typeZoomLevel.equals(EventTypeZoomLevel.SUB_TYPE);
|
||||||
|
String timeZone = TimeLineController.getTimeZone().get().equals(TimeZone.getDefault()) ? ", 'localtime'" : ""; // NON-NLS
|
||||||
|
String typeColumn = typeColumnHelper(useSubTypes);
|
||||||
|
|
||||||
//effectively map from type to (map from description to events)
|
//compose query string
|
||||||
Map<EventType, SetMultimap< String, AggregateEvent>> typeMap = new HashMap<>();
|
String query = "SELECT strftime('" + strfTimeFormat + "',time , 'unixepoch'" + timeZone + ") AS interval," // NON-NLS
|
||||||
|
+ " group_concat(events.event_id) as event_ids, min(time), max(time), " + typeColumn + ", " + descriptionColumn // NON-NLS
|
||||||
|
+ "\n FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) // NON-NLS
|
||||||
|
+ "\n WHERE time >= " + start + " AND time < " + end + " AND " + SQLHelper.getSQLWhere(filter) // NON-NLS
|
||||||
|
+ "\n GROUP BY interval, " + typeColumn + " , " + descriptionColumn // NON-NLS
|
||||||
|
+ "\n ORDER BY min(time)"; // NON-NLS
|
||||||
|
|
||||||
//get all agregate events in this time unit
|
// perform query and map results to AggregateEvent objects
|
||||||
|
List<AggregateEvent> events = new ArrayList<>();
|
||||||
DBLock.lock();
|
DBLock.lock();
|
||||||
String query = "select strftime('" + strfTimeFormat + "',time , 'unixepoch'" + (TimeLineController.getTimeZone().get().equals(TimeZone.getDefault()) ? ", 'localtime'" : "") + ") as interval,"
|
|
||||||
+ " group_concat(events.event_id) as event_ids, Min(time), Max(time), " + descriptionColumn + ", " + useSubTypeHelper(useSubTypes)
|
try (Statement createStatement = con.createStatement();
|
||||||
+ " from events" + useHashHitTablesHelper(filter) + " where " + "time >= " + start + " and time < " + end + " and " + SQLHelper.getSQLWhere(filter) // NON-NLS
|
ResultSet rs = createStatement.executeQuery(query)) {
|
||||||
+ " group by interval, " + useSubTypeHelper(useSubTypes) + " , " + descriptionColumn // NON-NLS
|
|
||||||
+ " order by Min(time)"; // NON-NLS
|
|
||||||
// scoop up requested events in groups organized by interval, type, and desription
|
|
||||||
try (ResultSet rs = con.createStatement().executeQuery(query);) {
|
|
||||||
while (rs.next()) {
|
while (rs.next()) {
|
||||||
Interval interval = new Interval(rs.getLong("Min(time)") * 1000, rs.getLong("Max(time)") * 1000, TimeLineController.getJodaTimeZone());
|
events.add(aggregateEventHelper(rs, useSubTypes, descriptionLOD, filter.getTagsFilter()));
|
||||||
String eventIDS = rs.getString("event_ids");
|
|
||||||
EventType type = useSubTypes ? RootEventType.allTypes.get(rs.getInt("sub_type")) : BaseTypes.values()[rs.getInt("base_type")];
|
|
||||||
|
|
||||||
HashSet<Long> hashHits = new HashSet<>();
|
|
||||||
HashSet<Long> tagged = new HashSet<>();
|
|
||||||
try (Statement st2 = con.createStatement();
|
|
||||||
ResultSet hashQueryResults = st2.executeQuery("select event_id , tagged, hash_hit from events where event_id in (" + eventIDS + ")");) {
|
|
||||||
while (hashQueryResults.next()) {
|
|
||||||
long eventID = hashQueryResults.getLong("event_id");
|
|
||||||
if (hashQueryResults.getInt("tagged") != 0) {
|
|
||||||
tagged.add(eventID);
|
|
||||||
}
|
}
|
||||||
if (hashQueryResults.getInt("hash_hit") != 0) {
|
|
||||||
hashHits.add(eventID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AggregateEvent aggregateEvent = new AggregateEvent(
|
|
||||||
interval, // NON-NLS
|
|
||||||
type,
|
|
||||||
Stream.of(eventIDS.split(",")).map(Long::valueOf).collect(Collectors.toSet()), // NON-NLS
|
|
||||||
hashHits,
|
|
||||||
tagged,
|
|
||||||
rs.getString(descriptionColumn),
|
|
||||||
lod);
|
|
||||||
|
|
||||||
//put events in map from type/descrition -> event
|
|
||||||
SetMultimap<String, AggregateEvent> descrMap = typeMap.get(type);
|
|
||||||
if (descrMap == null) {
|
|
||||||
descrMap = HashMultimap.<String, AggregateEvent>create();
|
|
||||||
typeMap.put(type, descrMap);
|
|
||||||
}
|
|
||||||
descrMap.put(aggregateEvent.getDescription(), aggregateEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
Exceptions.printStackTrace(ex);
|
LOGGER.log(Level.SEVERE, "Failed to get aggregate events with query: " + query, ex); // NON-NLS
|
||||||
} finally {
|
} finally {
|
||||||
DBLock.unlock();
|
DBLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return mergeAggregateEvents(rangeInfo.getPeriodSize().getPeriod(), events);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* map a single row in a ResultSet to an AggregateEvent
|
||||||
|
*
|
||||||
|
* @param rs the result set whose current row should be mapped
|
||||||
|
* @param useSubTypes use the sub_type column if true, else use the
|
||||||
|
* base_type column
|
||||||
|
* @param descriptionLOD the description level of detail for this event
|
||||||
|
*
|
||||||
|
* @return an AggregateEvent corresponding to the current row in the given
|
||||||
|
* result set
|
||||||
|
*
|
||||||
|
* @throws SQLException
|
||||||
|
*/
|
||||||
|
private AggregateEvent aggregateEventHelper(ResultSet rs, boolean useSubTypes, DescriptionLOD descriptionLOD, TagsFilter filter) throws SQLException {
|
||||||
|
Interval interval = new Interval(rs.getLong("min(time)") * 1000, rs.getLong("max(time)") * 1000, TimeLineController.getJodaTimeZone());// NON-NLS
|
||||||
|
String eventIDsString = rs.getString("event_ids");// NON-NLS
|
||||||
|
Set<Long> eventIDs = SQLHelper.unGroupConcat(eventIDsString, Long::valueOf);
|
||||||
|
String description = rs.getString(SQLHelper.getDescriptionColumn(descriptionLOD));
|
||||||
|
EventType type = useSubTypes ? RootEventType.allTypes.get(rs.getInt("sub_type")) : BaseTypes.values()[rs.getInt("base_type")];// NON-NLS
|
||||||
|
|
||||||
|
Set<Long> hashHits = new HashSet<>();
|
||||||
|
String hashHitQuery = "SELECT group_concat(event_id) FROM events WHERE event_id IN (" + eventIDsString + ") AND hash_hit = 1";// NON-NLS
|
||||||
|
try (Statement stmt = con.createStatement();
|
||||||
|
ResultSet hashHitsRS = stmt.executeQuery(hashHitQuery)) {
|
||||||
|
while (hashHitsRS.next()) {
|
||||||
|
hashHits = SQLHelper.unGroupConcat(hashHitsRS.getString("group_concat(event_id)"), Long::valueOf);// NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Long> tagged = new HashSet<>();
|
||||||
|
String taggedQuery = "SELECT group_concat(event_id) FROM events WHERE event_id IN (" + eventIDsString + ") AND tagged = 1";// NON-NLS
|
||||||
|
try (Statement stmt = con.createStatement();
|
||||||
|
ResultSet taggedRS = stmt.executeQuery(taggedQuery)) {
|
||||||
|
while (taggedRS.next()) {
|
||||||
|
tagged = SQLHelper.unGroupConcat(taggedRS.getString("group_concat(event_id)"), Long::valueOf);// NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AggregateEvent(interval, type, eventIDs, hashHits, tagged,
|
||||||
|
description, descriptionLOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* merge the events in the given list if they are within the same period
|
||||||
|
* General algorithm is as follows:
|
||||||
|
*
|
||||||
|
* 1) sort them into a map from (type, description)-> List<aggevent>
|
||||||
|
* 2) for each key in map, merge the events and accumulate them in a list to
|
||||||
|
* return
|
||||||
|
*
|
||||||
|
* @param timeUnitLength
|
||||||
|
* @param preMergedEvents
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
static private List<AggregateEvent> mergeAggregateEvents(Period timeUnitLength, List<AggregateEvent> preMergedEvents) {
|
||||||
|
|
||||||
|
//effectively map from type to (map from description to events)
|
||||||
|
Map<EventType, SetMultimap< String, AggregateEvent>> typeMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (AggregateEvent aggregateEvent : preMergedEvents) {
|
||||||
|
typeMap.computeIfAbsent(aggregateEvent.getType(), eventType -> HashMultimap.create())
|
||||||
|
.put(aggregateEvent.getDescription(), aggregateEvent);
|
||||||
|
}
|
||||||
//result list to return
|
//result list to return
|
||||||
ArrayList<AggregateEvent> aggEvents = new ArrayList<>();
|
ArrayList<AggregateEvent> aggEvents = new ArrayList<>();
|
||||||
|
|
||||||
//save this for use when comparing gap size
|
|
||||||
Period timeUnitLength = rangeInfo.getPeriodSize().getPeriod();
|
|
||||||
|
|
||||||
//For each (type, description) key, merge agg events
|
//For each (type, description) key, merge agg events
|
||||||
for (SetMultimap<String, AggregateEvent> descrMap : typeMap.values()) {
|
for (SetMultimap<String, AggregateEvent> descrMap : typeMap.values()) {
|
||||||
|
//for each description ...
|
||||||
for (String descr : descrMap.keySet()) {
|
for (String descr : descrMap.keySet()) {
|
||||||
//run through the sorted events, merging together adjacent events
|
//run through the sorted events, merging together adjacent events
|
||||||
Iterator<AggregateEvent> iterator = descrMap.get(descr).stream()
|
Iterator<AggregateEvent> iterator = descrMap.get(descr).stream()
|
||||||
.sorted((AggregateEvent o1, AggregateEvent o2)
|
.sorted(Comparator.comparing(event -> event.getSpan().getStartMillis()))
|
||||||
-> Long.compare(o1.getSpan().getStartMillis(), o2.getSpan().getStartMillis()))
|
|
||||||
.iterator();
|
.iterator();
|
||||||
AggregateEvent current = iterator.next();
|
AggregateEvent current = iterator.next();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
@ -1013,17 +1184,10 @@ public class EventDB {
|
|||||||
aggEvents.add(current);
|
aggEvents.add(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//at this point we should have a list of aggregate events.
|
|
||||||
//one per type/description spanning consecutive time units as determined in rangeInfo
|
|
||||||
return aggEvents;
|
return aggEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String useHashHitTablesHelper(RootFilter filter) {
|
private static String typeColumnHelper(final boolean useSubTypes) {
|
||||||
return SQLHelper.hasActiveHashFilter(filter) ? ", hash_set_hits" : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String useSubTypeHelper(final boolean useSubTypes) {
|
|
||||||
return useSubTypes ? "sub_type" : "base_type";
|
return useSubTypes ? "sub_type" : "base_type";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1050,37 +1214,6 @@ public class EventDB {
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDescriptionColumn(DescriptionLOD lod) {
|
|
||||||
switch (lod) {
|
|
||||||
case FULL:
|
|
||||||
return "full_description";
|
|
||||||
case MEDIUM:
|
|
||||||
return "med_description";
|
|
||||||
case SHORT:
|
|
||||||
default:
|
|
||||||
return "short_description";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getStrfTimeFormat(TimeUnits info) {
|
|
||||||
switch (info) {
|
|
||||||
case DAYS:
|
|
||||||
return "%Y-%m-%dT00:00:00"; // NON-NLS
|
|
||||||
case HOURS:
|
|
||||||
return "%Y-%m-%dT%H:00:00"; // NON-NLS
|
|
||||||
case MINUTES:
|
|
||||||
return "%Y-%m-%dT%H:%M:00"; // NON-NLS
|
|
||||||
case MONTHS:
|
|
||||||
return "%Y-%m-01T00:00:00"; // NON-NLS
|
|
||||||
case SECONDS:
|
|
||||||
return "%Y-%m-%dT%H:%M:%S"; // NON-NLS
|
|
||||||
case YEARS:
|
|
||||||
return "%Y-01-01T00:00:00"; // NON-NLS
|
|
||||||
default:
|
|
||||||
return "%Y-%m-%dT%H:%M:%S"; // NON-NLS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PreparedStatement prepareStatement(String queryString) throws SQLException {
|
private PreparedStatement prepareStatement(String queryString) throws SQLException {
|
||||||
PreparedStatement prepareStatement = con.prepareStatement(queryString);
|
PreparedStatement prepareStatement = con.prepareStatement(queryString);
|
||||||
preparedStatements.add(prepareStatement);
|
preparedStatements.add(prepareStatement);
|
||||||
@ -1143,16 +1276,13 @@ public class EventDB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commit(Boolean notify) {
|
private void commit() {
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
try {
|
try {
|
||||||
con.commit();
|
con.commit();
|
||||||
// make sure we close before we update, bc they'll need locks
|
// make sure we close before we update, bc they'll need locks
|
||||||
close();
|
close();
|
||||||
|
|
||||||
if (notify) {
|
|
||||||
// fireNewEvents(newEvents);
|
|
||||||
}
|
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "Error commiting events.db.", ex); // NON-NLS
|
LOGGER.log(Level.SEVERE, "Error commiting events.db.", ex); // NON-NLS
|
||||||
rollback();
|
rollback();
|
@ -16,13 +16,12 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events.db;
|
package org.sleuthkit.autopsy.timeline.db;
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -34,6 +33,7 @@ import java.util.logging.Level;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
import javafx.collections.ObservableMap;
|
import javafx.collections.ObservableMap;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
@ -45,27 +45,34 @@ import org.sleuthkit.autopsy.casemodule.Case;
|
|||||||
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.timeline.ProgressWindow;
|
import org.sleuthkit.autopsy.timeline.ProgressWindow;
|
||||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.events.TimeLineEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.ArtifactEventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.ArtifactEventType;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.FileSystemTypes;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.FileSystemTypes;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.TagNameFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
||||||
|
import org.sleuthkit.datamodel.ContentTag;
|
||||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
|
import org.sleuthkit.datamodel.Tag;
|
||||||
|
import org.sleuthkit.datamodel.TagName;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
import org.sleuthkit.datamodel.TskData;
|
import org.sleuthkit.datamodel.TskData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides public API (over EventsDB) to access events. In theory this
|
* Provides higher-level public API (over EventsDB) to access events. In theory
|
||||||
* insulates the rest of the timeline module form the details of the db
|
* this insulates the rest of the timeline module form the details of the db
|
||||||
* implementation. Since there are no other implementations of the database or
|
* implementation. Since there are no other implementations of the database or
|
||||||
* clients of this class, and no Java Interface defined yet, in practice this
|
* clients of this class, and no Java Interface defined yet, in practice this
|
||||||
* just delegates everything to the eventDB
|
* just delegates everything to the eventDB. Some results are also cached by
|
||||||
|
* this layer.
|
||||||
*
|
*
|
||||||
* Concurrency Policy:
|
* Concurrency Policy:
|
||||||
*
|
*
|
||||||
@ -95,12 +102,17 @@ public class EventsRepository {
|
|||||||
|
|
||||||
private final ObservableMap<Long, String> datasourcesMap = FXCollections.observableHashMap();
|
private final ObservableMap<Long, String> datasourcesMap = FXCollections.observableHashMap();
|
||||||
private final ObservableMap<Long, String> hashSetMap = FXCollections.observableHashMap();
|
private final ObservableMap<Long, String> hashSetMap = FXCollections.observableHashMap();
|
||||||
|
private final ObservableList<TagName> tagNames = FXCollections.observableArrayList();
|
||||||
private final Case autoCase;
|
private final Case autoCase;
|
||||||
|
|
||||||
public Case getAutoCase() {
|
public Case getAutoCase() {
|
||||||
return autoCase;
|
return autoCase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableList<TagName> getTagNames() {
|
||||||
|
return tagNames;
|
||||||
|
}
|
||||||
|
|
||||||
synchronized public ObservableMap<Long, String> getDatasourcesMap() {
|
synchronized public ObservableMap<Long, String> getDatasourcesMap() {
|
||||||
return datasourcesMap;
|
return datasourcesMap;
|
||||||
}
|
}
|
||||||
@ -125,7 +137,7 @@ public class EventsRepository {
|
|||||||
this.autoCase = autoCase;
|
this.autoCase = autoCase;
|
||||||
//TODO: we should check that case is open, or get passed a case object/directory -jm
|
//TODO: we should check that case is open, or get passed a case object/directory -jm
|
||||||
this.eventDB = EventDB.getEventDB(autoCase);
|
this.eventDB = EventDB.getEventDB(autoCase);
|
||||||
populateFilterMaps(autoCase.getSleuthkitCase());
|
populateFilterData(autoCase.getSleuthkitCase());
|
||||||
idToEventCache = CacheBuilder.newBuilder()
|
idToEventCache = CacheBuilder.newBuilder()
|
||||||
.maximumSize(5000L)
|
.maximumSize(5000L)
|
||||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||||
@ -218,6 +230,94 @@ public class EventsRepository {
|
|||||||
return eventDB.getSpanningInterval(eventIDs);
|
return eventDB.getSpanningInterval(eventIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasNewColumns() {
|
||||||
|
return eventDB.hasNewColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get a count of tagnames applied to the given event ids as a map from
|
||||||
|
* tagname displayname to count of tag applications
|
||||||
|
*
|
||||||
|
* @param eventIDsWithTags the event ids to get the tag counts map for
|
||||||
|
*
|
||||||
|
* @return a map from tagname displayname to count of applications
|
||||||
|
*/
|
||||||
|
public Map<String, Long> getTagCountsByTagName(Set<Long> eventIDsWithTags) {
|
||||||
|
return eventDB.getTagCountsByTagName(eventIDsWithTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* use the given SleuthkitCase to update the data used to determine the
|
||||||
|
* available filters.
|
||||||
|
*
|
||||||
|
* @param skCase
|
||||||
|
*/
|
||||||
|
synchronized private void populateFilterData(SleuthkitCase skCase) {
|
||||||
|
|
||||||
|
for (Map.Entry<Long, String> hashSet : eventDB.getHashSetNames().entrySet()) {
|
||||||
|
hashSetMap.putIfAbsent(hashSet.getKey(), hashSet.getValue());
|
||||||
|
}
|
||||||
|
//because there is no way to remove a datasource we only add to this map.
|
||||||
|
for (Long id : eventDB.getDataSourceIDs()) {
|
||||||
|
try {
|
||||||
|
datasourcesMap.putIfAbsent(id, skCase.getContentById(id).getDataSource().getName());
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Failed to get datasource by ID.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//should this only be tags applied to files or event bearing artifacts?
|
||||||
|
tagNames.setAll(skCase.getTagNamesInUse());
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Failed to get tag names in use.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public Set<Long> addTag(long objID, Long artifactID, Tag tag) {
|
||||||
|
Set<Long> updatedEventIDs = eventDB.addTag(objID, artifactID, tag);
|
||||||
|
if (!updatedEventIDs.isEmpty()) {
|
||||||
|
invalidateCaches(updatedEventIDs);
|
||||||
|
}
|
||||||
|
return updatedEventIDs;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public Set<Long> deleteTag(long objID, Long artifactID, Tag tag, boolean tagged) {
|
||||||
|
Set<Long> updatedEventIDs = eventDB.deleteTag(objID, artifactID, tag, tagged);
|
||||||
|
if (!updatedEventIDs.isEmpty()) {
|
||||||
|
invalidateCaches(updatedEventIDs);
|
||||||
|
}
|
||||||
|
return updatedEventIDs;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized private void invalidateCaches(Set<Long> updatedEventIDs) {
|
||||||
|
eventCountsCache.invalidateAll();
|
||||||
|
aggregateEventsCache.invalidateAll();
|
||||||
|
idToEventCache.invalidateAll(updatedEventIDs);
|
||||||
|
try {
|
||||||
|
tagNames.setAll(autoCase.getSleuthkitCase().getTagNamesInUse());
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Failed to get tag names in use.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "sync" the given tags filter with the tagnames in use: Disable filters
|
||||||
|
* for tags that are not in use in the case, and add new filters for tags
|
||||||
|
* that don't have them. New filters are selected by default.
|
||||||
|
*
|
||||||
|
* @param tagsFilter the tags filter to modify so it is consistent with the
|
||||||
|
* tags in use in the case
|
||||||
|
*/
|
||||||
|
public void syncTagsFilter(TagsFilter tagsFilter) {
|
||||||
|
for (TagName t : tagNames) {
|
||||||
|
tagsFilter.addSubFilter(new TagNameFilter(t, autoCase));
|
||||||
|
}
|
||||||
|
for (TagNameFilter t : tagsFilter.getSubFilters()) {
|
||||||
|
t.setDisabled(tagNames.contains(t.getTagName()) == false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
synchronized public void rebuildRepository(Runnable r) {
|
synchronized public void rebuildRepository(Runnable r) {
|
||||||
if (dbPopulationWorker != null) {
|
if (dbPopulationWorker != null) {
|
||||||
dbPopulationWorker.cancel(true);
|
dbPopulationWorker.cancel(true);
|
||||||
@ -227,8 +327,110 @@ public class EventsRepository {
|
|||||||
dbPopulationWorker.execute();
|
dbPopulationWorker.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasDataSourceInfo() {
|
synchronized public void rebuildTags(Runnable r) {
|
||||||
return eventDB.hasNewColumns();
|
if (dbPopulationWorker != null) {
|
||||||
|
dbPopulationWorker.cancel(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
dbPopulationWorker = new RebuildTagsWorker(r);
|
||||||
|
dbPopulationWorker.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RebuildTagsWorker extends SwingWorker<Void, ProgressWindow.ProgressUpdate> {
|
||||||
|
|
||||||
|
private final ProgressWindow progressDialog;
|
||||||
|
|
||||||
|
//TODO: can we avoid this with a state listener? does it amount to the same thing?
|
||||||
|
//post population operation to execute
|
||||||
|
private final Runnable postPopulationOperation;
|
||||||
|
private final SleuthkitCase skCase;
|
||||||
|
private final TagsManager tagsManager;
|
||||||
|
|
||||||
|
public RebuildTagsWorker(Runnable postPopulationOperation) {
|
||||||
|
progressDialog = new ProgressWindow(null, true, this);
|
||||||
|
progressDialog.setVisible(true);
|
||||||
|
|
||||||
|
skCase = autoCase.getSleuthkitCase();
|
||||||
|
tagsManager = autoCase.getServices().getTagsManager();
|
||||||
|
|
||||||
|
this.postPopulationOperation = postPopulationOperation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground() throws Exception {
|
||||||
|
|
||||||
|
EventDB.EventTransaction trans = eventDB.beginTransaction();
|
||||||
|
LOGGER.log(Level.INFO, "dropping old tags"); // NON-NLS
|
||||||
|
eventDB.reInitializeTags();
|
||||||
|
|
||||||
|
LOGGER.log(Level.INFO, "updating content tags"); // NON-NLS
|
||||||
|
List<ContentTag> contentTags = tagsManager.getAllContentTags();
|
||||||
|
int size = contentTags.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
if (isCancelled()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
publish(new ProgressWindow.ProgressUpdate(i, size, "refreshing file tags", ""));
|
||||||
|
ContentTag contentTag = contentTags.get(i);
|
||||||
|
eventDB.addTag(contentTag.getContent().getId(), null, contentTag);
|
||||||
|
}
|
||||||
|
LOGGER.log(Level.INFO, "updating artifact tags"); // NON-NLS
|
||||||
|
List<BlackboardArtifactTag> artifactTags = tagsManager.getAllBlackboardArtifactTags();
|
||||||
|
size = artifactTags.size();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
if (isCancelled()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
publish(new ProgressWindow.ProgressUpdate(i, size, "refreshing result tags", ""));
|
||||||
|
BlackboardArtifactTag artifactTag = artifactTags.get(i);
|
||||||
|
eventDB.addTag(artifactTag.getContent().getId(), artifactTag.getArtifact().getArtifactID(), artifactTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.log(Level.INFO, "committing tags"); // NON-NLS
|
||||||
|
publish(new ProgressWindow.ProgressUpdate(0, -1, "committing tag changes", ""));
|
||||||
|
if (isCancelled()) {
|
||||||
|
eventDB.rollBackTransaction(trans);
|
||||||
|
} else {
|
||||||
|
eventDB.commitTransaction(trans);
|
||||||
|
}
|
||||||
|
|
||||||
|
populateFilterData(skCase);
|
||||||
|
invalidateCaches();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handle intermediate 'results': just update progress dialog
|
||||||
|
*
|
||||||
|
* @param chunks
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void process(List<ProgressWindow.ProgressUpdate> chunks) {
|
||||||
|
super.process(chunks);
|
||||||
|
ProgressWindow.ProgressUpdate chunk = chunks.get(chunks.size() - 1);
|
||||||
|
progressDialog.update(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NbBundle.Messages("msgdlg.tagsproblem.text=There was a problem refreshing the tagged events."
|
||||||
|
+ " Some events may have inacurate tags. See the log for details.")
|
||||||
|
protected void done() {
|
||||||
|
super.done();
|
||||||
|
try {
|
||||||
|
progressDialog.close();
|
||||||
|
get();
|
||||||
|
} catch (CancellationException ex) {
|
||||||
|
LOGGER.log(Level.WARNING, "Database population was cancelled by the user. Not all events may be present or accurate. See the log for details.", ex); // NON-NLS
|
||||||
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
|
LOGGER.log(Level.WARNING, "Exception while populating database.", ex); // NON-NLS
|
||||||
|
JOptionPane.showMessageDialog(null, Bundle.msgdlg_tagsproblem_text());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.log(Level.WARNING, "Unexpected exception while populating database.", ex); // NON-NLS
|
||||||
|
JOptionPane.showMessageDialog(null, Bundle.msgdlg_tagsproblem_text());
|
||||||
|
}
|
||||||
|
postPopulationOperation.run(); //execute post db population operation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DBPopulationWorker extends SwingWorker<Void, ProgressWindow.ProgressUpdate> {
|
private class DBPopulationWorker extends SwingWorker<Void, ProgressWindow.ProgressUpdate> {
|
||||||
@ -256,7 +458,7 @@ public class EventsRepository {
|
|||||||
"progressWindow.msg.reinit_db=(re)initializing events database",
|
"progressWindow.msg.reinit_db=(re)initializing events database",
|
||||||
"progressWindow.msg.commitingDb=committing events db"})
|
"progressWindow.msg.commitingDb=committing events db"})
|
||||||
protected Void doInBackground() throws Exception {
|
protected Void doInBackground() throws Exception {
|
||||||
process(Arrays.asList(new ProgressWindow.ProgressUpdate(0, -1, Bundle.progressWindow_msg_reinit_db(), "")));
|
publish(new ProgressWindow.ProgressUpdate(0, -1, Bundle.progressWindow_msg_reinit_db(), ""));
|
||||||
//reset database
|
//reset database
|
||||||
//TODO: can we do more incremental updates? -jm
|
//TODO: can we do more incremental updates? -jm
|
||||||
eventDB.reInitializeDB();
|
eventDB.reInitializeDB();
|
||||||
@ -265,7 +467,7 @@ public class EventsRepository {
|
|||||||
List<Long> files = skCase.findAllFileIdsWhere("name != '.' AND name != '..'");
|
List<Long> files = skCase.findAllFileIdsWhere("name != '.' AND name != '..'");
|
||||||
|
|
||||||
final int numFiles = files.size();
|
final int numFiles = files.size();
|
||||||
process(Arrays.asList(new ProgressWindow.ProgressUpdate(0, numFiles, Bundle.progressWindow_msg_populateMacEventsFiles(), "")));
|
publish(new ProgressWindow.ProgressUpdate(0, numFiles, Bundle.progressWindow_msg_populateMacEventsFiles(), ""));
|
||||||
|
|
||||||
//insert file events into db
|
//insert file events into db
|
||||||
int i = 1;
|
int i = 1;
|
||||||
@ -290,25 +492,25 @@ public class EventsRepository {
|
|||||||
String shortDesc = datasourceName + "/" + StringUtils.defaultIfBlank(rootFolder, "");
|
String shortDesc = datasourceName + "/" + StringUtils.defaultIfBlank(rootFolder, "");
|
||||||
String medD = datasourceName + parentPath;
|
String medD = datasourceName + parentPath;
|
||||||
final TskData.FileKnown known = f.getKnown();
|
final TskData.FileKnown known = f.getKnown();
|
||||||
Set<String> hashSets = f.getHashSetNames() ;
|
Set<String> hashSets = f.getHashSetNames();
|
||||||
boolean tagged = !tagsManager.getContentTagsByContent(f).isEmpty();
|
List<ContentTag> tags = tagsManager.getContentTagsByContent(f);
|
||||||
|
|
||||||
//insert it into the db if time is > 0 => time is legitimate (drops logical files)
|
//insert it into the db if time is > 0 => time is legitimate (drops logical files)
|
||||||
if (f.getAtime() > 0) {
|
if (f.getAtime() > 0) {
|
||||||
eventDB.insertEvent(f.getAtime(), FileSystemTypes.FILE_ACCESSED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashSets, tagged, trans);
|
eventDB.insertEvent(f.getAtime(), FileSystemTypes.FILE_ACCESSED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashSets, tags, trans);
|
||||||
}
|
}
|
||||||
if (f.getMtime() > 0) {
|
if (f.getMtime() > 0) {
|
||||||
eventDB.insertEvent(f.getMtime(), FileSystemTypes.FILE_MODIFIED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashSets, tagged, trans);
|
eventDB.insertEvent(f.getMtime(), FileSystemTypes.FILE_MODIFIED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashSets, tags, trans);
|
||||||
}
|
}
|
||||||
if (f.getCtime() > 0) {
|
if (f.getCtime() > 0) {
|
||||||
eventDB.insertEvent(f.getCtime(), FileSystemTypes.FILE_CHANGED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashSets, tagged, trans);
|
eventDB.insertEvent(f.getCtime(), FileSystemTypes.FILE_CHANGED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashSets, tags, trans);
|
||||||
}
|
}
|
||||||
if (f.getCrtime() > 0) {
|
if (f.getCrtime() > 0) {
|
||||||
eventDB.insertEvent(f.getCrtime(), FileSystemTypes.FILE_CREATED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashSets, tagged, trans);
|
eventDB.insertEvent(f.getCrtime(), FileSystemTypes.FILE_CREATED, datasourceID, fID, null, uniquePath, medD, shortDesc, known, hashSets, tags, trans);
|
||||||
}
|
}
|
||||||
|
|
||||||
process(Arrays.asList(new ProgressWindow.ProgressUpdate(i, numFiles,
|
publish(new ProgressWindow.ProgressUpdate(i, numFiles,
|
||||||
Bundle.progressWindow_msg_populateMacEventsFiles(), f.getName())));
|
Bundle.progressWindow_msg_populateMacEventsFiles(), f.getName()));
|
||||||
}
|
}
|
||||||
} catch (TskCoreException tskCoreException) {
|
} catch (TskCoreException tskCoreException) {
|
||||||
LOGGER.log(Level.WARNING, "failed to insert mac event for file : " + fID, tskCoreException); // NON-NLS
|
LOGGER.log(Level.WARNING, "failed to insert mac event for file : " + fID, tskCoreException); // NON-NLS
|
||||||
@ -329,14 +531,15 @@ public class EventsRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
process(Arrays.asList(new ProgressWindow.ProgressUpdate(0, -1, Bundle.progressWindow_msg_commitingDb(), "")));
|
publish(new ProgressWindow.ProgressUpdate(0, -1, Bundle.progressWindow_msg_commitingDb(), ""));
|
||||||
|
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
eventDB.rollBackTransaction(trans);
|
eventDB.rollBackTransaction(trans);
|
||||||
} else {
|
} else {
|
||||||
eventDB.commitTransaction(trans, true);
|
eventDB.commitTransaction(trans);
|
||||||
}
|
}
|
||||||
|
|
||||||
populateFilterMaps(skCase);
|
populateFilterData(skCase);
|
||||||
invalidateCaches();
|
invalidateCaches();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -363,7 +566,7 @@ public class EventsRepository {
|
|||||||
progressDialog.close();
|
progressDialog.close();
|
||||||
get();
|
get();
|
||||||
} catch (CancellationException ex) {
|
} catch (CancellationException ex) {
|
||||||
LOGGER.log(Level.INFO, "Database population was cancelled by the user. Not all events may be present or accurate. See the log for details.", ex); // NON-NLS
|
LOGGER.log(Level.WARNING, "Database population was cancelled by the user. Not all events may be present or accurate. See the log for details.", ex); // NON-NLS
|
||||||
} catch (InterruptedException | ExecutionException ex) {
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
LOGGER.log(Level.WARNING, "Exception while populating database.", ex); // NON-NLS
|
LOGGER.log(Level.WARNING, "Exception while populating database.", ex); // NON-NLS
|
||||||
JOptionPane.showMessageDialog(null, Bundle.msgdlg_problem_text());
|
JOptionPane.showMessageDialog(null, Bundle.msgdlg_problem_text());
|
||||||
@ -388,64 +591,32 @@ public class EventsRepository {
|
|||||||
final ArrayList<BlackboardArtifact> blackboardArtifacts = skCase.getBlackboardArtifacts(type.getArtifactType());
|
final ArrayList<BlackboardArtifact> blackboardArtifacts = skCase.getBlackboardArtifacts(type.getArtifactType());
|
||||||
final int numArtifacts = blackboardArtifacts.size();
|
final int numArtifacts = blackboardArtifacts.size();
|
||||||
|
|
||||||
process(Arrays.asList(new ProgressWindow.ProgressUpdate(0, numArtifacts,
|
for (int i = 0; i < numArtifacts; i++) {
|
||||||
Bundle.progressWindow_populatingXevents(type.toString()), "")));
|
publish(new ProgressWindow.ProgressUpdate(i, numArtifacts,
|
||||||
|
Bundle.progressWindow_populatingXevents(type.getDisplayName()), ""));
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (final BlackboardArtifact bbart : blackboardArtifacts) {
|
|
||||||
//for each artifact, extract the relevant information for the descriptions
|
//for each artifact, extract the relevant information for the descriptions
|
||||||
|
BlackboardArtifact bbart = blackboardArtifacts.get(i);
|
||||||
ArtifactEventType.AttributeEventDescription eventDescription = ArtifactEventType.AttributeEventDescription.buildEventDescription(type, bbart);
|
ArtifactEventType.AttributeEventDescription eventDescription = ArtifactEventType.AttributeEventDescription.buildEventDescription(type, bbart);
|
||||||
|
|
||||||
if (eventDescription != null && eventDescription.getTime() > 0L) { //insert it into the db if time is > 0 => time is legitimate
|
//insert it into the db if time is > 0 => time is legitimate
|
||||||
long datasourceID = skCase.getContentById(bbart.getObjectID()).getDataSource().getId();
|
if (eventDescription != null && eventDescription.getTime() > 0L) {
|
||||||
|
long objectID = bbart.getObjectID();
|
||||||
AbstractFile f = skCase.getAbstractFileById(bbart.getObjectID());
|
AbstractFile f = skCase.getAbstractFileById(objectID);
|
||||||
|
long datasourceID = f.getDataSource().getId();
|
||||||
|
long artifactID = bbart.getArtifactID();
|
||||||
Set<String> hashSets = f.getHashSetNames();
|
Set<String> hashSets = f.getHashSetNames();
|
||||||
boolean tagged = tagsManager.getBlackboardArtifactTagsByArtifact(bbart).isEmpty() == false;
|
List<BlackboardArtifactTag> tags = tagsManager.getBlackboardArtifactTagsByArtifact(bbart);
|
||||||
|
String fullDescription = eventDescription.getFullDescription();
|
||||||
|
String medDescription = eventDescription.getMedDescription();
|
||||||
|
String shortDescription = eventDescription.getShortDescription();
|
||||||
|
|
||||||
eventDB.insertEvent(eventDescription.getTime(), type, datasourceID, bbart.getObjectID(), bbart.getArtifactID(), eventDescription.getFullDescription(), eventDescription.getMedDescription(), eventDescription.getShortDescription(), null, hashSets, tagged, trans);
|
eventDB.insertEvent(eventDescription.getTime(), type, datasourceID, objectID, artifactID, fullDescription, medDescription, shortDescription, null, hashSets, tags, trans);
|
||||||
}
|
}
|
||||||
|
|
||||||
i++;
|
|
||||||
process(Arrays.asList(new ProgressWindow.ProgressUpdate(i, numArtifacts,
|
|
||||||
Bundle.progressWindow_populatingXevents(type), "")));
|
|
||||||
}
|
}
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "There was a problem getting events with sub type = " + type.toString() + ".", ex); // NON-NLS
|
LOGGER.log(Level.SEVERE, "There was a problem getting events with sub type = " + type.toString() + ".", ex); // NON-NLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* use the given SleuthkitCase to look up the names for the datasources in
|
|
||||||
* the events table.
|
|
||||||
*
|
|
||||||
* TODO: we could keep a table of id -> name in the eventdb but I am wary of
|
|
||||||
* having too much redundant info.
|
|
||||||
*
|
|
||||||
* @param skCase
|
|
||||||
*/
|
|
||||||
synchronized private void populateFilterMaps(SleuthkitCase skCase) {
|
|
||||||
|
|
||||||
for (Map.Entry<Long, String> hashSet : eventDB.getHashSetNames().entrySet()) {
|
|
||||||
hashSetMap.putIfAbsent(hashSet.getKey(), hashSet.getValue());
|
|
||||||
}
|
|
||||||
//because there is no way to remove a datasource we only add to this map.
|
|
||||||
for (Long id : eventDB.getDataSourceIDs()) {
|
|
||||||
try {
|
|
||||||
datasourcesMap.putIfAbsent(id, skCase.getContentById(id).getDataSource().getName());
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
LOGGER.log(Level.SEVERE, "Failed to get datasource by ID.", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public Set<Long> markEventsTagged(long objID, Long artifactID, boolean tagged) {
|
|
||||||
Set<Long> updatedEventIDs = eventDB.markEventsTagged(objID, artifactID, tagged);
|
|
||||||
if (!updatedEventIDs.isEmpty()) {
|
|
||||||
aggregateEventsCache.invalidateAll();
|
|
||||||
idToEventCache.invalidateAll(updatedEventIDs);
|
|
||||||
}
|
|
||||||
return updatedEventIDs;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -16,7 +16,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events.db;
|
package org.sleuthkit.autopsy.timeline.db;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
283
Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java
Normal file
283
Core/src/org/sleuthkit/autopsy/timeline/db/SQLHelper.java
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2013-15 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.db;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.HashHitsFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.HashSetFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.HideKnownFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.IntersectionFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.TagNameFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.UnionFilter;
|
||||||
|
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||||
|
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
||||||
|
import static org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD.FULL;
|
||||||
|
import static org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD.MEDIUM;
|
||||||
|
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.DAYS;
|
||||||
|
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.HOURS;
|
||||||
|
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.MINUTES;
|
||||||
|
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.MONTHS;
|
||||||
|
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.SECONDS;
|
||||||
|
import static org.sleuthkit.autopsy.timeline.zooming.TimeUnits.YEARS;
|
||||||
|
import org.sleuthkit.datamodel.TskData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static helper methods for converting between java data model objects and
|
||||||
|
* sqlite queries.
|
||||||
|
*/
|
||||||
|
public class SQLHelper {
|
||||||
|
|
||||||
|
static String useHashHitTablesHelper(RootFilter filter) {
|
||||||
|
HashHitsFilter hashHitFilter = filter.getHashHitsFilter();
|
||||||
|
return hashHitFilter.isSelected() && false == hashHitFilter.isDisabled() ? " LEFT JOIN hash_set_hits " : " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
static String useTagTablesHelper(RootFilter filter) {
|
||||||
|
TagsFilter tagsFilter = filter.getTagsFilter();
|
||||||
|
return tagsFilter.isSelected() && false == tagsFilter.isDisabled() ? " LEFT JOIN tags " : " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* take the result of a group_concat SQLite operation and split it into a
|
||||||
|
* set of X using the mapper to to convert from string to X
|
||||||
|
*
|
||||||
|
* @param <X> the type of elements to return
|
||||||
|
* @param groupConcat a string containing the group_concat result ( a comma
|
||||||
|
* separated list)
|
||||||
|
* @param mapper a function from String to X
|
||||||
|
*
|
||||||
|
* @return a Set of X, each element mapped from one element of the original
|
||||||
|
* comma delimited string
|
||||||
|
*/
|
||||||
|
static <X> Set<X> unGroupConcat(String groupConcat, Function<String, X> mapper) {
|
||||||
|
return StringUtils.isBlank(groupConcat) ? Collections.emptySet()
|
||||||
|
: Stream.of(groupConcat.split(","))
|
||||||
|
.map(mapper::apply)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getSQLWhere(IntersectionFilter<?> filter) {
|
||||||
|
return filter.getSubFilters().stream()
|
||||||
|
.filter(Filter::isSelected)
|
||||||
|
.map(SQLHelper::getSQLWhere)
|
||||||
|
.collect(Collectors.joining(" and ", "( ", ")"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getSQLWhere(UnionFilter<?> filter) {
|
||||||
|
return filter.getSubFilters().stream()
|
||||||
|
.filter(Filter::isSelected).map(SQLHelper::getSQLWhere)
|
||||||
|
.collect(Collectors.joining(" or ", "( ", ")"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getSQLWhere(RootFilter filter) {
|
||||||
|
return getSQLWhere((IntersectionFilter) filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getSQLWhere(Filter filter) {
|
||||||
|
String result = "";
|
||||||
|
if (filter == null) {
|
||||||
|
return "1";
|
||||||
|
} else if (filter instanceof TagsFilter) {
|
||||||
|
result = getSQLWhere((TagsFilter) filter);
|
||||||
|
} else if (filter instanceof HashHitsFilter) {
|
||||||
|
result = getSQLWhere((HashHitsFilter) filter);
|
||||||
|
} else if (filter instanceof DataSourceFilter) {
|
||||||
|
result = getSQLWhere((DataSourceFilter) filter);
|
||||||
|
} else if (filter instanceof DataSourcesFilter) {
|
||||||
|
result = getSQLWhere((DataSourcesFilter) filter);
|
||||||
|
} else if (filter instanceof HideKnownFilter) {
|
||||||
|
result = getSQLWhere((HideKnownFilter) filter);
|
||||||
|
} else if (filter instanceof HashHitsFilter) {
|
||||||
|
result = getSQLWhere((HashHitsFilter) filter);
|
||||||
|
} else if (filter instanceof TextFilter) {
|
||||||
|
result = getSQLWhere((TextFilter) filter);
|
||||||
|
} else if (filter instanceof TypeFilter) {
|
||||||
|
result = getSQLWhere((TypeFilter) filter);
|
||||||
|
} else if (filter instanceof IntersectionFilter) {
|
||||||
|
result = getSQLWhere((IntersectionFilter) filter);
|
||||||
|
} else if (filter instanceof UnionFilter) {
|
||||||
|
result = getSQLWhere((UnionFilter) filter);
|
||||||
|
} else {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
result = StringUtils.deleteWhitespace(result).equals("(1and1and1)") ? "1" : result;
|
||||||
|
result = StringUtils.deleteWhitespace(result).equals("()") ? "1" : result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getSQLWhere(HideKnownFilter filter) {
|
||||||
|
if (filter.isSelected()) {
|
||||||
|
return "(known_state IS NOT '" + TskData.FileKnown.KNOWN.getFileKnownValue() + "')"; // NON-NLS
|
||||||
|
} else {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getSQLWhere(TagsFilter filter) {
|
||||||
|
if (filter.isSelected()
|
||||||
|
&& (false == filter.isDisabled())
|
||||||
|
&& (filter.getSubFilters().isEmpty() == false)) {
|
||||||
|
String tagNameIDs = filter.getSubFilters().stream()
|
||||||
|
.filter((TagNameFilter t) -> t.isSelected() && !t.isDisabled())
|
||||||
|
.map((TagNameFilter t) -> String.valueOf(t.getTagName().getId()))
|
||||||
|
.collect(Collectors.joining(", ", "(", ")"));
|
||||||
|
return "(events.event_id == tags.event_id AND "
|
||||||
|
+ "tags.tag_name_id IN " + tagNameIDs + ") ";
|
||||||
|
} else {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getSQLWhere(HashHitsFilter filter) {
|
||||||
|
if (filter.isSelected()
|
||||||
|
&& (false == filter.isDisabled())
|
||||||
|
&& (filter.getSubFilters().isEmpty() == false)) {
|
||||||
|
String hashSetIDs = filter.getSubFilters().stream()
|
||||||
|
.filter((HashSetFilter t) -> t.isSelected() && !t.isDisabled())
|
||||||
|
.map((HashSetFilter t) -> String.valueOf(t.getHashSetID()))
|
||||||
|
.collect(Collectors.joining(", ", "(", ")"));
|
||||||
|
return "(hash_set_hits.hash_set_id IN " + hashSetIDs + " AND hash_set_hits.event_id == events.event_id)";
|
||||||
|
} else {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getSQLWhere(DataSourceFilter filter) {
|
||||||
|
if (filter.isSelected()) {
|
||||||
|
return "(datasource_id = '" + filter.getDataSourceID() + "')";
|
||||||
|
} else {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getSQLWhere(DataSourcesFilter filter) {
|
||||||
|
return (filter.isSelected()) ? "(datasource_id in ("
|
||||||
|
+ filter.getSubFilters().stream()
|
||||||
|
.filter(AbstractFilter::isSelected)
|
||||||
|
.map((dataSourceFilter) -> String.valueOf(dataSourceFilter.getDataSourceID()))
|
||||||
|
.collect(Collectors.joining(", ")) + "))" : "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getSQLWhere(TextFilter filter) {
|
||||||
|
if (filter.isSelected()) {
|
||||||
|
if (StringUtils.isBlank(filter.getText())) {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
String strippedFilterText = StringUtils.strip(filter.getText());
|
||||||
|
return "((med_description like '%" + strippedFilterText + "%')"
|
||||||
|
+ " or (full_description like '%" + strippedFilterText + "%')"
|
||||||
|
+ " or (short_description like '%" + strippedFilterText + "%'))";
|
||||||
|
} else {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate a sql where clause for the given type filter, while trying to be
|
||||||
|
* as simple as possible to improve performance.
|
||||||
|
*
|
||||||
|
* @param typeFilter
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getSQLWhere(TypeFilter typeFilter) {
|
||||||
|
if (typeFilter.isSelected() == false) {
|
||||||
|
return "0";
|
||||||
|
} else if (typeFilter.getEventType() instanceof RootEventType) {
|
||||||
|
if (typeFilter.getSubFilters().stream()
|
||||||
|
.allMatch(subFilter -> subFilter.isSelected() && subFilter.getSubFilters().stream().allMatch(Filter::isSelected))) {
|
||||||
|
return "1"; //then collapse clause to true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "(sub_type IN (" + StringUtils.join(getActiveSubTypes(typeFilter), ",") + "))";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Integer> getActiveSubTypes(TypeFilter filter) {
|
||||||
|
if (filter.isSelected()) {
|
||||||
|
if (filter.getSubFilters().isEmpty()) {
|
||||||
|
return Collections.singletonList(RootEventType.allTypes.indexOf(filter.getEventType()));
|
||||||
|
} else {
|
||||||
|
return filter.getSubFilters().stream().flatMap((Filter t) -> getActiveSubTypes((TypeFilter) t).stream()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get a sqlite strftime format string that will allow us to group by the
|
||||||
|
* requested period size. That is, with all info more granular that that
|
||||||
|
* requested dropped (replaced with zeros).
|
||||||
|
*
|
||||||
|
* @param info the {@link RangeDivisionInfo} with the requested period size
|
||||||
|
*
|
||||||
|
* @return a String formatted according to the sqlite strftime spec
|
||||||
|
*
|
||||||
|
* @see https://www.sqlite.org/lang_datefunc.html
|
||||||
|
*/
|
||||||
|
static String getStrfTimeFormat(@Nonnull RangeDivisionInfo info) {
|
||||||
|
switch (info.getPeriodSize()) {
|
||||||
|
case YEARS:
|
||||||
|
return "%Y-01-01T00:00:00"; // NON-NLS
|
||||||
|
case MONTHS:
|
||||||
|
return "%Y-%m-01T00:00:00"; // NON-NLS
|
||||||
|
case DAYS:
|
||||||
|
return "%Y-%m-%dT00:00:00"; // NON-NLS
|
||||||
|
case HOURS:
|
||||||
|
return "%Y-%m-%dT%H:00:00"; // NON-NLS
|
||||||
|
case MINUTES:
|
||||||
|
return "%Y-%m-%dT%H:%M:00"; // NON-NLS
|
||||||
|
case SECONDS:
|
||||||
|
default: //seconds - should never happen
|
||||||
|
return "%Y-%m-%dT%H:%M:%S"; // NON-NLS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getDescriptionColumn(DescriptionLOD lod) {
|
||||||
|
switch (lod) {
|
||||||
|
case FULL:
|
||||||
|
return "full_description";
|
||||||
|
case MEDIUM:
|
||||||
|
return "med_description";
|
||||||
|
case SHORT:
|
||||||
|
default:
|
||||||
|
return "short_description";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SQLHelper() {
|
||||||
|
}
|
||||||
|
}
|
@ -18,22 +18,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events;
|
package org.sleuthkit.autopsy.timeline.events;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Posted to eventbus when a tag as been added to a file artifact that
|
* A "local" event published by filteredEventsModel to indicate that the user
|
||||||
* corresponds to an event
|
* 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.
|
||||||
*/
|
*/
|
||||||
public class EventsTaggedEvent {
|
public class RefreshRequestedEvent {
|
||||||
|
|
||||||
private final Set<Long> eventIDs;
|
|
||||||
|
|
||||||
public EventsTaggedEvent(Set<Long> eventIDs) {
|
|
||||||
this.eventIDs = eventIDs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Long> getEventIDs() {
|
|
||||||
return Collections.unmodifiableSet(eventIDs);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -18,23 +18,26 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.events;
|
package org.sleuthkit.autopsy.timeline.events;
|
||||||
|
|
||||||
import java.util.Collections;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Posted to eventbus when a tag as been removed from a file artifact that
|
* A "local" event published by filteredEventsModel to indicate that events have
|
||||||
* corresponds to an event
|
* been(un)tagged. This event is not intended for use out side of the timeline
|
||||||
|
* module.
|
||||||
*/
|
*/
|
||||||
public class EventsUnTaggedEvent {
|
public class TagsUpdatedEvent {
|
||||||
|
|
||||||
private final Set<Long> eventIDs;
|
private final Set<Long> updatedEventIDs;
|
||||||
|
|
||||||
public Set<Long> getEventIDs() {
|
|
||||||
return Collections.unmodifiableSet(eventIDs);
|
public ImmutableSet<Long> getUpdatedEventIDs() {
|
||||||
|
return ImmutableSet.copyOf(updatedEventIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventsUnTaggedEvent(Set<Long> eventIDs) {
|
|
||||||
this.eventIDs = eventIDs;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public TagsUpdatedEvent(Set<Long> updatedEventIDs) {
|
||||||
|
this.updatedEventIDs = updatedEventIDs;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,157 +0,0 @@
|
|||||||
/*
|
|
||||||
* To change this license header, choose License Headers in Project Properties.
|
|
||||||
* To change this template file, choose Tools | Templates
|
|
||||||
* and open the template in the editor.
|
|
||||||
*/
|
|
||||||
package org.sleuthkit.autopsy.timeline.events.db;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.HashHitsFilter;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.HashSetFilter;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.HideKnownFilter;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.IntersectionFilter;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.UnionFilter;
|
|
||||||
import org.sleuthkit.datamodel.TskData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class SQLHelper {
|
|
||||||
|
|
||||||
private static List<Integer> getActiveSubTypes(TypeFilter filter) {
|
|
||||||
if (filter.isSelected()) {
|
|
||||||
if (filter.getSubFilters().isEmpty()) {
|
|
||||||
return Collections.singletonList(RootEventType.allTypes.indexOf(filter.getEventType()));
|
|
||||||
} else {
|
|
||||||
return filter.getSubFilters().stream().flatMap((Filter t) -> getActiveSubTypes((TypeFilter) t).stream()).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static boolean hasActiveHashFilter(RootFilter filter) {
|
|
||||||
HashHitsFilter hashHitFilter = filter.getHashHitsFilter();
|
|
||||||
return hashHitFilter.isSelected() && false == hashHitFilter.isDisabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
private SQLHelper() {
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getSQLWhere(IntersectionFilter<?> filter) {
|
|
||||||
return filter.getSubFilters().stream().filter(Filter::isSelected).map(SQLHelper::getSQLWhere).collect(Collectors.joining(" and ", "( ", ")"));
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getSQLWhere(UnionFilter<?> filter) {
|
|
||||||
return filter.getSubFilters().stream().filter(Filter::isSelected).map(SQLHelper::getSQLWhere).collect(Collectors.joining(" or ", "( ", ")"));
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getSQLWhere(Filter filter) {
|
|
||||||
String result = "";
|
|
||||||
if (filter == null) {
|
|
||||||
return "1";
|
|
||||||
} else if (filter instanceof HashHitsFilter) {
|
|
||||||
result = getSQLWhere((HashHitsFilter) filter);
|
|
||||||
} else if (filter instanceof DataSourceFilter) {
|
|
||||||
result = getSQLWhere((DataSourceFilter) filter);
|
|
||||||
} else if (filter instanceof DataSourcesFilter) {
|
|
||||||
result = getSQLWhere((DataSourcesFilter) filter);
|
|
||||||
} else if (filter instanceof HideKnownFilter) {
|
|
||||||
result = getSQLWhere((HideKnownFilter) filter);
|
|
||||||
} else if (filter instanceof HashHitsFilter) {
|
|
||||||
result = getSQLWhere((HashHitsFilter) filter);
|
|
||||||
} else if (filter instanceof TextFilter) {
|
|
||||||
result = getSQLWhere((TextFilter) filter);
|
|
||||||
} else if (filter instanceof TypeFilter) {
|
|
||||||
result = getSQLWhere((TypeFilter) filter);
|
|
||||||
} else if (filter instanceof IntersectionFilter) {
|
|
||||||
result = getSQLWhere((IntersectionFilter) filter);
|
|
||||||
} else if (filter instanceof UnionFilter) {
|
|
||||||
result = getSQLWhere((UnionFilter) filter);
|
|
||||||
} else {
|
|
||||||
return "1";
|
|
||||||
}
|
|
||||||
result = StringUtils.deleteWhitespace(result).equals("(1and1and1)") ? "1" : result;
|
|
||||||
result = StringUtils.deleteWhitespace(result).equals("()") ? "1" : result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getSQLWhere(HideKnownFilter filter) {
|
|
||||||
if (filter.isSelected()) {
|
|
||||||
return "(known_state IS NOT '" + TskData.FileKnown.KNOWN.getFileKnownValue() + "')"; // NON-NLS
|
|
||||||
} else {
|
|
||||||
return "1";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getSQLWhere(HashHitsFilter filter) {
|
|
||||||
if (filter.isSelected()
|
|
||||||
&& (false == filter.isDisabled())
|
|
||||||
&& (filter.getSubFilters().isEmpty() == false)) {
|
|
||||||
return "(hash_set_hits.hash_set_id in " + filter.getSubFilters().stream()
|
|
||||||
.filter((HashSetFilter t) -> t.isSelected() && !t.isDisabled())
|
|
||||||
.map((HashSetFilter t) -> String.valueOf(t.getHashSetID()))
|
|
||||||
.collect(Collectors.joining(", ", "(", ")")) + " and hash_set_hits.event_id == events.event_id)";
|
|
||||||
} else {
|
|
||||||
return "1";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getSQLWhere(DataSourceFilter filter) {
|
|
||||||
return (filter.isSelected()) ? "(datasource_id = '" + filter.getDataSourceID() + "')" : "1";
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getSQLWhere(DataSourcesFilter filter) {
|
|
||||||
return (filter.isSelected()) ? "(datasource_id in ("
|
|
||||||
+ filter.getSubFilters().stream()
|
|
||||||
.filter(AbstractFilter::isSelected)
|
|
||||||
.map((dataSourceFilter) -> String.valueOf(dataSourceFilter.getDataSourceID()))
|
|
||||||
.collect(Collectors.joining(", ")) + "))" : "1";
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getSQLWhere(TextFilter filter) {
|
|
||||||
if (filter.isSelected()) {
|
|
||||||
if (StringUtils.isBlank(filter.getText())) {
|
|
||||||
return "1";
|
|
||||||
}
|
|
||||||
String strippedFilterText = StringUtils.strip(filter.getText());
|
|
||||||
return "((med_description like '%" + strippedFilterText + "%')"
|
|
||||||
+ " or (full_description like '%" + strippedFilterText + "%')"
|
|
||||||
+ " or (short_description like '%" + strippedFilterText + "%'))";
|
|
||||||
} else {
|
|
||||||
return "1";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* generate a sql where clause for the given type filter, while trying to be
|
|
||||||
* as simple as possible to improve performance.
|
|
||||||
*
|
|
||||||
* @param typeFilter
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
static String getSQLWhere(TypeFilter typeFilter) {
|
|
||||||
if (typeFilter.isSelected() == false) {
|
|
||||||
return "0";
|
|
||||||
} else if (typeFilter.getEventType() instanceof RootEventType) {
|
|
||||||
if (typeFilter.getSubFilters().stream()
|
|
||||||
.allMatch(subFilter -> subFilter.isSelected() && subFilter.getSubFilters().stream().allMatch(Filter::isSelected))) {
|
|
||||||
return "1"; //then collapse clause to true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "(sub_type IN (" + StringUtils.join(getActiveSubTypes(typeFilter), ",") + "))";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -36,7 +36,7 @@ import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
|||||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
|
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
|
||||||
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
import org.sleuthkit.autopsy.datamodel.NodeProperty;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.events.TimeLineEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
import org.sleuthkit.datamodel.Content;
|
import org.sleuthkit.datamodel.Content;
|
||||||
|
@ -27,9 +27,9 @@ import org.openide.nodes.Children;
|
|||||||
import org.openide.nodes.Node;
|
import org.openide.nodes.Node;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.openide.util.lookup.Lookups;
|
import org.openide.util.lookup.Lookups;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.events.TimeLineEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.BaseTypes;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.BaseTypes;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
|
||||||
|
@ -78,7 +78,7 @@ public abstract class CompoundFilter<SubFilterType extends Filter> extends Abstr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static <SubFilterType extends Filter> boolean hashEqualSubFilters(final CompoundFilter<SubFilterType> oneFilter, final CompoundFilter<SubFilterType> otherFilter) {
|
static <SubFilterType extends Filter> boolean areSubFiltersEqual(final CompoundFilter<SubFilterType> oneFilter, final CompoundFilter<SubFilterType> otherFilter) {
|
||||||
if (oneFilter.getSubFilters().size() != otherFilter.getSubFilters().size()) {
|
if (oneFilter.getSubFilters().size() != otherFilter.getSubFilters().size()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -85,4 +85,5 @@ public class DataSourceFilter extends AbstractFilter {
|
|||||||
return isSelected() == other.isSelected();
|
return isSelected() == other.isSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.filters;
|
package org.sleuthkit.autopsy.timeline.filters;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
@ -38,7 +39,7 @@ public class DataSourcesFilter extends UnionFilter<DataSourceFilter> {
|
|||||||
filterCopy.setSelected(isSelected());
|
filterCopy.setSelected(isSelected());
|
||||||
//add a copy of each subfilter
|
//add a copy of each subfilter
|
||||||
this.getSubFilters().forEach((DataSourceFilter t) -> {
|
this.getSubFilters().forEach((DataSourceFilter t) -> {
|
||||||
filterCopy.addDataSourceFilter(t.copyOf());
|
filterCopy.addSubFilter(t.copyOf());
|
||||||
});
|
});
|
||||||
|
|
||||||
return filterCopy;
|
return filterCopy;
|
||||||
@ -63,13 +64,14 @@ public class DataSourcesFilter extends UnionFilter<DataSourceFilter> {
|
|||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDataSourceFilter(DataSourceFilter dataSourceFilter) {
|
public void addSubFilter(DataSourceFilter dataSourceFilter) {
|
||||||
if (getSubFilters().stream().map(DataSourceFilter.class::cast)
|
if (getSubFilters().stream().map(DataSourceFilter.class::cast)
|
||||||
.map(DataSourceFilter::getDataSourceID)
|
.map(DataSourceFilter::getDataSourceID)
|
||||||
.filter(t -> t == dataSourceFilter.getDataSourceID())
|
.filter(t -> t == dataSourceFilter.getDataSourceID())
|
||||||
.findAny().isPresent() == false) {
|
.findAny().isPresent() == false) {
|
||||||
dataSourceFilter.getDisabledProperty().bind(getDisabledProperty());
|
dataSourceFilter.getDisabledProperty().bind(getDisabledProperty());
|
||||||
getSubFilters().add(dataSourceFilter);
|
getSubFilters().add(dataSourceFilter);
|
||||||
|
getSubFilters().sort(Comparator.comparing(DataSourceFilter::getDisplayName));
|
||||||
}
|
}
|
||||||
if (getSubFilters().size() > 1) {
|
if (getSubFilters().size() > 1) {
|
||||||
setSelected(Boolean.TRUE);
|
setSelected(Boolean.TRUE);
|
||||||
@ -90,7 +92,7 @@ public class DataSourcesFilter extends UnionFilter<DataSourceFilter> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hashEqualSubFilters(this, other);
|
return areSubFiltersEqual(this, other);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,5 +100,4 @@ public class DataSourcesFilter extends UnionFilter<DataSourceFilter> {
|
|||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return 9;
|
return 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.filters;
|
package org.sleuthkit.autopsy.timeline.filters;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
@ -31,7 +32,7 @@ public class HashHitsFilter extends UnionFilter<HashSetFilter> {
|
|||||||
filterCopy.setSelected(isSelected());
|
filterCopy.setSelected(isSelected());
|
||||||
//add a copy of each subfilter
|
//add a copy of each subfilter
|
||||||
this.getSubFilters().forEach((HashSetFilter t) -> {
|
this.getSubFilters().forEach((HashSetFilter t) -> {
|
||||||
filterCopy.addHashSetFilter(t.copyOf());
|
filterCopy.addSubFilter(t.copyOf());
|
||||||
});
|
});
|
||||||
return filterCopy;
|
return filterCopy;
|
||||||
}
|
}
|
||||||
@ -68,15 +69,16 @@ public class HashHitsFilter extends UnionFilter<HashSetFilter> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return hashEqualSubFilters(this, other);
|
return areSubFiltersEqual(this, other);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addHashSetFilter(HashSetFilter hashSetFilter) {
|
public void addSubFilter(HashSetFilter hashSetFilter) {
|
||||||
if (getSubFilters().stream().map(HashSetFilter.class::cast)
|
if (getSubFilters().stream()
|
||||||
.map(HashSetFilter::getHashSetID)
|
.map(HashSetFilter::getHashSetID)
|
||||||
.filter(t -> t == hashSetFilter.getHashSetID())
|
.filter(t -> t == hashSetFilter.getHashSetID())
|
||||||
.findAny().isPresent() == false) {
|
.findAny().isPresent() == false) {
|
||||||
getSubFilters().add(hashSetFilter);
|
getSubFilters().add(hashSetFilter);
|
||||||
|
getSubFilters().sort(Comparator.comparing(HashSetFilter::getDisplayName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.timeline.filters;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter for an individual datasource
|
* Filter for an individual hash set
|
||||||
*/
|
*/
|
||||||
public class HashSetFilter extends AbstractFilter {
|
public class HashSetFilter extends AbstractFilter {
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import javafx.collections.FXCollections;
|
|||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Intersection(And) filter
|
* Intersection (And) filter
|
||||||
*/
|
*/
|
||||||
public class IntersectionFilter<S extends Filter> extends CompoundFilter<S> {
|
public class IntersectionFilter<S extends Filter> extends CompoundFilter<S> {
|
||||||
|
|
||||||
@ -60,7 +60,10 @@ public class IntersectionFilter<S extends Filter> extends CompoundFilter<S> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHTMLReportString() {
|
public String getHTMLReportString() {
|
||||||
return getSubFilters().stream().filter(Filter::isSelected).map(Filter::getHTMLReportString).collect(Collectors.joining("</li><li>", "<ul><li>", "</li></ul>")); // NON-NLS
|
return getSubFilters().stream()
|
||||||
|
.filter(Filter::isSelected)
|
||||||
|
.map(Filter::getHTMLReportString)
|
||||||
|
.collect(Collectors.joining("</li><li>", "<ul><li>", "</li></ul>")); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -26,7 +26,8 @@ import javafx.collections.FXCollections;
|
|||||||
*/
|
*/
|
||||||
public class RootFilter extends IntersectionFilter<Filter> {
|
public class RootFilter extends IntersectionFilter<Filter> {
|
||||||
|
|
||||||
private final HideKnownFilter knwonFilter;
|
private final HideKnownFilter knownFilter;
|
||||||
|
private final TagsFilter tagsFilter;
|
||||||
private final HashHitsFilter hashFilter;
|
private final HashHitsFilter hashFilter;
|
||||||
private final TextFilter textFilter;
|
private final TextFilter textFilter;
|
||||||
private final TypeFilter typeFilter;
|
private final TypeFilter typeFilter;
|
||||||
@ -36,13 +37,20 @@ public class RootFilter extends IntersectionFilter<Filter> {
|
|||||||
return dataSourcesFilter;
|
return dataSourcesFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TagsFilter getTagsFilter() {
|
||||||
|
return tagsFilter;
|
||||||
|
}
|
||||||
|
|
||||||
public HashHitsFilter getHashHitsFilter() {
|
public HashHitsFilter getHashHitsFilter() {
|
||||||
return hashFilter;
|
return hashFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RootFilter(HideKnownFilter knownFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter) {
|
public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter) {
|
||||||
super(FXCollections.observableArrayList(knownFilter, hashFilter, textFilter, dataSourceFilter, typeFilter));
|
super(FXCollections.observableArrayList(knownFilter, tagsFilter, hashFilter, textFilter, dataSourceFilter, typeFilter));
|
||||||
this.knwonFilter = knownFilter;
|
setSelected(Boolean.TRUE);
|
||||||
|
setDisabled(false);
|
||||||
|
this.knownFilter = knownFilter;
|
||||||
|
this.tagsFilter = tagsFilter;
|
||||||
this.hashFilter = hashFilter;
|
this.hashFilter = hashFilter;
|
||||||
this.textFilter = textFilter;
|
this.textFilter = textFilter;
|
||||||
this.typeFilter = typeFilter;
|
this.typeFilter = typeFilter;
|
||||||
@ -51,7 +59,7 @@ public class RootFilter extends IntersectionFilter<Filter> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RootFilter copyOf() {
|
public RootFilter copyOf() {
|
||||||
RootFilter filter = new RootFilter(knwonFilter.copyOf(), hashFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf());
|
RootFilter filter = new RootFilter(knownFilter.copyOf(), tagsFilter.copyOf(), hashFilter.copyOf(), textFilter.copyOf(), typeFilter.copyOf(), dataSourcesFilter.copyOf());
|
||||||
filter.setSelected(isSelected());
|
filter.setSelected(isSelected());
|
||||||
filter.setDisabled(isDisabled());
|
filter.setDisabled(isDisabled());
|
||||||
return filter;
|
return filter;
|
||||||
@ -71,6 +79,6 @@ public class RootFilter extends IntersectionFilter<Filter> {
|
|||||||
if (getClass() != obj.getClass()) {
|
if (getClass() != obj.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return hashEqualSubFilters(this, (CompoundFilter<Filter>) obj);
|
return areSubFiltersEqual(this, (CompoundFilter<Filter>) obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2015 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.filters;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||||
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
|
import org.sleuthkit.datamodel.TagName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter for an individual TagName
|
||||||
|
*/
|
||||||
|
public class TagNameFilter extends AbstractFilter {
|
||||||
|
|
||||||
|
private final TagName tagName;
|
||||||
|
private final Case autoCase;
|
||||||
|
private final TagsManager tagsManager;
|
||||||
|
private final SleuthkitCase sleuthkitCase;
|
||||||
|
|
||||||
|
public TagNameFilter(TagName tagName, Case autoCase) {
|
||||||
|
this.autoCase = autoCase;
|
||||||
|
sleuthkitCase = autoCase.getSleuthkitCase();
|
||||||
|
tagsManager = autoCase.getServices().getTagsManager();
|
||||||
|
this.tagName = tagName;
|
||||||
|
setSelected(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagName getTagName() {
|
||||||
|
return tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
synchronized public TagNameFilter copyOf() {
|
||||||
|
TagNameFilter filterCopy = new TagNameFilter(getTagName(), autoCase);
|
||||||
|
filterCopy.setSelected(isSelected());
|
||||||
|
filterCopy.setDisabled(isDisabled());
|
||||||
|
return filterCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return tagName.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHTMLReportString() {
|
||||||
|
return getDisplayName() + getStringCheckBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int hash = 3;
|
||||||
|
hash = 53 * hash + Objects.hashCode(this.tagName);
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final TagNameFilter other = (TagNameFilter) obj;
|
||||||
|
if (!Objects.equals(this.tagName, other.tagName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSelected() == other.isSelected();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package org.sleuthkit.autopsy.timeline.filters;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
|
import org.sleuthkit.datamodel.TagName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter to show only events tag with the tagNames of the selected subfilters.
|
||||||
|
*/
|
||||||
|
public class TagsFilter extends UnionFilter<TagNameFilter> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NbBundle.Messages("tagsFilter.displayName.text=Only Events Tagged")
|
||||||
|
public String getDisplayName() {
|
||||||
|
return Bundle.tagsFilter_displayName_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TagsFilter() {
|
||||||
|
getDisabledProperty().bind(Bindings.size(getSubFilters()).lessThan(1));
|
||||||
|
setSelected(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TagsFilter copyOf() {
|
||||||
|
TagsFilter filterCopy = new TagsFilter();
|
||||||
|
filterCopy.setSelected(isSelected());
|
||||||
|
//add a copy of each subfilter
|
||||||
|
this.getSubFilters().forEach((TagNameFilter t) -> {
|
||||||
|
filterCopy.addSubFilter(t.copyOf());
|
||||||
|
});
|
||||||
|
return filterCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHTMLReportString() {
|
||||||
|
//move this logic into SaveSnapshot
|
||||||
|
String string = getDisplayName() + getStringCheckBox();
|
||||||
|
if (getSubFilters().isEmpty() == false) {
|
||||||
|
string = string + " : " + getSubFilters().stream()
|
||||||
|
.filter(Filter::isSelected)
|
||||||
|
.map(Filter::getHTMLReportString)
|
||||||
|
.collect(Collectors.joining("</li><li>", "<ul><li>", "</li></ul>")); // NON-NLS
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final TagsFilter other = (TagsFilter) obj;
|
||||||
|
|
||||||
|
if (isSelected() != other.isSelected()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return areSubFiltersEqual(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSubFilter(TagNameFilter tagFilter) {
|
||||||
|
TagName newFilterTagName = tagFilter.getTagName();
|
||||||
|
if (getSubFilters().stream()
|
||||||
|
.map(TagNameFilter::getTagName)
|
||||||
|
.filter(newFilterTagName::equals)
|
||||||
|
.findAny().isPresent() == false) {
|
||||||
|
getSubFilters().add(tagFilter);
|
||||||
|
}
|
||||||
|
getSubFilters().sort(Comparator.comparing(TagNameFilter::getDisplayName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeFilterForTag(TagName tagName) {
|
||||||
|
getSubFilters().removeIf(subfilter -> subfilter.getTagName().equals(tagName));
|
||||||
|
getSubFilters().sort(Comparator.comparing(TagNameFilter::getDisplayName));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -91,5 +91,4 @@ public class TextFilter extends AbstractFilter {
|
|||||||
hash = 29 * hash + Objects.hashCode(this.text.get());
|
hash = 29 * hash + Objects.hashCode(this.text.get());
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,8 @@ import javafx.collections.FXCollections;
|
|||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event Type Filter. An instance of TypeFilter is usually a tree that parallels
|
* Event Type Filter. An instance of TypeFilter is usually a tree that parallels
|
||||||
@ -133,7 +133,7 @@ public class TypeFilter extends UnionFilter<TypeFilter> {
|
|||||||
if (this.eventType != other.eventType) {
|
if (this.eventType != other.eventType) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return hashEqualSubFilters(this, other);
|
return areSubFiltersEqual(this, other);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -142,5 +142,4 @@ public class TypeFilter extends UnionFilter<TypeFilter> {
|
|||||||
hash = 67 * hash + Objects.hashCode(this.eventType);
|
hash = 67 * hash + Objects.hashCode(this.eventType);
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -33,5 +33,4 @@ abstract public class UnionFilter<SubFilterType extends Filter> extends Compound
|
|||||||
public UnionFilter() {
|
public UnionFilter() {
|
||||||
super(FXCollections.<SubFilterType>observableArrayList());
|
super(FXCollections.<SubFilterType>observableArrayList());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.ui;
|
package org.sleuthkit.autopsy.timeline.ui;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.logging.Level;
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.property.ReadOnlyListProperty;
|
import javafx.beans.property.ReadOnlyListProperty;
|
||||||
@ -48,12 +50,14 @@ import javafx.scene.text.Font;
|
|||||||
import javafx.scene.text.FontWeight;
|
import javafx.scene.text.FontWeight;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.scene.text.TextAlignment;
|
import javafx.scene.text.TextAlignment;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.openide.util.Exceptions;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
|
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for {@link Chart} based {@link TimeLineView}s used in the
|
* Abstract base class for {@link Chart} based {@link TimeLineView}s used in the
|
||||||
@ -169,8 +173,8 @@ public abstract class AbstractVisualization<X, Y, N extends Node, C extends XYCh
|
|||||||
protected abstract Axis<Y> getYAxis();
|
protected abstract Axis<Y> getYAxis();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* * update this visualization based on current state of zoom /
|
* update this visualization based on current state of zoom /
|
||||||
* filters.Primarily this invokes the background {@link Task} returned by
|
* filters. Primarily this invokes the background {@link Task} returned by
|
||||||
* {@link #getUpdateTask()} which derived classes must implement.
|
* {@link #getUpdateTask()} which derived classes must implement.
|
||||||
*/
|
*/
|
||||||
synchronized public void update() {
|
synchronized public void update() {
|
||||||
@ -191,7 +195,7 @@ public abstract class AbstractVisualization<X, Y, N extends Node, C extends XYCh
|
|||||||
try {
|
try {
|
||||||
this.hasEvents.set(updateTask.get());
|
this.hasEvents.set(updateTask.get());
|
||||||
} catch (InterruptedException | ExecutionException ex) {
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
Exceptions.printStackTrace(ex);
|
Logger.getLogger(AbstractVisualization.class.getName()).log(Level.SEVERE, "Unexpected exception updating visualization", ex);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -203,7 +207,7 @@ public abstract class AbstractVisualization<X, Y, N extends Node, C extends XYCh
|
|||||||
if (updateTask != null) {
|
if (updateTask != null) {
|
||||||
updateTask.cancel(true);
|
updateTask.cancel(true);
|
||||||
}
|
}
|
||||||
this.filteredEvents.getRequestedZoomParamters().removeListener(invalidationListener);
|
this.filteredEvents.zoomParametersProperty().removeListener(invalidationListener);
|
||||||
invalidationListener = null;
|
invalidationListener = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,10 +240,23 @@ public abstract class AbstractVisualization<X, Y, N extends Node, C extends XYCh
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
synchronized public void setModel(FilteredEventsModel filteredEvents) {
|
synchronized public void setModel(@Nonnull FilteredEventsModel filteredEvents) {
|
||||||
|
|
||||||
|
if (this.filteredEvents != null && this.filteredEvents != filteredEvents) {
|
||||||
|
this.filteredEvents.unRegisterForEvents(this);
|
||||||
|
this.filteredEvents.zoomParametersProperty().removeListener(invalidationListener);
|
||||||
|
}
|
||||||
|
if (this.filteredEvents != filteredEvents) {
|
||||||
|
filteredEvents.registerForEvents(this);
|
||||||
|
filteredEvents.zoomParametersProperty().addListener(invalidationListener);
|
||||||
|
}
|
||||||
this.filteredEvents = filteredEvents;
|
this.filteredEvents = filteredEvents;
|
||||||
|
|
||||||
this.filteredEvents.getRequestedZoomParamters().addListener(invalidationListener);
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void handleRefreshRequested(RefreshRequestedEvent event) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,15 +6,28 @@
|
|||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" type="ToolBar" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<items><Label fx:id="refreshLabel">
|
<items>
|
||||||
<graphic><ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
<Label fx:id="refreshLabel">
|
||||||
<image>
|
<graphic>
|
||||||
<Image url="@../images/info-icon-16.png" />
|
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||||
</image></ImageView>
|
<image>
|
||||||
</graphic></Label><Separator orientation="VERTICAL" /><Region fx:id="spacer" /><Separator orientation="VERTICAL" /><Label fx:id="taskLabel" contentDisplay="RIGHT">
|
<Image url="@../images/info-icon-16.png" />
|
||||||
<graphic><StackPane>
|
</image>
|
||||||
<children><ProgressBar fx:id="progressBar" progress="0.0" /><Label fx:id="messageLabel" />
|
</ImageView>
|
||||||
</children></StackPane>
|
</graphic>
|
||||||
</graphic></Label>
|
</Label>
|
||||||
</items>
|
<Separator orientation="VERTICAL" />
|
||||||
|
<Region fx:id="spacer" maxWidth="1.7976931348623157E308" />
|
||||||
|
<Separator orientation="VERTICAL" />
|
||||||
|
<Label fx:id="taskLabel" contentDisplay="RIGHT">
|
||||||
|
<graphic>
|
||||||
|
<StackPane>
|
||||||
|
<children>
|
||||||
|
<ProgressBar fx:id="progressBar" progress="0.0" />
|
||||||
|
<Label fx:id="messageLabel" />
|
||||||
|
</children>
|
||||||
|
</StackPane>
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
|
</items>
|
||||||
</fx:root>
|
</fx:root>
|
||||||
|
@ -27,7 +27,7 @@ import org.openide.nodes.Node;
|
|||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
|
import org.sleuthkit.autopsy.timeline.explorernodes.EventRootNode;
|
||||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
|
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent;
|
||||||
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.ui;
|
package org.sleuthkit.autopsy.timeline.ui;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@ -45,6 +46,8 @@ import javafx.scene.control.ToggleButton;
|
|||||||
import javafx.scene.control.ToolBar;
|
import javafx.scene.control.ToolBar;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.effect.Lighting;
|
import javafx.scene.effect.Lighting;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.image.WritableImage;
|
import javafx.scene.image.WritableImage;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.layout.Background;
|
import javafx.scene.layout.Background;
|
||||||
@ -60,6 +63,7 @@ import javafx.scene.layout.StackPane;
|
|||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import jfxtras.scene.control.LocalDateTimeTextField;
|
import jfxtras.scene.control.LocalDateTimeTextField;
|
||||||
|
import org.controlsfx.control.NotificationPane;
|
||||||
import org.controlsfx.control.RangeSlider;
|
import org.controlsfx.control.RangeSlider;
|
||||||
import org.controlsfx.control.action.Action;
|
import org.controlsfx.control.action.Action;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
@ -75,7 +79,11 @@ import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
|||||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshot;
|
import org.sleuthkit.autopsy.timeline.actions.SaveSnapshot;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.ZoomOut;
|
import org.sleuthkit.autopsy.timeline.actions.ZoomOut;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
|
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
|
||||||
|
import org.sleuthkit.autopsy.timeline.filters.TagsFilter;
|
||||||
|
import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_refresh;
|
||||||
|
import static org.sleuthkit.autopsy.timeline.ui.Bundle.VisualizationPanel_tagsAddedOrDeleted;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavPanel;
|
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavPanel;
|
||||||
@ -91,27 +99,24 @@ import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
|||||||
*/
|
*/
|
||||||
public class VisualizationPanel extends BorderPane implements TimeLineView {
|
public class VisualizationPanel extends BorderPane implements TimeLineView {
|
||||||
|
|
||||||
@GuardedBy("this")
|
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
|
||||||
private LoggedTask<Void> histogramTask;
|
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(VisualizationPanel.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(VisualizationPanel.class.getName());
|
||||||
|
|
||||||
|
@GuardedBy("this")
|
||||||
|
private LoggedTask<Void> histogramTask;
|
||||||
|
|
||||||
private final NavPanel navPanel;
|
private final NavPanel navPanel;
|
||||||
|
|
||||||
private AbstractVisualization<?, ?, ?, ?> visualization;
|
private AbstractVisualization<?, ?, ?, ?> visualization;
|
||||||
|
|
||||||
@FXML // ResourceBundle that was given to the FXMLLoader
|
|
||||||
private ResourceBundle resources;
|
|
||||||
|
|
||||||
@FXML // URL location of the FXML file that was given to the FXMLLoader
|
|
||||||
private URL location;
|
|
||||||
|
|
||||||
//// range slider and histogram componenets
|
//// range slider and histogram componenets
|
||||||
@FXML // fx:id="histogramBox"
|
@FXML
|
||||||
protected HBox histogramBox; // Value injected by FXMLLoader
|
protected HBox histogramBox;
|
||||||
|
|
||||||
@FXML // fx:id="rangeHistogramStack"
|
@FXML
|
||||||
protected StackPane rangeHistogramStack; // Value injected by FXMLLoader
|
protected StackPane rangeHistogramStack;
|
||||||
|
|
||||||
private final RangeSlider rangeSlider = new RangeSlider(0, 1.0, .25, .75);
|
private final RangeSlider rangeSlider = new RangeSlider(0, 1.0, .25, .75);
|
||||||
|
|
||||||
@ -167,7 +172,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
@FXML
|
@FXML
|
||||||
private Label endLabel;
|
private Label endLabel;
|
||||||
|
|
||||||
private double preDragPos;
|
private final NotificationPane notificationPane = new NotificationPane();
|
||||||
|
|
||||||
protected TimeLineController controller;
|
protected TimeLineController controller;
|
||||||
|
|
||||||
@ -186,14 +191,14 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
|
|
||||||
private final InvalidationListener endListener = (Observable observable) -> {
|
private final InvalidationListener endListener = (Observable observable) -> {
|
||||||
if (endPicker.getLocalDateTime() != null) {
|
if (endPicker.getLocalDateTime() != null) {
|
||||||
controller.pushTimeRange(VisualizationPanel.this.filteredEvents.timeRange().get().withEndMillis(
|
controller.pushTimeRange(VisualizationPanel.this.filteredEvents.timeRangeProperty().get().withEndMillis(
|
||||||
ZonedDateTime.of(endPicker.getLocalDateTime(), TimeLineController.getTimeZoneID()).toInstant().toEpochMilli()));
|
ZonedDateTime.of(endPicker.getLocalDateTime(), TimeLineController.getTimeZoneID()).toInstant().toEpochMilli()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final InvalidationListener startListener = (Observable observable) -> {
|
private final InvalidationListener startListener = (Observable observable) -> {
|
||||||
if (startPicker.getLocalDateTime() != null) {
|
if (startPicker.getLocalDateTime() != null) {
|
||||||
controller.pushTimeRange(VisualizationPanel.this.filteredEvents.timeRange().get().withStartMillis(
|
controller.pushTimeRange(VisualizationPanel.this.filteredEvents.timeRangeProperty().get().withStartMillis(
|
||||||
ZonedDateTime.of(startPicker.getLocalDateTime(), TimeLineController.getTimeZoneID()).toInstant().toEpochMilli()));
|
ZonedDateTime.of(startPicker.getLocalDateTime(), TimeLineController.getTimeZoneID()).toInstant().toEpochMilli()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -208,6 +213,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@FXML // This method is called by the FXMLLoader when initialization is complete
|
@FXML // This method is called by the FXMLLoader when initialization is complete
|
||||||
|
@NbBundle.Messages("VisualizationPanel.refresh=refresh")
|
||||||
protected void initialize() {
|
protected void initialize() {
|
||||||
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
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
|
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
|
||||||
@ -216,10 +222,20 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
assert countsToggle != null : "fx:id=\"countsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
|
assert countsToggle != null : "fx:id=\"countsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
|
||||||
assert detailsToggle != null : "fx:id=\"eventsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
|
assert detailsToggle != null : "fx:id=\"eventsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
|
||||||
|
|
||||||
visualizationModeLabel.setText(
|
notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
|
||||||
NbBundle.getMessage(this.getClass(), "VisualizationPanel.visualizationModeLabel.text"));
|
notificationPane.getActions().setAll(new Action(VisualizationPanel_refresh()) {
|
||||||
startLabel.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.startLabel.text"));
|
{
|
||||||
endLabel.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.endLabel.text"));
|
setGraphic(new ImageView(REFRESH));
|
||||||
|
setEventHandler((ActionEvent t) -> {
|
||||||
|
filteredEvents.refresh();
|
||||||
|
notificationPane.hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setCenter(notificationPane);
|
||||||
|
visualizationModeLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.visualizationModeLabel.text")); // NON-NLS
|
||||||
|
startLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.startLabel.text")); // NON-NLS
|
||||||
|
endLabel.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.endLabel.text")); // NON-NLS
|
||||||
|
|
||||||
HBox.setHgrow(leftSeperator, Priority.ALWAYS);
|
HBox.setHgrow(leftSeperator, Priority.ALWAYS);
|
||||||
HBox.setHgrow(rightSeperator, Priority.ALWAYS);
|
HBox.setHgrow(rightSeperator, Priority.ALWAYS);
|
||||||
@ -242,26 +258,12 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
|
countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
countsToggle.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.countsToggle.text"));
|
countsToggle.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.countsToggle.text")); // NON-NLS
|
||||||
detailsToggle.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.detailsToggle.text"));
|
detailsToggle.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.detailsToggle.text")); // NON-NLS
|
||||||
|
|
||||||
//setup rangeslider
|
//setup rangeslider
|
||||||
rangeSlider.setOpacity(.7);
|
rangeSlider.setOpacity(.7);
|
||||||
rangeSlider.setMin(0);
|
rangeSlider.setMin(0);
|
||||||
|
|
||||||
// /** this is still needed to not get swamped by low/high value changes.
|
|
||||||
// * https://bitbucket.org/controlsfx/controlsfx/issue/241/rangeslider-high-low-properties
|
|
||||||
// * TODO: committ an appropriate version of this fix to the ControlsFX
|
|
||||||
// * repo on bitbucket, remove this after next release -jm */
|
|
||||||
// Skin<?> skin = rangeSlider.getSkin();
|
|
||||||
// if (skin != null) {
|
|
||||||
// attachDragListener((RangeSliderSkin) skin);
|
|
||||||
// } else {
|
|
||||||
// rangeSlider.skinProperty().addListener((Observable observable) -> {
|
|
||||||
// RangeSliderSkin skin1 = (RangeSliderSkin) rangeSlider.getSkin();
|
|
||||||
// attachDragListener(skin1);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
rangeSlider.setBlockIncrement(1);
|
rangeSlider.setBlockIncrement(1);
|
||||||
|
|
||||||
rangeHistogramStack.getChildren().add(rangeSlider);
|
rangeHistogramStack.getChildren().add(rangeSlider);
|
||||||
@ -275,7 +277,6 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
|
|
||||||
zoomMenuButton.getItems().clear();
|
zoomMenuButton.getItems().clear();
|
||||||
for (ZoomRanges b : ZoomRanges.values()) {
|
for (ZoomRanges b : ZoomRanges.values()) {
|
||||||
|
|
||||||
MenuItem menuItem = new MenuItem(b.getDisplayName());
|
MenuItem menuItem = new MenuItem(b.getDisplayName());
|
||||||
menuItem.setOnAction((event) -> {
|
menuItem.setOnAction((event) -> {
|
||||||
if (b != ZoomRanges.ALL) {
|
if (b != ZoomRanges.ALL) {
|
||||||
@ -286,7 +287,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
});
|
});
|
||||||
zoomMenuButton.getItems().add(menuItem);
|
zoomMenuButton.getItems().add(menuItem);
|
||||||
}
|
}
|
||||||
zoomMenuButton.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.zoomMenuButton.text"));
|
zoomMenuButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.zoomMenuButton.text")); // NON-NLS
|
||||||
|
|
||||||
zoomOutButton.setOnAction(e -> {
|
zoomOutButton.setOnAction(e -> {
|
||||||
controller.pushZoomOutTime();
|
controller.pushZoomOutTime();
|
||||||
@ -307,69 +308,14 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
new SaveSnapshot(controller, snapshot).handle(event);
|
new SaveSnapshot(controller, snapshot).handle(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
snapShotButton.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.snapShotButton.text"));
|
snapShotButton.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.snapShotButton.text")); // NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * TODO: committed an appropriate version of this fix to the ControlsFX repo
|
|
||||||
// * on bitbucket, remove this after next release -jm
|
|
||||||
// *
|
|
||||||
// * @param skin
|
|
||||||
// */
|
|
||||||
// private void attachDragListener(RangeSliderSkin skin) {
|
|
||||||
// if (skin != null) {
|
|
||||||
// for (Node n : skin.getChildren()) {
|
|
||||||
// if (n.getStyleClass().contains("track")) {
|
|
||||||
// n.setOpacity(.3);
|
|
||||||
// }
|
|
||||||
// if (n.getStyleClass().contains("range-bar")) {
|
|
||||||
// StackPane rangeBar = (StackPane) n;
|
|
||||||
// rangeBar.setOnMousePressed((MouseEvent e) -> {
|
|
||||||
// rangeBar.requestFocus();
|
|
||||||
// preDragPos = e.getX();
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// //don't mark as not changing until mouse is released
|
|
||||||
// rangeBar.setOnMouseReleased((MouseEvent event) -> {
|
|
||||||
// rangeSlider.setLowValueChanging(false);
|
|
||||||
// rangeSlider.setHighValueChanging(false);
|
|
||||||
// });
|
|
||||||
// rangeBar.setOnMouseDragged((MouseEvent event) -> {
|
|
||||||
// final double min = rangeSlider.getMin();
|
|
||||||
// final double max = rangeSlider.getMax();
|
|
||||||
//
|
|
||||||
// ///!!! compensate for range and width so that rangebar actualy stays with the slider
|
|
||||||
// double delta = (event.getX() - preDragPos) * (max - min) / rangeSlider.
|
|
||||||
// getWidth();
|
|
||||||
// ////////////////////////////////////////////////////
|
|
||||||
//
|
|
||||||
// final double lowValue = rangeSlider.getLowValue();
|
|
||||||
// final double newLowValue = Math.min(Math.max(min, lowValue + delta),
|
|
||||||
// max);
|
|
||||||
// final double highValue = rangeSlider.getHighValue();
|
|
||||||
// final double newHighValue = Math.min(Math.max(min, highValue + delta),
|
|
||||||
// max);
|
|
||||||
//
|
|
||||||
// if (newLowValue <= min || newHighValue >= max) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// rangeSlider.setLowValueChanging(true);
|
|
||||||
// rangeSlider.setHighValueChanging(true);
|
|
||||||
// rangeSlider.setLowValue(newLowValue);
|
|
||||||
// rangeSlider.setHighValue(newHighValue);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void setController(TimeLineController controller) {
|
public synchronized void setController(TimeLineController controller) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
setModel(controller.getEventsModel());
|
setModel(controller.getEventsModel());
|
||||||
|
|
||||||
setViewMode(controller.getViewMode().get());
|
setViewMode(controller.getViewMode().get());
|
||||||
|
|
||||||
controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
refreshHistorgram();
|
refreshHistorgram();
|
||||||
@ -379,9 +325,29 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
controller.getViewMode().addListener((ObservableValue<? extends VisualizationMode> ov, VisualizationMode t, VisualizationMode t1) -> {
|
controller.getViewMode().addListener((ObservableValue<? extends VisualizationMode> ov, VisualizationMode t, VisualizationMode t1) -> {
|
||||||
setViewMode(t1);
|
setViewMode(t1);
|
||||||
});
|
});
|
||||||
|
TimeLineController.getTimeZone().addListener(timeRangeInvalidationListener);
|
||||||
refreshHistorgram();
|
refreshHistorgram();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setModel(FilteredEventsModel filteredEvents) {
|
||||||
|
if (this.filteredEvents != null && this.filteredEvents != filteredEvents) {
|
||||||
|
this.filteredEvents.unRegisterForEvents(this);
|
||||||
|
this.filteredEvents.timeRangeProperty().removeListener(timeRangeInvalidationListener);
|
||||||
|
this.filteredEvents.zoomParametersProperty().removeListener(zoomListener);
|
||||||
|
}
|
||||||
|
if (this.filteredEvents != filteredEvents) {
|
||||||
|
filteredEvents.registerForEvents(this);
|
||||||
|
filteredEvents.timeRangeProperty().addListener(timeRangeInvalidationListener);
|
||||||
|
filteredEvents.zoomParametersProperty().addListener(zoomListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filteredEvents = filteredEvents;
|
||||||
|
|
||||||
|
refreshTimeUI(filteredEvents.timeRangeProperty().get());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void setViewMode(VisualizationMode visualizationMode) {
|
private void setViewMode(VisualizationMode visualizationMode) {
|
||||||
switch (visualizationMode) {
|
switch (visualizationMode) {
|
||||||
case COUNTS:
|
case COUNTS:
|
||||||
@ -407,29 +373,40 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
toolBar.getItems().addAll(newViz.getSettingsNodes());
|
toolBar.getItems().addAll(newViz.getSettingsNodes());
|
||||||
|
|
||||||
visualization.setController(controller);
|
visualization.setController(controller);
|
||||||
setCenter(visualization);
|
notificationPane.setContent(visualization);
|
||||||
if (visualization instanceof DetailViewPane) {
|
if (visualization instanceof DetailViewPane) {
|
||||||
navPanel.setChart((DetailViewPane) visualization);
|
navPanel.setChart((DetailViewPane) visualization);
|
||||||
}
|
}
|
||||||
visualization.hasEvents.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
visualization.hasEvents.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
|
||||||
if (newValue == false) {
|
if (newValue == false) {
|
||||||
|
|
||||||
setCenter(new StackPane(visualization, new Region() {
|
notificationPane.setContent(new StackPane(visualization, new Region() {
|
||||||
{
|
{
|
||||||
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
|
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||||
setOpacity(.3);
|
setOpacity(.3);
|
||||||
}
|
}
|
||||||
}, new NoEventsDialog(() -> {
|
}, new NoEventsDialog(() -> {
|
||||||
setCenter(visualization);
|
notificationPane.setContent(visualization);
|
||||||
})));
|
})));
|
||||||
} else {
|
} else {
|
||||||
setCenter(visualization);
|
notificationPane.setContent(visualization);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
@NbBundle.Messages("VisualizationPanel.tagsAddedOrDeleted=Tags have been created and/or deleted. The visualization may not be up to date.")
|
||||||
|
public void handleTimeLineTagEvent(TagsUpdatedEvent event) {
|
||||||
|
TagsFilter tagsFilter = filteredEvents.getFilter().getTagsFilter();
|
||||||
|
if (tagsFilter.isSelected() && tagsFilter.isDisabled() == false) {
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
notificationPane.show(VisualizationPanel_tagsAddedOrDeleted(), new ImageView(INFORMATION));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
synchronized private void refreshHistorgram() {
|
synchronized private void refreshHistorgram() {
|
||||||
|
|
||||||
if (histogramTask != null) {
|
if (histogramTask != null) {
|
||||||
@ -437,12 +414,12 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
histogramTask = new LoggedTask<Void>(
|
histogramTask = new LoggedTask<Void>(
|
||||||
NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.title"), true) {
|
NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.title"), true) { // NON-NLS
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void call() throws Exception {
|
protected Void call() throws Exception {
|
||||||
|
|
||||||
updateMessage(NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.preparing"));
|
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.preparing")); // NON-NLS
|
||||||
|
|
||||||
long max = 0;
|
long max = 0;
|
||||||
final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval());
|
final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval());
|
||||||
@ -455,7 +432,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
|
|
||||||
//clear old data, and reset ranges and series
|
//clear old data, and reset ranges and series
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
updateMessage(NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.resetUI"));
|
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.resetUI")); // NON-NLS
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -472,7 +449,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
|
|
||||||
start = end;
|
start = end;
|
||||||
|
|
||||||
updateMessage(NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.queryDb"));
|
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.queryDb")); // NON-NLS
|
||||||
//query for current range
|
//query for current range
|
||||||
long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum();
|
long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum();
|
||||||
bins.add(count);
|
bins.add(count);
|
||||||
@ -482,7 +459,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
final double fMax = Math.log(max);
|
final double fMax = Math.log(max);
|
||||||
final ArrayList<Long> fbins = new ArrayList<>(bins);
|
final ArrayList<Long> fbins = new ArrayList<>(bins);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
updateMessage(NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.updateUI2"));
|
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.updateUI2")); // NON-NLS
|
||||||
|
|
||||||
histogramBox.getChildren().clear();
|
histogramBox.getChildren().clear();
|
||||||
|
|
||||||
@ -514,18 +491,13 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
controller.monitorTask(histogramTask);
|
controller.monitorTask(histogramTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private InvalidationListener timeRangeInvalidationListener = (Observable observable) -> {
|
||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
refreshTimeUI(filteredEvents.timeRangeProperty().get());
|
||||||
this.filteredEvents = filteredEvents;
|
};
|
||||||
|
|
||||||
refreshTimeUI(filteredEvents.timeRange().get());
|
private InvalidationListener zoomListener = (Observable observable) -> {
|
||||||
this.filteredEvents.timeRange().addListener((Observable observable) -> {
|
notificationPane.hide();
|
||||||
refreshTimeUI(filteredEvents.timeRange().get());
|
};
|
||||||
});
|
|
||||||
TimeLineController.getTimeZone().addListener((Observable observable) -> {
|
|
||||||
refreshTimeUI(filteredEvents.timeRange().get());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshTimeUI(Interval interval) {
|
private void refreshTimeUI(Interval interval) {
|
||||||
RangeDivisionInfo rangeDivisionInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval());
|
RangeDivisionInfo rangeDivisionInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.getSpanningInterval());
|
||||||
@ -589,9 +561,8 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
assert dismissButton != null : "fx:id=\"dismissButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
|
assert dismissButton != null : "fx:id=\"dismissButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
|
||||||
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
|
assert zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
|
||||||
|
|
||||||
noEventsDialogLabel.setText(
|
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS
|
||||||
NbBundle.getMessage(this.getClass(), "VisualizationPanel.noEventsDialogLabel.text"));
|
zoomButton.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.zoomButton.text")); // NON-NLS
|
||||||
zoomButton.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.zoomButton.text"));
|
|
||||||
|
|
||||||
Action zoomOutAction = new ZoomOut(controller);
|
Action zoomOutAction = new ZoomOut(controller);
|
||||||
zoomButton.setOnAction(zoomOutAction);
|
zoomButton.setOnAction(zoomOutAction);
|
||||||
@ -603,8 +574,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
|
|||||||
Action defaultFiltersAction = new ResetFilters(controller);
|
Action defaultFiltersAction = new ResetFilters(controller);
|
||||||
resetFiltersButton.setOnAction(defaultFiltersAction);
|
resetFiltersButton.setOnAction(defaultFiltersAction);
|
||||||
resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
|
||||||
resetFiltersButton.setText(
|
resetFiltersButton.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.resetFiltersButton.text")); // NON-NLS
|
||||||
NbBundle.getMessage(this.getClass(), "VisualizationPanel.resetFiltersButton.text"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,13 @@ import javafx.scene.chart.CategoryAxis;
|
|||||||
import javafx.scene.chart.NumberAxis;
|
import javafx.scene.chart.NumberAxis;
|
||||||
import javafx.scene.chart.StackedBarChart;
|
import javafx.scene.chart.StackedBarChart;
|
||||||
import javafx.scene.chart.XYChart;
|
import javafx.scene.chart.XYChart;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.ContextMenu;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.scene.control.RadioButton;
|
||||||
|
import javafx.scene.control.SeparatorMenuItem;
|
||||||
|
import javafx.scene.control.ToggleGroup;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.effect.DropShadow;
|
import javafx.scene.effect.DropShadow;
|
||||||
import javafx.scene.effect.Effect;
|
import javafx.scene.effect.Effect;
|
||||||
import javafx.scene.effect.Lighting;
|
import javafx.scene.effect.Lighting;
|
||||||
@ -64,9 +70,9 @@ import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|||||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
|
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||||
|
|
||||||
@ -141,7 +147,7 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
|||||||
setCursor(Cursor.WAIT);
|
setCursor(Cursor.WAIT);
|
||||||
});
|
});
|
||||||
|
|
||||||
final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.timeRange().get());
|
final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.timeRangeProperty().get());
|
||||||
chart.setRangeInfo(rangeInfo);
|
chart.setRangeInfo(rangeInfo);
|
||||||
//extend range to block bounderies (ie day, month, year)
|
//extend range to block bounderies (ie day, month, year)
|
||||||
final long lowerBound = rangeInfo.getLowerBound();
|
final long lowerBound = rangeInfo.getLowerBound();
|
||||||
@ -304,6 +310,9 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected NumberAxis getYAxis() {
|
protected NumberAxis getYAxis() {
|
||||||
return countAxis;
|
return countAxis;
|
||||||
|
@ -36,7 +36,7 @@ import org.openide.util.NbBundle;
|
|||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
public void setModel(FilteredEventsModel filteredEvents) {
|
||||||
filteredEvents.getRequestedZoomParamters().addListener(o -> {
|
filteredEvents.zoomParametersProperty().addListener(o -> {
|
||||||
clearIntervalSelector();
|
clearIntervalSelector();
|
||||||
controller.selectEventIDs(Collections.emptyList());
|
controller.selectEventIDs(Collections.emptyList());
|
||||||
});
|
});
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.timeline.ui.detailview;
|
package org.sleuthkit.autopsy.timeline.ui.detailview;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -63,21 +62,16 @@ import org.openide.util.NbBundle;
|
|||||||
import org.sleuthkit.autopsy.coreutils.ColorUtilities;
|
import org.sleuthkit.autopsy.coreutils.ColorUtilities;
|
||||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.events.EventsTaggedEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.events.EventsUnTaggedEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
|
||||||
import org.sleuthkit.autopsy.timeline.events.TimeLineEvent;
|
|
||||||
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
import org.sleuthkit.autopsy.timeline.zooming.DescriptionLOD;
|
||||||
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifactTag;
|
|
||||||
import org.sleuthkit.datamodel.ContentTag;
|
|
||||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
@ -302,27 +296,8 @@ public class AggregateEventNode extends StackPane {
|
|||||||
|
|
||||||
Map<String, Long> tagCounts = new HashMap<>();
|
Map<String, Long> tagCounts = new HashMap<>();
|
||||||
if (!aggEvent.getEventIDsWithTags().isEmpty()) {
|
if (!aggEvent.getEventIDsWithTags().isEmpty()) {
|
||||||
try {
|
tagCounts.putAll( eventsModel.getTagCountsByTagName(aggEvent.getEventIDsWithTags()));
|
||||||
for (TimeLineEvent tle : eventsModel.getEventsById(aggEvent.getEventIDsWithTags())) {
|
|
||||||
|
|
||||||
AbstractFile abstractFileById = sleuthkitCase.getAbstractFileById(tle.getFileID());
|
|
||||||
List<ContentTag> contentTags = sleuthkitCase.getContentTagsByContent(abstractFileById);
|
|
||||||
for (ContentTag tag : contentTags) {
|
|
||||||
tagCounts.merge(tag.getName().getDisplayName(), 1l, Long::sum);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long artifactID = tle.getArtifactID();
|
|
||||||
if (artifactID != 0) {
|
|
||||||
BlackboardArtifact blackboardArtifact = sleuthkitCase.getBlackboardArtifact(artifactID);
|
|
||||||
List<BlackboardArtifactTag> artifactTags = sleuthkitCase.getBlackboardArtifactTagsByArtifact(blackboardArtifact);
|
|
||||||
for (BlackboardArtifactTag tag : artifactTags) {
|
|
||||||
tagCounts.merge(tag.getName().getDisplayName(), 1l, Long::sum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
LOGGER.log(Level.SEVERE, "Error getting tag info for event.", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String hashSetCountsString = hashSetCounts.entrySet().stream()
|
String hashSetCountsString = hashSetCounts.entrySet().stream()
|
||||||
@ -375,6 +350,7 @@ public class AggregateEventNode extends StackPane {
|
|||||||
/**
|
/**
|
||||||
* @param descrVis the level of description that should be displayed
|
* @param descrVis the level of description that should be displayed
|
||||||
*/
|
*/
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
synchronized final void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
synchronized final void setDescriptionVisibility(DescriptionVisibility descrVis) {
|
||||||
this.descrVis = descrVis;
|
this.descrVis = descrVis;
|
||||||
final int size = aggEvent.getEventIDs().size();
|
final int size = aggEvent.getEventIDs().size();
|
||||||
@ -469,7 +445,7 @@ public class AggregateEventNode extends StackPane {
|
|||||||
chart.setRequiresLayout(true);
|
chart.setRequiresLayout(true);
|
||||||
chart.requestChartLayout();
|
chart.requestChartLayout();
|
||||||
} else {
|
} else {
|
||||||
RootFilter combinedFilter = eventsModel.filter().get().copyOf();
|
RootFilter combinedFilter = eventsModel.filterProperty().get().copyOf();
|
||||||
//make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters
|
//make a new filter intersecting the global filter with text(description) and type filters to restrict sub-clusters
|
||||||
combinedFilter.getSubFilters().addAll(new TextFilter(aggEvent.getDescription()),
|
combinedFilter.getSubFilters().addAll(new TextFilter(aggEvent.getDescription()),
|
||||||
new TypeFilter(aggEvent.getType()));
|
new TypeFilter(aggEvent.getType()));
|
||||||
@ -485,7 +461,7 @@ public class AggregateEventNode extends StackPane {
|
|||||||
protected List<AggregateEventNode> call() throws Exception {
|
protected List<AggregateEventNode> call() throws Exception {
|
||||||
//query for the sub-clusters
|
//query for the sub-clusters
|
||||||
List<AggregateEvent> aggregatedEvents = eventsModel.getAggregatedEvents(new ZoomParams(span,
|
List<AggregateEvent> aggregatedEvents = eventsModel.getAggregatedEvents(new ZoomParams(span,
|
||||||
eventsModel.eventTypeZoom().get(),
|
eventsModel.eventTypeZoomProperty().get(),
|
||||||
combinedFilter,
|
combinedFilter,
|
||||||
newDescriptionLOD));
|
newDescriptionLOD));
|
||||||
//for each sub cluster make an AggregateEventNode to visually represent it, and set x-position
|
//for each sub cluster make an AggregateEventNode to visually represent it, and set x-position
|
||||||
@ -544,30 +520,4 @@ public class AggregateEventNode extends StackPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void handleEventsUnTagged(EventsUnTaggedEvent tagEvent) {
|
|
||||||
AggregateEvent withTagsRemoved = aggEvent.withTagsRemoved(tagEvent.getEventIDs());
|
|
||||||
if (withTagsRemoved != aggEvent) {
|
|
||||||
aggEvent = withTagsRemoved;
|
|
||||||
tooltip = null;
|
|
||||||
boolean hasTags = aggEvent.getEventIDsWithTags().isEmpty() == false;
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
tagIV.setManaged(hasTags);
|
|
||||||
tagIV.setVisible(hasTags);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
synchronized void handleEventsTagged(EventsTaggedEvent tagEvent) {
|
|
||||||
AggregateEvent withTagsAdded = aggEvent.withTagsAdded(tagEvent.getEventIDs());
|
|
||||||
if (withTagsAdded != aggEvent) {
|
|
||||||
aggEvent = withTagsAdded;
|
|
||||||
tooltip = null;
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
tagIV.setManaged(true);
|
|
||||||
tagIV.setVisible(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -67,11 +67,12 @@ import org.joda.time.DateTime;
|
|||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
|
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavTreeNode;
|
import org.sleuthkit.autopsy.timeline.ui.detailview.tree.NavTreeNode;
|
||||||
@ -220,6 +221,11 @@ public class DetailViewPane extends AbstractVisualization<DateTime, AggregateEve
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setModel(FilteredEventsModel filteredEvents) {
|
||||||
|
super.setModel(filteredEvents);
|
||||||
|
}
|
||||||
|
|
||||||
private void incrementScrollValue(int factor) {
|
private void incrementScrollValue(int factor) {
|
||||||
vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() + factor * (chart.getHeight() / chart.getMaxVScroll().get()))));
|
vertScrollBar.valueProperty().set(Math.max(0, Math.min(100, vertScrollBar.getValue() + factor * (chart.getHeight() / chart.getMaxVScroll().get()))));
|
||||||
}
|
}
|
||||||
@ -272,6 +278,7 @@ public class DetailViewPane extends AbstractVisualization<DateTime, AggregateEve
|
|||||||
* @return a Series object to contain all the events with the given
|
* @return a Series object to contain all the events with the given
|
||||||
* EventType
|
* EventType
|
||||||
*/
|
*/
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
private XYChart.Series<DateTime, AggregateEvent> getSeries(final EventType et) {
|
private XYChart.Series<DateTime, AggregateEvent> getSeries(final EventType et) {
|
||||||
XYChart.Series<DateTime, AggregateEvent> series = eventTypeToSeriesMap.get(et);
|
XYChart.Series<DateTime, AggregateEvent> series = eventTypeToSeriesMap.get(et);
|
||||||
if (series == null) {
|
if (series == null) {
|
||||||
@ -302,7 +309,7 @@ public class DetailViewPane extends AbstractVisualization<DateTime, AggregateEve
|
|||||||
updateProgress(-1, 1);
|
updateProgress(-1, 1);
|
||||||
updateMessage(NbBundle.getMessage(this.getClass(), "DetailViewPane.loggedTask.preparing"));
|
updateMessage(NbBundle.getMessage(this.getClass(), "DetailViewPane.loggedTask.preparing"));
|
||||||
|
|
||||||
final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.timeRange().get());
|
final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.timeRangeProperty().get());
|
||||||
final long lowerBound = rangeInfo.getLowerBound();
|
final long lowerBound = rangeInfo.getLowerBound();
|
||||||
final long upperBound = rangeInfo.getUpperBound();
|
final long upperBound = rangeInfo.getUpperBound();
|
||||||
|
|
||||||
@ -315,9 +322,6 @@ public class DetailViewPane extends AbstractVisualization<DateTime, AggregateEve
|
|||||||
}
|
}
|
||||||
dateAxis.setLowerBound(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()));
|
dateAxis.setLowerBound(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()));
|
||||||
dateAxis.setUpperBound(new DateTime(upperBound, TimeLineController.getJodaTimeZone()));
|
dateAxis.setUpperBound(new DateTime(upperBound, TimeLineController.getJodaTimeZone()));
|
||||||
// if (chart == null) {
|
|
||||||
// initializeClusterChart();
|
|
||||||
// }
|
|
||||||
vertScrollBar.setValue(0);
|
vertScrollBar.setValue(0);
|
||||||
eventTypeToSeriesMap.clear();
|
eventTypeToSeriesMap.clear();
|
||||||
dataSets.clear();
|
dataSets.clear();
|
||||||
@ -478,4 +482,5 @@ public class DetailViewPane extends AbstractVisualization<DateTime, AggregateEve
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javafx.scene.chart.Axis;
|
import javafx.scene.chart.Axis;
|
||||||
import javafx.scene.chart.XYChart;
|
import javafx.scene.chart.XYChart;
|
||||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No-Op axis that doesn't do anything usefull but is necessary to pass
|
* No-Op axis that doesn't do anything usefull but is necessary to pass
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
package org.sleuthkit.autopsy.timeline.ui.detailview;
|
package org.sleuthkit.autopsy.timeline.ui.detailview;
|
||||||
|
|
||||||
import com.google.common.collect.Collections2;
|
import com.google.common.collect.Collections2;
|
||||||
import com.google.common.eventbus.Subscribe;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -72,11 +71,9 @@ import org.openide.util.NbBundle;
|
|||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.events.EventsTaggedEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.events.EventsUnTaggedEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
|
||||||
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -344,12 +341,9 @@ public final class EventDetailChart extends XYChart<DateTime, AggregateEvent> im
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
public void setModel(FilteredEventsModel filteredEvents) {
|
||||||
if (this.filteredEvents != null) {
|
|
||||||
this.filteredEvents.unRegisterForEvents(this);
|
|
||||||
}
|
|
||||||
if (this.filteredEvents != filteredEvents) {
|
if (this.filteredEvents != filteredEvents) {
|
||||||
filteredEvents.registerForEvents(this);
|
filteredEvents.zoomParametersProperty().addListener(o -> {
|
||||||
filteredEvents.getRequestedZoomParamters().addListener(o -> {
|
|
||||||
clearGuideLine();
|
clearGuideLine();
|
||||||
clearIntervalSelector();
|
clearIntervalSelector();
|
||||||
|
|
||||||
@ -527,7 +521,7 @@ public final class EventDetailChart extends XYChart<DateTime, AggregateEvent> im
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Iterable<AggregateEventNode> getAllNodes() {
|
Iterable<AggregateEventNode> getAllNodes() {
|
||||||
return getNodes(x -> true);
|
return getNodes(x -> true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -736,18 +730,4 @@ public final class EventDetailChart extends XYChart<DateTime, AggregateEvent> im
|
|||||||
protected void requestChartLayout() {
|
protected void requestChartLayout() {
|
||||||
super.requestChartLayout();
|
super.requestChartLayout();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
synchronized public void handleEventsUnTagged(EventsUnTaggedEvent tagEvent) {
|
|
||||||
for (AggregateEventNode t : getAllNodes()) {
|
|
||||||
t.handleEventsUnTagged(tagEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
synchronized public void handleEventsTagged(EventsTaggedEvent tagEvent) {
|
|
||||||
for (AggregateEventNode t : getAllNodes()) {
|
|
||||||
t.handleEventsTagged(tagEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
|
|||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import javafx.scene.control.TreeItem;
|
import javafx.scene.control.TreeItem;
|
||||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.scene.control.TreeItem;
|
import javafx.scene.control.TreeItem;
|
||||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
|
||||||
|
|
||||||
class EventTypeTreeItem extends NavTreeItem {
|
class EventTypeTreeItem extends NavTreeItem {
|
||||||
|
|
||||||
|
@ -26,7 +26,13 @@ import javafx.application.Platform;
|
|||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.ComboBox;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.SelectionMode;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.control.TreeCell;
|
||||||
|
import javafx.scene.control.TreeItem;
|
||||||
|
import javafx.scene.control.TreeView;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
@ -35,8 +41,8 @@ import org.openide.util.NbBundle;
|
|||||||
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.detailview.AggregateEventNode;
|
import org.sleuthkit.autopsy.timeline.ui.detailview.AggregateEventNode;
|
||||||
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
|
|||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import javafx.scene.control.TreeItem;
|
import javafx.scene.control.TreeItem;
|
||||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A node in the nav tree. Manages inserts and resorts. Has parents and
|
* A node in the nav tree. Manages inserts and resorts. Has parents and
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
|
package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data item for the nav tree. Represents a combination of type and
|
* The data item for the nav tree. Represents a combination of type and
|
||||||
|
@ -24,8 +24,8 @@ import java.util.Map;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.scene.control.TreeItem;
|
import javafx.scene.control.TreeItem;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
|
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
|
|||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import javafx.scene.control.TreeItem;
|
import javafx.scene.control.TreeItem;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.EventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
|
||||||
|
|
||||||
enum TreeComparator implements Comparator<TreeItem<NavTreeNode>> {
|
enum TreeComparator implements Comparator<TreeItem<NavTreeNode>> {
|
||||||
|
|
||||||
|
@ -38,8 +38,8 @@ import org.sleuthkit.autopsy.timeline.FXMLConstructor;
|
|||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
|
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
import org.sleuthkit.autopsy.timeline.filters.Filter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
|
||||||
@ -174,14 +174,14 @@ public class FilterSetPanel extends BorderPane implements TimeLineView {
|
|||||||
public void setModel(FilteredEventsModel filteredEvents) {
|
public void setModel(FilteredEventsModel filteredEvents) {
|
||||||
this.filteredEvents = filteredEvents;
|
this.filteredEvents = filteredEvents;
|
||||||
refresh();
|
refresh();
|
||||||
this.filteredEvents.filter().addListener((Observable o) -> {
|
this.filteredEvents.filterProperty().addListener((Observable o) -> {
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refresh() {
|
private void refresh() {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
filterTreeTable.setRoot(new FilterTreeItem(filteredEvents.filter().get().copyOf(), expansionMap));
|
filterTreeTable.setRoot(new FilterTreeItem(filteredEvents.filterProperty().get().copyOf(), expansionMap));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ import javafx.scene.shape.Rectangle;
|
|||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
import org.sleuthkit.autopsy.timeline.TimeLineController;
|
||||||
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
import org.sleuthkit.autopsy.timeline.TimeLineView;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
import org.sleuthkit.autopsy.timeline.filters.TextFilter;
|
||||||
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
|
||||||
@ -74,7 +74,7 @@ class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> implement
|
|||||||
rect.setArcWidth(5);
|
rect.setArcWidth(5);
|
||||||
rect.setStrokeWidth(3);
|
rect.setStrokeWidth(3);
|
||||||
setLegendColor(filter, rect, this.filteredEvents.getEventTypeZoom());
|
setLegendColor(filter, rect, this.filteredEvents.getEventTypeZoom());
|
||||||
this.filteredEvents.eventTypeZoom().addListener((obs, oldZoomLevel, newZoomLevel) -> {
|
this.filteredEvents.eventTypeZoomProperty().addListener((obs, oldZoomLevel, newZoomLevel) -> {
|
||||||
setLegendColor(filter, rect, newZoomLevel);
|
setLegendColor(filter, rect, newZoomLevel);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ public class ZoomParams {
|
|||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DescriptionLOD getDescrLOD() {
|
public DescriptionLOD getDescriptionLOD() {
|
||||||
return descrLOD;
|
return descrLOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineView;
|
|||||||
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
import org.sleuthkit.autopsy.timeline.VisualizationMode;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Back;
|
import org.sleuthkit.autopsy.timeline.actions.Back;
|
||||||
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
import org.sleuthkit.autopsy.timeline.actions.Forward;
|
||||||
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
|
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
|
import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
|
||||||
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
|
||||||
|
|
||||||
@ -141,12 +141,12 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
|||||||
if (requestedUnit == TimeUnits.FOREVER) {
|
if (requestedUnit == TimeUnits.FOREVER) {
|
||||||
controller.showFullRange();
|
controller.showFullRange();
|
||||||
} else {
|
} else {
|
||||||
controller.pushTimeRange(IntervalUtils.getIntervalAround(IntervalUtils.middleOf(ZoomSettingsPane.this.filteredEvents.timeRange().get()), requestedUnit.getPeriod()));
|
controller.pushTimeRange(IntervalUtils.getIntervalAround(IntervalUtils.middleOf(ZoomSettingsPane.this.filteredEvents.timeRangeProperty().get()), requestedUnit.getPeriod()));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
this.filteredEvents.timeRange(),
|
this.filteredEvents.timeRangeProperty(),
|
||||||
() -> {
|
() -> {
|
||||||
RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(this.filteredEvents.timeRange().get());
|
RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(this.filteredEvents.timeRangeProperty().get());
|
||||||
ChronoUnit chronoUnit = rangeInfo.getPeriodSize().getChronoUnit();
|
ChronoUnit chronoUnit = rangeInfo.getPeriodSize().getChronoUnit();
|
||||||
|
|
||||||
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
|
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
|
||||||
@ -158,9 +158,9 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
|||||||
if (controller.pushDescrLOD(newLOD) == false) {
|
if (controller.pushDescrLOD(newLOD) == false) {
|
||||||
descrLODSlider.setValue(new DescrLODConverter().fromString(filteredEvents.getDescriptionLOD().toString()));
|
descrLODSlider.setValue(new DescrLODConverter().fromString(filteredEvents.getDescriptionLOD().toString()));
|
||||||
}
|
}
|
||||||
}, this.filteredEvents.descriptionLOD(),
|
}, this.filteredEvents.descriptionLODProperty(),
|
||||||
() -> {
|
() -> {
|
||||||
descrLODSlider.setValue(this.filteredEvents.descriptionLOD().get().ordinal());
|
descrLODSlider.setValue(this.filteredEvents.descriptionLODProperty().get().ordinal());
|
||||||
});
|
});
|
||||||
|
|
||||||
initializeSlider(typeZoomSlider,
|
initializeSlider(typeZoomSlider,
|
||||||
@ -168,9 +168,9 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
|
|||||||
EventTypeZoomLevel newZoomLevel = EventTypeZoomLevel.values()[Math.round(typeZoomSlider.valueProperty().floatValue())];
|
EventTypeZoomLevel newZoomLevel = EventTypeZoomLevel.values()[Math.round(typeZoomSlider.valueProperty().floatValue())];
|
||||||
controller.pushEventTypeZoom(newZoomLevel);
|
controller.pushEventTypeZoom(newZoomLevel);
|
||||||
},
|
},
|
||||||
this.filteredEvents.eventTypeZoom(),
|
this.filteredEvents.eventTypeZoomProperty(),
|
||||||
() -> {
|
() -> {
|
||||||
typeZoomSlider.setValue(this.filteredEvents.eventTypeZoom().get().ordinal());
|
typeZoomSlider.setValue(this.filteredEvents.eventTypeZoomProperty().get().ordinal());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,5 +77,7 @@ file.reference.xmlbeans-2.3.0.jar=release/modules/ext/xmlbeans-2.3.0.jar
|
|||||||
javac.source=1.7
|
javac.source=1.7
|
||||||
javac.compilerargs=-Xlint -Xlint:-serial
|
javac.compilerargs=-Xlint -Xlint:-serial
|
||||||
javadoc.reference.controlsfx-8.40.9.jar=release/modules/ext/controlsfx-8.40.9-javadoc.jar
|
javadoc.reference.controlsfx-8.40.9.jar=release/modules/ext/controlsfx-8.40.9-javadoc.jar
|
||||||
|
javadoc.reference.guava-11.0.2.jar=release/modules/ext/guava-11.0.2-javadoc.jar
|
||||||
nbm.needs.restart=true
|
nbm.needs.restart=true
|
||||||
source.reference.controlsfx-8.40.9.jar=release/modules/ext/controlsfx-8.40.9-sources.jar
|
source.reference.controlsfx-8.40.9.jar=release/modules/ext/controlsfx-8.40.9-sources.jar
|
||||||
|
source.reference.guava-11.0.2.jar=release/modules/ext/guava-11.0.2-sources.jar
|
||||||
|
@ -685,10 +685,6 @@
|
|||||||
<runtime-relative-path>ext/poi-ooxml-schemas-3.8.jar</runtime-relative-path>
|
<runtime-relative-path>ext/poi-ooxml-schemas-3.8.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/poi-ooxml-schemas-3.8.jar</binary-origin>
|
<binary-origin>release/modules/ext/poi-ooxml-schemas-3.8.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
<class-path-extension>
|
|
||||||
<runtime-relative-path>ext/jfxtras-controls-8.0-r3.jar</runtime-relative-path>
|
|
||||||
<binary-origin>release/modules/ext/jfxtras-controls-8.0-r3.jar</binary-origin>
|
|
||||||
</class-path-extension>
|
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/gson-1.4.jar</runtime-relative-path>
|
<runtime-relative-path>ext/gson-1.4.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/gson-1.4.jar</binary-origin>
|
<binary-origin>release/modules/ext/gson-1.4.jar</binary-origin>
|
||||||
@ -697,6 +693,10 @@
|
|||||||
<runtime-relative-path>ext/jsr305-1.3.9.jar</runtime-relative-path>
|
<runtime-relative-path>ext/jsr305-1.3.9.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/jsr305-1.3.9.jar</binary-origin>
|
<binary-origin>release/modules/ext/jsr305-1.3.9.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
|
<class-path-extension>
|
||||||
|
<runtime-relative-path>ext/jfxtras-common-8.0-r3-sources.jar</runtime-relative-path>
|
||||||
|
<binary-origin>release/modules/ext/jfxtras-common-8.0-r3-sources.jar</binary-origin>
|
||||||
|
</class-path-extension>
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/xml-apis-1.0.b2.jar</runtime-relative-path>
|
<runtime-relative-path>ext/xml-apis-1.0.b2.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/xml-apis-1.0.b2.jar</binary-origin>
|
<binary-origin>release/modules/ext/xml-apis-1.0.b2.jar</binary-origin>
|
||||||
@ -713,10 +713,6 @@
|
|||||||
<runtime-relative-path>ext/guava-11.0.2.jar</runtime-relative-path>
|
<runtime-relative-path>ext/guava-11.0.2.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/guava-11.0.2.jar</binary-origin>
|
<binary-origin>release/modules/ext/guava-11.0.2.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
<class-path-extension>
|
|
||||||
<runtime-relative-path>ext/jfxtras-common-8.0-r3-sources.jar</runtime-relative-path>
|
|
||||||
<binary-origin>release/modules/ext/jfxtras-common-8.0-r3-sources.jar</binary-origin>
|
|
||||||
</class-path-extension>
|
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/mail-1.4.3.jar</runtime-relative-path>
|
<runtime-relative-path>ext/mail-1.4.3.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/mail-1.4.3.jar</binary-origin>
|
<binary-origin>release/modules/ext/mail-1.4.3.jar</binary-origin>
|
||||||
@ -733,6 +729,10 @@
|
|||||||
<runtime-relative-path>ext/common-lang-3.1.1.jar</runtime-relative-path>
|
<runtime-relative-path>ext/common-lang-3.1.1.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/common-lang-3.1.1.jar</binary-origin>
|
<binary-origin>release/modules/ext/common-lang-3.1.1.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
|
<class-path-extension>
|
||||||
|
<runtime-relative-path>ext/jfxtras-common-8.0-r3.jar</runtime-relative-path>
|
||||||
|
<binary-origin>release/modules/ext/jfxtras-common-8.0-r3.jar</binary-origin>
|
||||||
|
</class-path-extension>
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/slf4j-api-1.6.1.jar</runtime-relative-path>
|
<runtime-relative-path>ext/slf4j-api-1.6.1.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/slf4j-api-1.6.1.jar</binary-origin>
|
<binary-origin>release/modules/ext/slf4j-api-1.6.1.jar</binary-origin>
|
||||||
@ -746,8 +746,8 @@
|
|||||||
<binary-origin>release/modules/ext/joda-time-2.4-javadoc.jar</binary-origin>
|
<binary-origin>release/modules/ext/joda-time-2.4-javadoc.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/jfxtras-fxml-8.0-r3-sources.jar</runtime-relative-path>
|
<runtime-relative-path>ext/jfxtras-fxml-8.0-r3.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/jfxtras-fxml-8.0-r3-sources.jar</binary-origin>
|
<binary-origin>release/modules/ext/jfxtras-fxml-8.0-r3.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/jcalendarbutton-1.4.6.jar</runtime-relative-path>
|
<runtime-relative-path>ext/jcalendarbutton-1.4.6.jar</runtime-relative-path>
|
||||||
@ -765,6 +765,14 @@
|
|||||||
<runtime-relative-path>ext/servlet-api-2.5.jar</runtime-relative-path>
|
<runtime-relative-path>ext/servlet-api-2.5.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/servlet-api-2.5.jar</binary-origin>
|
<binary-origin>release/modules/ext/servlet-api-2.5.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
|
<class-path-extension>
|
||||||
|
<runtime-relative-path>ext/jfxtras-controls-8.0-r3.jar</runtime-relative-path>
|
||||||
|
<binary-origin>release/modules/ext/jfxtras-controls-8.0-r3.jar</binary-origin>
|
||||||
|
</class-path-extension>
|
||||||
|
<class-path-extension>
|
||||||
|
<runtime-relative-path>ext/jfxtras-fxml-8.0-r3-sources.jar</runtime-relative-path>
|
||||||
|
<binary-origin>release/modules/ext/jfxtras-fxml-8.0-r3-sources.jar</binary-origin>
|
||||||
|
</class-path-extension>
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/poi-excelant-3.8.jar</runtime-relative-path>
|
<runtime-relative-path>ext/poi-excelant-3.8.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/poi-excelant-3.8.jar</binary-origin>
|
<binary-origin>release/modules/ext/poi-excelant-3.8.jar</binary-origin>
|
||||||
@ -785,6 +793,10 @@
|
|||||||
<runtime-relative-path>ext/avalon-framework-4.1.5.jar</runtime-relative-path>
|
<runtime-relative-path>ext/avalon-framework-4.1.5.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/avalon-framework-4.1.5.jar</binary-origin>
|
<binary-origin>release/modules/ext/avalon-framework-4.1.5.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
|
<class-path-extension>
|
||||||
|
<runtime-relative-path>ext/jfxtras-fxml-8.0-r3-javadoc.jar</runtime-relative-path>
|
||||||
|
<binary-origin>release/modules/ext/jfxtras-fxml-8.0-r3-javadoc.jar</binary-origin>
|
||||||
|
</class-path-extension>
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/geronimo-jms_1.1_spec-1.0.jar</runtime-relative-path>
|
<runtime-relative-path>ext/geronimo-jms_1.1_spec-1.0.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/geronimo-jms_1.1_spec-1.0.jar</binary-origin>
|
<binary-origin>release/modules/ext/geronimo-jms_1.1_spec-1.0.jar</binary-origin>
|
||||||
@ -801,30 +813,14 @@
|
|||||||
<runtime-relative-path>ext/joda-time-2.4.jar</runtime-relative-path>
|
<runtime-relative-path>ext/joda-time-2.4.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/joda-time-2.4.jar</binary-origin>
|
<binary-origin>release/modules/ext/joda-time-2.4.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
<class-path-extension>
|
|
||||||
<runtime-relative-path>ext/jfxtras-fxml-8.0-r3-javadoc.jar</runtime-relative-path>
|
|
||||||
<binary-origin>release/modules/ext/jfxtras-fxml-8.0-r3-javadoc.jar</binary-origin>
|
|
||||||
</class-path-extension>
|
|
||||||
<class-path-extension>
|
|
||||||
<runtime-relative-path>ext/jfxtras-fxml-8.0-r3.jar</runtime-relative-path>
|
|
||||||
<binary-origin>release/modules/ext/jfxtras-fxml-8.0-r3.jar</binary-origin>
|
|
||||||
</class-path-extension>
|
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/common-io-3.1.1.jar</runtime-relative-path>
|
<runtime-relative-path>ext/common-io-3.1.1.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/common-io-3.1.1.jar</binary-origin>
|
<binary-origin>release/modules/ext/common-io-3.1.1.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
<class-path-extension>
|
|
||||||
<runtime-relative-path>ext/jfxtras-common-8.0-r3.jar</runtime-relative-path>
|
|
||||||
<binary-origin>release/modules/ext/jfxtras-common-8.0-r3.jar</binary-origin>
|
|
||||||
</class-path-extension>
|
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/commons-logging-1.1.2-javadoc.jar</runtime-relative-path>
|
<runtime-relative-path>ext/commons-logging-1.1.2-javadoc.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/commons-logging-1.1.2-javadoc.jar</binary-origin>
|
<binary-origin>release/modules/ext/commons-logging-1.1.2-javadoc.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
<class-path-extension>
|
|
||||||
<runtime-relative-path>ext/jfxtras-controls-8.0-r3-javadoc.jar</runtime-relative-path>
|
|
||||||
<binary-origin>release/modules/ext/jfxtras-controls-8.0-r3-javadoc.jar</binary-origin>
|
|
||||||
</class-path-extension>
|
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/slf4j-simple-1.6.1.jar</runtime-relative-path>
|
<runtime-relative-path>ext/slf4j-simple-1.6.1.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/slf4j-simple-1.6.1.jar</binary-origin>
|
<binary-origin>release/modules/ext/slf4j-simple-1.6.1.jar</binary-origin>
|
||||||
@ -861,6 +857,10 @@
|
|||||||
<runtime-relative-path>ext/imageio-icns-3.1.1.jar</runtime-relative-path>
|
<runtime-relative-path>ext/imageio-icns-3.1.1.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/imageio-icns-3.1.1.jar</binary-origin>
|
<binary-origin>release/modules/ext/imageio-icns-3.1.1.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
|
<class-path-extension>
|
||||||
|
<runtime-relative-path>ext/jfxtras-common-8.0-r3-javadoc.jar</runtime-relative-path>
|
||||||
|
<binary-origin>release/modules/ext/jfxtras-common-8.0-r3-javadoc.jar</binary-origin>
|
||||||
|
</class-path-extension>
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/javassist-3.12.1.GA.jar</runtime-relative-path>
|
<runtime-relative-path>ext/javassist-3.12.1.GA.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/javassist-3.12.1.GA.jar</binary-origin>
|
<binary-origin>release/modules/ext/javassist-3.12.1.GA.jar</binary-origin>
|
||||||
@ -893,10 +893,6 @@
|
|||||||
<runtime-relative-path>ext/commons-lang3-3.0.jar</runtime-relative-path>
|
<runtime-relative-path>ext/commons-lang3-3.0.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/commons-lang3-3.0.jar</binary-origin>
|
<binary-origin>release/modules/ext/commons-lang3-3.0.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
<class-path-extension>
|
|
||||||
<runtime-relative-path>ext/jfxtras-common-8.0-r3-javadoc.jar</runtime-relative-path>
|
|
||||||
<binary-origin>release/modules/ext/jfxtras-common-8.0-r3-javadoc.jar</binary-origin>
|
|
||||||
</class-path-extension>
|
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/imageio-iff-3.1.1.jar</runtime-relative-path>
|
<runtime-relative-path>ext/imageio-iff-3.1.1.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/imageio-iff-3.1.1.jar</binary-origin>
|
<binary-origin>release/modules/ext/imageio-iff-3.1.1.jar</binary-origin>
|
||||||
@ -918,8 +914,8 @@
|
|||||||
<binary-origin>release/modules/ext/gstreamer-java-1.5.jar</binary-origin>
|
<binary-origin>release/modules/ext/gstreamer-java-1.5.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/jfxtras-controls-8.0-r3-sources.jar</runtime-relative-path>
|
<runtime-relative-path>ext/jfxtras-controls-8.0-r3-javadoc.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/jfxtras-controls-8.0-r3-sources.jar</binary-origin>
|
<binary-origin>release/modules/ext/jfxtras-controls-8.0-r3-javadoc.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
<class-path-extension>
|
<class-path-extension>
|
||||||
<runtime-relative-path>ext/imageio-metadata-3.1.1.jar</runtime-relative-path>
|
<runtime-relative-path>ext/imageio-metadata-3.1.1.jar</runtime-relative-path>
|
||||||
@ -937,6 +933,10 @@
|
|||||||
<runtime-relative-path>ext/imgscalr-lib-4.2-sources.jar</runtime-relative-path>
|
<runtime-relative-path>ext/imgscalr-lib-4.2-sources.jar</runtime-relative-path>
|
||||||
<binary-origin>release/modules/ext/imgscalr-lib-4.2-sources.jar</binary-origin>
|
<binary-origin>release/modules/ext/imgscalr-lib-4.2-sources.jar</binary-origin>
|
||||||
</class-path-extension>
|
</class-path-extension>
|
||||||
|
<class-path-extension>
|
||||||
|
<runtime-relative-path>ext/jfxtras-controls-8.0-r3-sources.jar</runtime-relative-path>
|
||||||
|
<binary-origin>release/modules/ext/jfxtras-controls-8.0-r3-sources.jar</binary-origin>
|
||||||
|
</class-path-extension>
|
||||||
</data>
|
</data>
|
||||||
</configuration>
|
</configuration>
|
||||||
</project>
|
</project>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
|
<data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
|
||||||
<code-name-base>org.sleuthkit.autopsy.imagegallery</code-name-base>
|
<code-name-base>org.sleuthkit.autopsy.imagegallery</code-name-base>
|
||||||
<standalone/>
|
<suite-component/>
|
||||||
<module-dependencies>
|
<module-dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<code-name-base>org.netbeans.api.progress</code-name-base>
|
<code-name-base>org.netbeans.api.progress</code-name-base>
|
||||||
|
1
ImageGallery/nbproject/suite.properties
Normal file
1
ImageGallery/nbproject/suite.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
suite.dir=${basedir}/..
|
@ -248,6 +248,7 @@ public final class ImageGalleryController {
|
|||||||
return historyManager.getCanRetreat();
|
return historyManager.getCanRetreat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
public void advance(GroupViewState newState, boolean forceShowTree) {
|
public void advance(GroupViewState newState, boolean forceShowTree) {
|
||||||
if (Objects.nonNull(navPanel) && forceShowTree) {
|
if (Objects.nonNull(navPanel) && forceShowTree) {
|
||||||
navPanel.showTree();
|
navPanel.showTree();
|
||||||
@ -414,7 +415,7 @@ public final class ImageGalleryController {
|
|||||||
dbWorkerThread.addTask(innerTask);
|
dbWorkerThread.addTask(innerTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DrawableFile<?> getFileFromId(Long fileID) throws TskCoreException {
|
synchronized public DrawableFile<?> getFileFromId(Long fileID) throws TskCoreException {
|
||||||
return db.getFileFromID(fileID);
|
return db.getFileFromID(fileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -776,7 +777,8 @@ public final class ImageGalleryController {
|
|||||||
+ " AND (blackboard_attributes.value_text LIKE 'video/%'"
|
+ " AND (blackboard_attributes.value_text LIKE 'video/%'"
|
||||||
+ " OR blackboard_attributes.value_text LIKE 'image/%'"
|
+ " OR blackboard_attributes.value_text LIKE 'image/%'"
|
||||||
+ " OR " + MIMETYPE_CLAUSE
|
+ " OR " + MIMETYPE_CLAUSE
|
||||||
+ " )";
|
+ " )"
|
||||||
|
+ ")";
|
||||||
|
|
||||||
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("populating analyzed image/video database");
|
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("populating analyzed image/video database");
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ import javafx.scene.image.Image;
|
|||||||
import javafx.scene.image.WritableImage;
|
import javafx.scene.image.WritableImage;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import org.openide.util.Exceptions;
|
|
||||||
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
import org.sleuthkit.autopsy.coreutils.ImageUtils;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||||
@ -46,7 +45,8 @@ import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
|
|||||||
import org.sleuthkit.datamodel.ReadContentInputStream;
|
import org.sleuthkit.datamodel.ReadContentInputStream;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/** Singleton to manage creation and access of icons. Keeps a cache in memory of
|
/**
|
||||||
|
* Singleton to manage creation and access of icons. Keeps a cache in memory of
|
||||||
* most recently used icons, and a disk cache of all icons.
|
* most recently used icons, and a disk cache of all icons.
|
||||||
*
|
*
|
||||||
* TODO: this was only a singleton for convenience, convert this to
|
* TODO: this was only a singleton for convenience, convert this to
|
||||||
@ -60,7 +60,8 @@ public enum ThumbnailCache {
|
|||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ThumbnailCache.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(ThumbnailCache.class.getName());
|
||||||
|
|
||||||
/** in memory cache. keeps at most 1000 items each for up to 10 minutes.
|
/**
|
||||||
|
* in memory cache. keeps at most 1000 items each for up to 10 minutes.
|
||||||
* items may be garbage collected if there are no strong references to them.
|
* items may be garbage collected if there are no strong references to them.
|
||||||
*/
|
*/
|
||||||
private final Cache<Long, Optional<Image>> cache = CacheBuilder.newBuilder()
|
private final Cache<Long, Optional<Image>> cache = CacheBuilder.newBuilder()
|
||||||
@ -72,7 +73,9 @@ public enum ThumbnailCache {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** currently desired icon size. is bound in {@link Toolbar} */
|
/**
|
||||||
|
* currently desired icon size. is bound in {@link Toolbar}
|
||||||
|
*/
|
||||||
public final SimpleIntegerProperty iconSize = new SimpleIntegerProperty(200);
|
public final SimpleIntegerProperty iconSize = new SimpleIntegerProperty(200);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,7 +85,8 @@ public enum ThumbnailCache {
|
|||||||
cache.invalidateAll();
|
cache.invalidateAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** get the cached thumbnail for the given file or generate a new one if
|
/**
|
||||||
|
* get the cached thumbnail for the given file or generate a new one if
|
||||||
* needed
|
* needed
|
||||||
*
|
*
|
||||||
* @param file
|
* @param file
|
||||||
@ -132,16 +136,15 @@ public enum ThumbnailCache {
|
|||||||
if (cachFile.exists()) {
|
if (cachFile.exists()) {
|
||||||
// If a thumbnail file is already saved locally, load it
|
// If a thumbnail file is already saved locally, load it
|
||||||
try {
|
try {
|
||||||
BufferedImage read = ImageIO.read(cachFile);
|
BufferedImage cachedThumbnail = ImageIO.read(cachFile);
|
||||||
|
|
||||||
if (read.getWidth() < MAX_THUMBNAIL_SIZE) {
|
|
||||||
return read;
|
|
||||||
|
|
||||||
|
if (cachedThumbnail.getWidth() < MAX_THUMBNAIL_SIZE) {
|
||||||
|
return cachedThumbnail;
|
||||||
}
|
}
|
||||||
} catch (MalformedURLException ex) {
|
} catch (MalformedURLException ex) {
|
||||||
LOGGER.log(Level.WARNING, "Unable to parse cache file path..");
|
LOGGER.log(Level.WARNING, "Unable to parse cache file path: " + cachFile.getPath(), ex);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
Exceptions.printStackTrace(ex);
|
LOGGER.log(Level.WARNING, "Unable to read cache file " + cachFile.getPath(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -150,10 +153,6 @@ public enum ThumbnailCache {
|
|||||||
return (BufferedImage) ImageUtils.getThumbnail(file.getAbstractFile(), MAX_THUMBNAIL_SIZE);
|
return (BufferedImage) ImageUtils.getThumbnail(file.getAbstractFile(), MAX_THUMBNAIL_SIZE);
|
||||||
});
|
});
|
||||||
|
|
||||||
// } catch (IllegalStateException e) {
|
|
||||||
// LOGGER.log(Level.WARNING, "can't load icon when no case is open");
|
|
||||||
// return Optional.empty();
|
|
||||||
// }
|
|
||||||
WritableImage jfxthumbnail;
|
WritableImage jfxthumbnail;
|
||||||
if (thumbnail == ImageUtils.getDefaultThumbnail()) {
|
if (thumbnail == ImageUtils.getDefaultThumbnail()) {
|
||||||
// if we go the default icon, ignore it
|
// if we go the default icon, ignore it
|
||||||
|
@ -62,6 +62,7 @@ public class NextUnseenGroup extends Action {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setEventHandler((ActionEvent t) -> {
|
setEventHandler((ActionEvent t) -> {
|
||||||
|
//if there is a group assigned to the view, mark it as seen
|
||||||
Optional.ofNullable(controller.viewState())
|
Optional.ofNullable(controller.viewState())
|
||||||
.map(ObjectExpression<GroupViewState>::getValue)
|
.map(ObjectExpression<GroupViewState>::getValue)
|
||||||
.map(GroupViewState::getGroup)
|
.map(GroupViewState::getGroup)
|
||||||
|
@ -15,10 +15,14 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
*/
|
*/
|
||||||
public class HashSetManager {
|
public class HashSetManager {
|
||||||
|
|
||||||
/** The db that initial values are loaded from. */
|
/**
|
||||||
|
* The db that initial values are loaded from.
|
||||||
|
*/
|
||||||
private DrawableDB db = null;
|
private DrawableDB db = null;
|
||||||
|
|
||||||
/** the internal cache from fileID to a set of hashset names. */
|
/**
|
||||||
|
* the internal cache from fileID to a set of hashset names.
|
||||||
|
*/
|
||||||
private final LoadingCache<Long, Set<String>> hashSetCache = CacheBuilder.newBuilder().build(CacheLoader.from(this::getHashSetsForFileHelper));
|
private final LoadingCache<Long, Set<String>> hashSetCache = CacheBuilder.newBuilder().build(CacheLoader.from(this::getHashSetsForFileHelper));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,6 +27,8 @@ import javafx.application.Platform;
|
|||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.scene.control.TreeItem;
|
import javafx.scene.control.TreeItem;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,11 +39,11 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
|||||||
*/
|
*/
|
||||||
class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeItem> {
|
class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeItem> {
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
static GroupTreeItem getTreeItemForGroup(GroupTreeItem root, DrawableGroup grouping) {
|
static GroupTreeItem getTreeItemForGroup(GroupTreeItem root, DrawableGroup grouping) {
|
||||||
if (Objects.equals(root.getValue().getGroup(), grouping)) {
|
if (Objects.equals(root.getValue().getGroup(), grouping)) {
|
||||||
return root;
|
return root;
|
||||||
} else {
|
} else {
|
||||||
synchronized (root.getChildren()) {
|
|
||||||
for (TreeItem<TreeNode> child : root.getChildren()) {
|
for (TreeItem<TreeNode> child : root.getChildren()) {
|
||||||
final GroupTreeItem childGTI = (GroupTreeItem) child;
|
final GroupTreeItem childGTI = (GroupTreeItem) child;
|
||||||
|
|
||||||
@ -51,7 +53,6 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,11 +108,8 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
|
|||||||
prefixTreeItem = newTreeItem;
|
prefixTreeItem = newTreeItem;
|
||||||
childMap.put(prefix, prefixTreeItem);
|
childMap.put(prefix, prefixTreeItem);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
synchronized (getChildren()) {
|
|
||||||
getChildren().add(newTreeItem);
|
getChildren().add(newTreeItem);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// recursively go into the path
|
// recursively go into the path
|
||||||
@ -125,14 +123,11 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
|
|||||||
childMap.put(path, newTreeItem);
|
childMap.put(path, newTreeItem);
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
synchronized (getChildren()) {
|
|
||||||
getChildren().add(newTreeItem);
|
getChildren().add(newTreeItem);
|
||||||
if (comp != null) {
|
if (comp != null) {
|
||||||
FXCollections.sort(getChildren(), comp);
|
FXCollections.sort(getChildren(), comp);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,11 +155,8 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
|
|||||||
childMap.put(prefix, prefixTreeItem);
|
childMap.put(prefix, prefixTreeItem);
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
synchronized (getChildren()) {
|
|
||||||
getChildren().add(newTreeItem);
|
getChildren().add(newTreeItem);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// recursively go into the path
|
// recursively go into the path
|
||||||
@ -179,12 +171,10 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
|
|||||||
childMap.put(path.get(0), newTreeItem);
|
childMap.put(path.get(0), newTreeItem);
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
synchronized (getChildren()) {
|
|
||||||
getChildren().add(newTreeItem);
|
getChildren().add(newTreeItem);
|
||||||
if (comp != null) {
|
if (comp != null) {
|
||||||
FXCollections.sort(getChildren(), comp);
|
FXCollections.sort(getChildren(), comp);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,12 +185,13 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
|
|||||||
return comp.compare(this, o);
|
return comp.compare(this, o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
GroupTreeItem getTreeItemForPath(List<String> path) {
|
GroupTreeItem getTreeItemForPath(List<String> path) {
|
||||||
// end of recursion
|
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
|
// end of recursion
|
||||||
return this;
|
return this;
|
||||||
} else {
|
} else {
|
||||||
synchronized (getChildren()) {
|
|
||||||
String prefix = path.get(0);
|
String prefix = path.get(0);
|
||||||
|
|
||||||
GroupTreeItem prefixTreeItem = childMap.get(prefix);
|
GroupTreeItem prefixTreeItem = childMap.get(prefix);
|
||||||
@ -213,17 +204,15 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
|
|||||||
return prefixTreeItem.getTreeItemForPath(path.subList(1, path.size()));
|
return prefixTreeItem.getTreeItemForPath(path.subList(1, path.size()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
void removeFromParent() {
|
void removeFromParent() {
|
||||||
final GroupTreeItem parent = (GroupTreeItem) getParent();
|
final GroupTreeItem parent = (GroupTreeItem) getParent();
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
parent.childMap.remove(getValue().getPath());
|
parent.childMap.remove(getValue().getPath());
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
synchronized (parent.getChildren()) {
|
|
||||||
parent.getChildren().removeAll(Collections.singleton(GroupTreeItem.this));
|
parent.getChildren().removeAll(Collections.singleton(GroupTreeItem.this));
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (parent.childMap.isEmpty()) {
|
if (parent.childMap.isEmpty()) {
|
||||||
@ -232,14 +221,17 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* must be performed on fx thread because it manipualtes the tree directly.
|
||||||
|
*
|
||||||
|
* @param newComp
|
||||||
|
*/
|
||||||
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
void resortChildren(TreeNodeComparators newComp) {
|
void resortChildren(TreeNodeComparators newComp) {
|
||||||
this.comp = newComp;
|
this.comp = newComp;
|
||||||
synchronized (getChildren()) {
|
|
||||||
FXCollections.sort(getChildren(), comp);
|
FXCollections.sort(getChildren(), comp);
|
||||||
}
|
|
||||||
for (GroupTreeItem ti : childMap.values()) {
|
for (GroupTreeItem ti : childMap.values()) {
|
||||||
ti.resortChildren(comp);
|
ti.resortChildren(comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -55,15 +55,15 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
|||||||
*/
|
*/
|
||||||
public class NavPanel extends TabPane {
|
public class NavPanel extends TabPane {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ComboBox<TreeNodeComparators> sortByBox;
|
||||||
|
@FXML
|
||||||
|
private TabPane navTabPane;
|
||||||
/**
|
/**
|
||||||
* TreeView for folders with hash hits
|
* TreeView for folders with hash hits
|
||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
private TreeView<TreeNode> hashTree;
|
private TreeView<TreeNode> hashTree;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TabPane navTabPane;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TreeView for all folders
|
* TreeView for all folders
|
||||||
*/
|
*/
|
||||||
@ -76,17 +76,14 @@ public class NavPanel extends TabPane {
|
|||||||
@FXML
|
@FXML
|
||||||
private Tab navTab;
|
private Tab navTab;
|
||||||
|
|
||||||
@FXML
|
private GroupTreeItem hashTreeRoot;
|
||||||
private ComboBox<TreeNodeComparators> sortByBox;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* contains the 'active tree'
|
|
||||||
*/
|
|
||||||
private final SimpleObjectProperty<TreeView<TreeNode>> activeTreeProperty = new SimpleObjectProperty<>();
|
|
||||||
|
|
||||||
private GroupTreeItem navTreeRoot;
|
private GroupTreeItem navTreeRoot;
|
||||||
|
|
||||||
private GroupTreeItem hashTreeRoot;
|
/**
|
||||||
|
* contains the 'active tree', three in the selected Tab.
|
||||||
|
*/
|
||||||
|
private final SimpleObjectProperty<TreeView<TreeNode>> activeTreeProperty = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
|
|
||||||
@ -118,6 +115,7 @@ public class NavPanel extends TabPane {
|
|||||||
sortByBox.setItems(FXCollections.observableArrayList(FXCollections.observableArrayList(TreeNodeComparators.values())));
|
sortByBox.setItems(FXCollections.observableArrayList(FXCollections.observableArrayList(TreeNodeComparators.values())));
|
||||||
sortByBox.getSelectionModel().select(TreeNodeComparators.HIT_COUNT);
|
sortByBox.getSelectionModel().select(TreeNodeComparators.HIT_COUNT);
|
||||||
sortByBox.getSelectionModel().selectedItemProperty().addListener((Observable o) -> {
|
sortByBox.getSelectionModel().selectedItemProperty().addListener((Observable o) -> {
|
||||||
|
//user action ->jfx thread
|
||||||
resortHashTree();
|
resortHashTree();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -143,14 +141,8 @@ public class NavPanel extends TabPane {
|
|||||||
});
|
});
|
||||||
|
|
||||||
controller.getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
|
controller.getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
|
||||||
TreeItem<TreeNode> selectedItem = activeTreeProperty.get().getSelectionModel().getSelectedItem();
|
//analyzed groups shoud only be modified on jfx thread
|
||||||
boolean wasPermuted = false;
|
|
||||||
while (change.next()) {
|
while (change.next()) {
|
||||||
if (change.wasPermutated()) {
|
|
||||||
// Handle this afterward
|
|
||||||
wasPermuted = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
for (DrawableGroup g : change.getAddedSubList()) {
|
for (DrawableGroup g : change.getAddedSubList()) {
|
||||||
insertIntoNavTree(g);
|
insertIntoNavTree(g);
|
||||||
if (g.getHashSetHitsCount() > 0) {
|
if (g.getHashSetHitsCount() > 0) {
|
||||||
@ -162,15 +154,6 @@ public class NavPanel extends TabPane {
|
|||||||
removeFromHashTree(g);
|
removeFromHashTree(g);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wasPermuted) {
|
|
||||||
rebuildTrees();
|
|
||||||
}
|
|
||||||
if (selectedItem != null && selectedItem.getValue().getGroup() != null) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
setFocusedGroup(selectedItem.getValue().getGroup());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
rebuildTrees();
|
rebuildTrees();
|
||||||
@ -208,6 +191,7 @@ public class NavPanel extends TabPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
private void resortHashTree() {
|
private void resortHashTree() {
|
||||||
hashTreeRoot.resortChildren(sortByBox.getSelectionModel().getSelectedItem());
|
hashTreeRoot.resortChildren(sortByBox.getSelectionModel().getSelectedItem());
|
||||||
}
|
}
|
||||||
@ -262,16 +246,19 @@ public class NavPanel extends TabPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
private void insertIntoHashTree(DrawableGroup g) {
|
private void insertIntoHashTree(DrawableGroup g) {
|
||||||
initHashTree();
|
initHashTree();
|
||||||
hashTreeRoot.insert(groupingToPath(g), g, false);
|
hashTreeRoot.insert(groupingToPath(g), g, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
private void insertIntoNavTree(DrawableGroup g) {
|
private void insertIntoNavTree(DrawableGroup g) {
|
||||||
initNavTree();
|
initNavTree();
|
||||||
navTreeRoot.insert(groupingToPath(g), g, true);
|
navTreeRoot.insert(groupingToPath(g), g, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
private void removeFromNavTree(DrawableGroup g) {
|
private void removeFromNavTree(DrawableGroup g) {
|
||||||
initNavTree();
|
initNavTree();
|
||||||
final GroupTreeItem treeItemForGroup = GroupTreeItem.getTreeItemForGroup(navTreeRoot, g);
|
final GroupTreeItem treeItemForGroup = GroupTreeItem.getTreeItemForGroup(navTreeRoot, g);
|
||||||
@ -280,6 +267,7 @@ public class NavPanel extends TabPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
private void removeFromHashTree(DrawableGroup g) {
|
private void removeFromHashTree(DrawableGroup g) {
|
||||||
initHashTree();
|
initHashTree();
|
||||||
final GroupTreeItem treeItemForGroup = GroupTreeItem.getTreeItemForGroup(hashTreeRoot, g);
|
final GroupTreeItem treeItemForGroup = GroupTreeItem.getTreeItemForGroup(hashTreeRoot, g);
|
||||||
@ -310,6 +298,7 @@ public class NavPanel extends TabPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
public void showTree() {
|
public void showTree() {
|
||||||
getSelectionModel().select(navTab);
|
getSelectionModel().select(navTab);
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,13 @@ modules=\
|
|||||||
${project.org.sleuthkit.autopsy.testing}:\
|
${project.org.sleuthkit.autopsy.testing}:\
|
||||||
${project.org.sleuthkit.autopsy.thunderbirdparser}:\
|
${project.org.sleuthkit.autopsy.thunderbirdparser}:\
|
||||||
${project.org.sleuthkit.autopsy.core}:\
|
${project.org.sleuthkit.autopsy.core}:\
|
||||||
${project.org.sleuthkit.autopsy.corelibs}
|
${project.org.sleuthkit.autopsy.corelibs}:\
|
||||||
|
${project.org.sleuthkit.autopsy.imagegallery}
|
||||||
project.org.sleuthkit.autopsy.core=Core
|
project.org.sleuthkit.autopsy.core=Core
|
||||||
project.org.sleuthkit.autopsy.corelibs=CoreLibs
|
project.org.sleuthkit.autopsy.corelibs=CoreLibs
|
||||||
project.org.sleuthkit.autopsy.keywordsearch=KeywordSearch
|
project.org.sleuthkit.autopsy.keywordsearch=KeywordSearch
|
||||||
project.org.sleuthkit.autopsy.recentactivity=RecentActivity
|
project.org.sleuthkit.autopsy.recentactivity=RecentActivity
|
||||||
project.org.sleuthkit.autopsy.testing=Testing
|
project.org.sleuthkit.autopsy.testing=Testing
|
||||||
project.org.sleuthkit.autopsy.thunderbirdparser=thunderbirdparser
|
project.org.sleuthkit.autopsy.thunderbirdparser=thunderbirdparser
|
||||||
|
project.org.sleuthkit.autopsy.imagegallery=ImageGallery
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user