Merge remote-tracking branch 'sleuthkit/develop' into develop

This commit is contained in:
Roberto Larcher 2015-09-01 16:09:26 +02:00
commit 36a9017c03
85 changed files with 2121 additions and 1335 deletions

View File

@ -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.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.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.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbinding-AllPlatforms.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.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.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.compilerargs=-Xlint -Xlint:-serial
license.file=../LICENSE-2.0.txt
nbm.homepage=http://www.sleuthkit.org/
nbm.module.author=Brian Carrier
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

View File

@ -203,10 +203,26 @@
<package>org.sleuthkit.autopsy.report</package>
<package>org.sleuthkit.datamodel</package>
</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>
<runtime-relative-path>ext/jdom-2.0.5.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jdom-2.0.5.jar</binary-origin>
</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>
<runtime-relative-path>ext/Rejistry-1.0-SNAPSHOT.jar</runtime-relative-path>
<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>
<binary-origin>release/modules/ext/jython-standalone-2.7.0.jar</binary-origin>
</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>
<runtime-relative-path>ext/sevenzipjbinding-AllPlatforms.jar</runtime-relative-path>
<binary-origin>release/modules/ext/sevenzipjbinding-AllPlatforms.jar</binary-origin>
</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>
<runtime-relative-path>ext/tika-core-1.2.jar</runtime-relative-path>
<binary-origin>release/modules/ext/tika-core-1.2.jar</binary-origin>
</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>
<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 file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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> 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> 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() {
return IMAGE_EXTENSIONS;

View File

@ -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.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.ArchiveExtractor.UnpackStream.write.noSpace.msg=Unable to write content to disk. Not enough space.

View File

@ -24,6 +24,8 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@ -40,7 +42,6 @@ import net.sf.sevenzipjbinding.simple.ISimpleInArchive;
import net.sf.sevenzipjbinding.simple.ISimpleInArchiveItem;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.services.FileManager;
@ -98,7 +99,8 @@ class SevenZipExtractor {
GZIP("application/gzip"),
XGZIP("application/x-gzip"),
XBZIP2("application/x-bzip2"),
XTAR("application/x-tar");
XTAR("application/x-tar"),
XGTAR("application/x-gtar");
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
} catch (SevenZipNativeInitializationException e) {
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());
String details = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errCantInitLib",
String details = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errCantInitLib",
e.getMessage());
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
throw new IngestModuleException(e.getMessage());
@ -204,7 +206,7 @@ class SevenZipExtractor {
if (cRatio >= MAX_COMPRESSION_RATIO) {
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
String msg = NbBundle.getMessage(this.getClass(),
String msg = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName);
String path;
try {
@ -212,7 +214,7 @@ class SevenZipExtractor {
} catch (TskCoreException ex) {
path = archiveFile.getParentPath() + archiveFile.getName();
}
String details = NbBundle.getMessage(this.getClass(),
String details = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path);
//MessageNotifyUtil.Notify.error(msg, details);
services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
@ -311,9 +313,9 @@ class SevenZipExtractor {
if (parentAr == null) {
parentAr = archiveDepthCountTree.addArchive(null, archiveId);
} 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());
String details = NbBundle.getMessage(this.getClass(),
String details = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
parentAr.getDepth(), archiveFilePath);
//MessageNotifyUtil.Notify.error(msg, details);
@ -328,7 +330,7 @@ class SevenZipExtractor {
SevenZipContentReadStream stream = null;
final ProgressHandle progress = ProgressHandleFactory.createHandle(
NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.moduleName"));
NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.moduleName"));
int processedItems = 0;
boolean progressStarted = false;
@ -400,7 +402,7 @@ class SevenZipExtractor {
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);
logger.log(Level.WARNING, msg);
@ -432,24 +434,19 @@ class SevenZipExtractor {
fullEncryption = false;
}
final Long size = item.getSize();
if (size == null) {
// If the size property cannot be determined, out-of-disk-space
// 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;
}
// NOTE: item.getSize() may return null in case of certain
// archiving formats. Eg: BZ2
Long size = item.getSize();
//check if unpacking this file will result in out of disk space
//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;
if (newDiskSpace < MIN_FREE_DISK_SPACE) {
String msg = NbBundle.getMessage(this.getClass(),
String msg = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
archiveFilePath, fileName);
String details = NbBundle.getMessage(this.getClass(),
String details = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
//MessageNotifyUtil.Notify.error(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 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
if (!isDir) {
SevenZipExtractor.UnpackStream unpackStream = null;
if (!isDir) {
try {
unpackStream = new SevenZipExtractor.UnpackStream(localAbsPath);
unpackStream = new SevenZipExtractor.UnpackStream(localAbsPath, freeDiskSpace, size == null);
item.extractSlow(unpackStream);
} catch (Exception e) {
//could be something unexpected with this file, move on
logger.log(Level.WARNING, "Could not extract file from archive: " + localAbsPath, e); //NON-NLS
} finally {
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();
}
}
@ -549,9 +556,9 @@ class SevenZipExtractor {
// print a message if the file is allocated
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());
String details = NbBundle.getMessage(this.getClass(),
String details = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
archiveFilePath, ex.getMessage());
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
}
String msg = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
String details = NbBundle.getMessage(this.getClass(),
String msg = NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
String details = NbBundle.getMessage(SevenZipExtractor.class,
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.details",
archiveFile.getName(), EmbeddedFileExtractorModuleFactory.getModuleName());
services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
@ -612,8 +619,15 @@ class SevenZipExtractor {
private OutputStream output;
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 {
output = new BufferedOutputStream(new FileOutputStream(localAbsPath));
} catch (FileNotFoundException ex) {
@ -622,13 +636,38 @@ class SevenZipExtractor {
}
public long getNumberOfBytesWritten() {
return this.bytesWritten;
}
@Override
public int write(byte[] bytes) throws SevenZipException {
try {
if (!sizeUnknown) {
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) {
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);
}
return bytes.length;
@ -639,6 +678,9 @@ class SevenZipExtractor {
try {
output.flush();
output.close();
if (this.outOfSpace) {
Files.delete(Paths.get(this.localAbsPath));
}
} catch (IOException e) {
logger.log(Level.SEVERE, "Error closing unpack stream for file: {0}", localAbsPath); //NON-NLS
}
@ -774,7 +816,7 @@ class SevenZipExtractor {
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error adding a derived file to db:" + fileName, ex); //NON-NLS
throw new TskCoreException(
NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
NbBundle.getMessage(SevenZipExtractor.class, "EmbeddedFileExtractorIngestModule.ArchiveExtractor.UnpackedTree.exception.msg",
fileName), ex);
}

View File

@ -22,10 +22,21 @@ import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.lang.GeoLocation;
import com.drew.lang.Rational;
import com.drew.metadata.Directory;
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.ExifSubIFDDirectory;
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.IOException;
import java.io.InputStream;
@ -33,6 +44,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.openide.util.NbBundle;
@ -47,6 +60,8 @@ import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
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.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@ -63,10 +78,13 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
private final IngestServices services = IngestServices.getInstance();
private final AtomicInteger filesProcessed = new AtomicInteger(0);
private volatile boolean filesToFire = false;
private volatile boolean facesDetected = false;
private final List<BlackboardArtifact> listOfFacesDetectedArtifacts = new ArrayList<>();
private long jobId;
private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
private FileTypeDetector fileTypeDetector;
private final HashSet<String> supportedMimeTypes = new HashSet<>();
private TimeZone timeZone = null;
ExifParserFileIngestModule() {
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
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));
filesToFire = false;
}
if (facesDetected) {
services.fireModuleDataEvent(new ModuleDataEvent(ExifParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_FACE_DETECTED, listOfFacesDetectedArtifacts));
listOfFacesDetectedArtifacts.clear();
facesDetected = false;
}
}
//skip unsupported
if (!parsableFormat(content)) {
@ -125,19 +150,32 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
bin = new BufferedInputStream(in);
Collection<BlackboardAttribute> attributes = new ArrayList<>();
Metadata metadata = ImageMetadataReader.readMetadata(bin, true);
Metadata metadata = ImageMetadataReader.readMetadata(bin);
// Date
ExifSubIFDDirectory exifDir = metadata.getDirectory(ExifSubIFDDirectory.class);
ExifSubIFDDirectory exifDir = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
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) {
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID(), ExifParserModuleFactory.getModuleName(), date.getTime() / 1000));
}
}
// GPS Stuff
GpsDirectory gpsDir = metadata.getDirectory(GpsDirectory.class);
GpsDirectory gpsDir = metadata.getFirstDirectoryOfType(GpsDirectory.class);
if (gpsDir != null) {
GeoLocation loc = gpsDir.getGeoLocation();
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));
}
Rational altitude = gpsDir.getRational(GpsDirectory.TAG_GPS_ALTITUDE);
Rational altitude = gpsDir.getRational(GpsDirectory.TAG_ALTITUDE);
if (altitude != null) {
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID(), ExifParserModuleFactory.getModuleName(), altitude.doubleValue()));
}
}
// Device info
ExifIFD0Directory devDir = metadata.getDirectory(ExifIFD0Directory.class);
ExifIFD0Directory devDir = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
if (devDir != null) {
String model = devDir.getString(ExifIFD0Directory.TAG_MODEL);
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
if (!attributes.isEmpty()) {
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
* image (by signature)
@ -225,10 +383,15 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
public void shutDown() {
// We only need to check for this final event on the last module per job
if (refCounter.decrementAndGet(jobId) == 0) {
timeZone = null;
if (filesToFire) {
//send the final new data event
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));
}
}
}
}

View File

@ -1,4 +1,3 @@
OpenIDE-Module-Name=Timeline
CTL_MakeTimeline="Timeline"
CTL_TimeLineTopComponentAction=TimeLineTopComponent
CTL_TimeLineTopComponent=Timeline Window
@ -11,10 +10,6 @@ Timeline.goToButton.text=Go To\:
Timeline.yearBarChart.x.years=Years
Timeline.resultPanel.loading=Loading...
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
TimelinePanel.jButton1.text=6m
TimelinePanel.jButton13.text=all
@ -29,18 +24,10 @@ TimelinePanel.jButton7.text=3d
TimelinePanel.jButton2.text=1m
TimelinePanel.jButton3.text=3m
TimelinePanel.jButton4.text=2w
ProgressWindow.progressHeader.text=\
TimeLineTopComponent.eventsTab.name=Events
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
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.
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=\

View File

@ -54,7 +54,10 @@
<Component class="javax.swing.JLabel" name="progressHeader">
<Properties>
<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, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/timeline/Bundle.properties" key="ProgressWindow.progressHeader.text" replaceFormat="NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[10, 14]"/>
</Property>
</Properties>
</Component>

View File

@ -21,25 +21,19 @@ package org.sleuthkit.autopsy.timeline;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.annotation.concurrent.Immutable;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.GroupLayout;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.KeyStroke;
import javax.swing.LayoutStyle;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.openide.awt.Mnemonics;
import org.openide.util.NbBundle;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
/**
* 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
*/
@NbBundle.Messages({"Timeline.progressWindow.name=Timeline",
"Timeline.progressWindow.title=Generating Timeline data"})
public ProgressWindow(Component parent, boolean modal, SwingWorker<?, ?> worker) {
super();
initComponents();
@ -64,9 +60,8 @@ public class ProgressWindow extends JFrame {
setIconImage(WindowManager.getDefault().getMainWindow().getIconImage());
});
//progressBar.setIndeterminate(true);
setName(NbBundle.getMessage(TimeLineTopComponent.class, "Timeline.progressWindow.name"));
setTitle(NbBundle.getMessage(TimeLineTopComponent.class, "Timeline.progressWindow.title"));
setName(Bundle.Timeline_progressWindow_name());
setTitle(Bundle.Timeline_progressWindow_title());
// Close the dialog when Esc is pressed
String cancelName = "cancel"; // NON-NLS
InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
@ -83,46 +78,6 @@ public class ProgressWindow extends JFrame {
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.
* 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
private void initComponents() {
progressBar = new JProgressBar();
progressHeader = new JLabel();
progressBar = new javax.swing.JProgressBar();
progressHeader = new javax.swing.JLabel();
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent evt) {
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent 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);
layout.setHorizontalGroup(
layout.createParallelGroup(GroupLayout.Alignment.LEADING)
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(progressBar, GroupLayout.DEFAULT_SIZE, 504, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(progressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 504, Short.MAX_VALUE)
.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)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(GroupLayout.Alignment.LEADING)
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(progressHeader)
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressBar, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(progressHeader, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
pack();
@ -176,14 +132,14 @@ public class ProgressWindow extends JFrame {
cancel();
}//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() {
SwingUtilities.invokeLater(() -> {
if (isVisible()) {
int showConfirmDialog = JOptionPane.showConfirmDialog(ProgressWindow.this,
NbBundle.getMessage(TimeLineTopComponent.class,
"Timeline.ProgressWindow.cancel.confdlg.msg"),
NbBundle.getMessage(TimeLineTopComponent.class,
"Timeline.ProgressWindow.cancel.confdlg.detail"),
Bundle.Timeline_ProgressWindow_cancel_confdlg_msg(),
Bundle.Timeline_ProgressWindow_cancel_confdlg_detail(),
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
if (showConfirmDialog == JOptionPane.YES_OPTION) {
close();
@ -200,18 +156,23 @@ public class ProgressWindow extends JFrame {
dispose();
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private JProgressBar progressBar;
private JLabel progressHeader;
private javax.swing.JProgressBar progressBar;
private javax.swing.JLabel progressHeader;
// End of variables declaration//GEN-END:variables
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void update(ProgressUpdate chunk) {
updateHeaderMessage(chunk.getHeaderMessage());
progressHeader.setText(chunk.getHeaderMessage());
if (chunk.getTotal() >= 0) {
setProgressTotal(chunk.getTotal());
updateProgress(chunk.getProgress(), chunk.getDetailMessage());
progressBar.setIndeterminate(false);
progressBar.setMaximum(chunk.getTotal());
progressBar.setStringPainted(true);
progressBar.setValue(chunk.getProgress());
progressBar.setString(chunk.getDetailMessage());
} else {
setIndeterminate();
updateProgress(chunk.getDetailMessage());
progressBar.setIndeterminate(true);
progressBar.setStringPainted(true);
progressBar.setString(chunk.getDetailMessage());
}
}

View File

@ -34,6 +34,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
@ -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.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.events.BlackBoardArtifactTagAddedEvent;
import org.sleuthkit.autopsy.events.BlackBoardArtifactTagDeletedEvent;
import org.sleuthkit.autopsy.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.db.EventsRepository;
import org.sleuthkit.autopsy.timeline.events.type.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.db.EventsRepository;
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
import org.sleuthkit.autopsy.timeline.utils.IntervalUtils;
@ -99,13 +101,11 @@ import org.sleuthkit.datamodel.TskCoreException;
* </li>
* <ul>
*/
@NbBundle.Messages({"Timeline.confirmation.dialogs.title=Update Timeline database?"})
public class TimeLineController {
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());
public static ZoneId getTimeZoneID() {
@ -189,6 +189,9 @@ public class TimeLineController {
@GuardedBy("this")
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
//selected events (ie shown in the result viewer)
@GuardedBy("this")
@ -231,16 +234,30 @@ public class TimeLineController {
private final ReadOnlyBooleanWrapper newEventsFlag = new ReadOnlyBooleanWrapper(false);
public TimeLineController(Case autoCase) {
this.autoCase = autoCase; //initalize repository and filteredEvents on creation
eventsRepository = new EventsRepository(autoCase, historyManager.currentState());
this.autoCase = autoCase;
/*
* 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();
InitialZoomState = new ZoomParams(filteredEvents.getSpanningInterval(),
EventTypeZoomLevel.BASE_TYPE,
filteredEvents.filter().get(),
filteredEvents.filterProperty().get(),
DescriptionLOD.SHORT);
historyManager.advance(InitialZoomState);
}
/**
@ -256,7 +273,7 @@ public class TimeLineController {
public void zoomOutToActivity() {
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
synchronized (eventsRepository) {
eventsRepository.rebuildRepository(() -> {
synchronized (eventsRepository) {
eventsRepository.recordLastObjID(lastObjId);
eventsRepository.recordLastArtifactID(lastArtfID);
@ -303,7 +319,7 @@ public class TimeLineController {
Platform.runLater(() -> {
//TODO: should this be an event?
newEventsFlag.set(false);
historyManager.reset(filteredEvents.getRequestedZoomParamters().get());
historyManager.reset(filteredEvents.zoomParametersProperty().get());
TimeLineController.this.showFullRange();
});
});
@ -315,6 +331,28 @@ public class TimeLineController {
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() {
synchronized (filteredEvents) {
pushTimeRange(filteredEvents.getSpanningInterval());
@ -337,7 +375,6 @@ public class TimeLineController {
* show the timeline window and prompt for rebuilding database if necessary.
*/
synchronized void openTimeLine() {
// listen for case changes (specifically images being added, and case changes).
if (Case.isCaseOpen() && !listeningToAutopsy) {
IngestManager.getInstance().addIngestModuleEventListener(ingestModuleListener);
@ -347,13 +384,16 @@ public class TimeLineController {
}
try {
boolean repoRebuilt = false; //has the repo been rebuilt
long timeLineLastObjectId = eventsRepository.getLastObjID();
boolean repoRebuilt = false;
//if the repo is empty rebuild it
if (timeLineLastObjectId == -1) {
repoRebuilt = rebuildRepo();
}
if (repoRebuilt == false) {
//if ingest was running uring last rebuild, prompt to rebuild
if (eventsRepository.getWasIngestRunning()) {
if (confirmLastBuiltDuringIngestRebuild()) {
repoRebuilt = rebuildRepo();
@ -363,6 +403,7 @@ public class TimeLineController {
if (repoRebuilt == false) {
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
|| getCaseLastArtifactID(sleuthkitCase) != eventsRepository.getLastArtfactID()) {
if (confirmOutOfDateRebuild()) {
@ -372,8 +413,8 @@ public class TimeLineController {
}
if (repoRebuilt == false) {
boolean hasDSInfo = eventsRepository.hasDataSourceInfo();
if (hasDSInfo == false) {
// if the TLDB schema has been upgraded since last time TL ran, prompt for rebuild
if (eventsRepository.hasNewColumns() == false) {
if (confirmDataSourceIDsMissingRebuild()) {
repoRebuilt = rebuildRepo();
}
@ -381,12 +422,11 @@ public class TimeLineController {
}
/*
* if the repo was not rebuilt show the UI. If the repo was rebuild
* it will be displayed as part of that process
* if the repo was not rebuilt at minimum rebuild the tags which may
* have been updated without our knowing it.
*/
if (repoRebuilt == false) {
showWindow();
showFullRange();
rebuildTagsTable();
}
} catch (TskCoreException ex) {
@ -419,13 +459,13 @@ public class TimeLineController {
*/
synchronized public void pushPeriod(ReadablePeriod period) {
synchronized (filteredEvents) {
final DateTime middleOf = IntervalUtils.middleOf(filteredEvents.timeRange().get());
final DateTime middleOf = IntervalUtils.middleOf(filteredEvents.timeRangeProperty().get());
pushTimeRange(IntervalUtils.getIntervalAround(middleOf, period));
}
}
synchronized public void pushZoomOutTime() {
final Interval timeRange = filteredEvents.timeRange().get();
final Interval timeRange = filteredEvents.timeRangeProperty().get();
long toDurationMillis = timeRange.toDurationMillis() / 4;
DateTime start = timeRange.getStart().minus(toDurationMillis);
DateTime end = timeRange.getEnd().plus(toDurationMillis);
@ -433,7 +473,7 @@ public class TimeLineController {
}
synchronized public void pushZoomInTime() {
final Interval timeRange = filteredEvents.timeRange().get();
final Interval timeRange = filteredEvents.timeRangeProperty().get();
long toDurationMillis = timeRange.toDurationMillis() / 4;
DateTime start = timeRange.getStart().plus(toDurationMillis);
DateTime end = timeRange.getEnd().minus(toDurationMillis);
@ -481,7 +521,7 @@ public class TimeLineController {
if (mainFrame == null) {
LOGGER.log(Level.WARNING, "Tried to show timeline with invalid window. Rebuilding GUI."); // NON-NLS
mainFrame = (TimeLineTopComponent) WindowManager.getDefault().findTopComponent(
NbBundle.getMessage(TimeLineTopComponent.class, "CTL_TimeLineTopComponentAction"));
NbBundle.getMessage(TimeLineController.class, "CTL_TimeLineTopComponentAction"));
if (mainFrame == null) {
mainFrame = new TimeLineTopComponent();
}
@ -495,7 +535,7 @@ public class TimeLineController {
}
synchronized public void pushEventTypeZoom(EventTypeZoomLevel typeZoomeLevel) {
ZoomParams currentZoom = filteredEvents.getRequestedZoomParamters().get();
ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
if (currentZoom == null) {
advance(InitialZoomState.withTypeZoomLevel(typeZoomeLevel));
} else if (currentZoom.hasTypeZoomLevel(typeZoomeLevel) == false) {
@ -505,7 +545,7 @@ public class TimeLineController {
synchronized public void pushTimeRange(Interval timeRange) {
// timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
ZoomParams currentZoom = filteredEvents.getRequestedZoomParamters().get();
ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
if (currentZoom == null) {
advance(InitialZoomState.withTimeRange(timeRange));
} 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) {
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);
boolean shouldContinue = true;
if (newLOD == DescriptionLOD.FULL && count > 10_000) {
String format = NumberFormat.getInstance().format(count);
int showConfirmDialog = JOptionPane.showConfirmDialog(mainFrame,
NbBundle.getMessage(this.getClass(),
"Timeline.pushDescrLOD.confdlg.msg",
NumberFormat.getInstance().format(count)),
NbBundle.getMessage(TimeLineTopComponent.class,
"Timeline.pushDescrLOD.confdlg.details"),
Bundle.Timeline_pushDescrLOD_confdlg_msg(format),
Bundle.Timeline_confirmation_dialogs_title(),
JOptionPane.YES_NO_OPTION);
shouldContinue = (showConfirmDialog == JOptionPane.YES_OPTION);
}
if (shouldContinue) {
ZoomParams currentZoom = filteredEvents.getRequestedZoomParamters().get();
ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
if (currentZoom == null) {
advance(InitialZoomState.withDescrLOD(newLOD));
} else if (currentZoom.hasDescrLOD(newLOD) == false) {
@ -544,7 +585,7 @@ public class TimeLineController {
synchronized public void pushTimeAndType(Interval timeRange, EventTypeZoomLevel typeZoom) {
// timeRange = this.filteredEvents.getSpanningInterval().overlap(timeRange);
ZoomParams currentZoom = filteredEvents.getRequestedZoomParamters().get();
ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
if (currentZoom == null) {
advance(InitialZoomState.withTimeAndType(timeRange, typeZoom));
} else if (currentZoom.hasTimeRange(timeRange) == false && currentZoom.hasTypeZoomLevel(typeZoom) == false) {
@ -557,7 +598,7 @@ public class TimeLineController {
}
synchronized public void pushFilters(RootFilter filter) {
ZoomParams currentZoom = filteredEvents.getRequestedZoomParamters().get();
ZoomParams currentZoom = filteredEvents.zoomParametersProperty().get();
if (currentZoom == null) {
advance(InitialZoomState.withFilter(filter.copyOf()));
} else if (currentZoom.hasFilter(filter) == false) {
@ -565,17 +606,17 @@ public class TimeLineController {
}
}
synchronized public ZoomParams advance() {
return historyManager.advance();
synchronized public void advance() {
historyManager.advance();
}
synchronized public ZoomParams retreat() {
return historyManager.retreat();
synchronized public void retreat() {
historyManager.retreat();
}
synchronized private void advance(ZoomParams newState) {
historyManager.advance(newState);
}
public void selectTimeAndType(Interval interval, EventType type) {
@ -683,16 +724,36 @@ public class TimeLineController {
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
* from the database and that the datasource filter will not work
*
* @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() {
return JOptionPane.showConfirmDialog(mainFrame,
NbBundle.getMessage(TimeLineController.class, "datasource.missing.confirmation"),
"Update Timeline database?",
Bundle.datasource_missing_confirmation(),
Bundle.Timeline_confirmation_dialogs_title(),
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
}
@ -703,11 +764,14 @@ public class TimeLineController {
*
* @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() {
return JOptionPane.showConfirmDialog(mainFrame,
DO_REPOPULATE_MESSAGE,
NbBundle.getMessage(TimeLineTopComponent.class,
"Timeline.showLastPopulatedWhileIngestingConf.confDlg.details"),
Bundle.Timeline_do_repopulate_msg(),
Bundle.Timeline_confirmation_dialogs_title(),
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
}
@ -718,12 +782,12 @@ public class TimeLineController {
*
* @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 {
return JOptionPane.showConfirmDialog(mainFrame,
NbBundle.getMessage(TimeLineController.class,
"Timeline.propChg.confDlg.timelineOOD.msg"),
NbBundle.getMessage(TimeLineController.class,
"Timeline.propChg.confDlg.timelineOOD.details"),
Bundle.Timeline_propChg_confDlg_timelineOOD_msg(),
Bundle.Timeline_confirmation_dialogs_title(),
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
}
@ -733,12 +797,13 @@ public class TimeLineController {
*
* @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 {
return JOptionPane.showConfirmDialog(mainFrame,
NbBundle.getMessage(TimeLineController.class,
"Timeline.initTimeline.confDlg.genBeforeIngest.msg"),
NbBundle.getMessage(TimeLineController.class,
"Timeline.initTimeline.confDlg.genBeforeIngest.details"),
Bundle.Timeline_initTimeline_confDlg_genBeforeIngest_msg(),
Bundle.Timeline_confirmation_dialogs_title(),
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION;
}
@ -748,16 +813,9 @@ public class TimeLineController {
public void propertyChange(PropertyChangeEvent evt) {
switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
case CONTENT_CHANGED:
// ((ModuleContentEvent)evt.getOldValue())????
//ModuleContentEvent doesn't seem to provide any usefull information...
break;
case DATA_ADDED:
// Collection<BlackboardArtifact> artifacts = ((ModuleDataEvent) evt.getOldValue()).getArtifacts();
//new artifacts, insert them into db
break;
case FILE_DONE:
// Long fileID = (Long) evt.getOldValue();
//update file (known status) for file with id
Platform.runLater(() -> {
newEventsFlag.set(true);
});
@ -774,14 +832,7 @@ public class TimeLineController {
switch (IngestManager.IngestJobEvent.valueOf(evt.getPropertyName())) {
case CANCELLED:
case COMPLETED:
//if we are doing incremental updates, drop this
SwingUtilities.invokeLater(() -> {
if (isWindowOpen()) {
if (confirmOutOfDateRebuild()) {
rebuildRepo();
}
}
});
SwingUtilities.invokeLater(TimeLineController.this::confirmOutOfDateRebuildIfWindowOpen);
}
}
}
@ -793,27 +844,27 @@ public class TimeLineController {
public void propertyChange(PropertyChangeEvent evt) {
switch (Case.Events.valueOf(evt.getPropertyName())) {
case BLACKBOARD_ARTIFACT_TAG_ADDED:
filteredEvents.handleTagAdded((BlackBoardArtifactTagAddedEvent) evt);
executor.submit(() -> {
filteredEvents.handleArtifactTagAdded((BlackBoardArtifactTagAddedEvent) evt);
});
break;
case BLACKBOARD_ARTIFACT_TAG_DELETED:
filteredEvents.handleTagDeleted((BlackBoardArtifactTagDeletedEvent) evt);
executor.submit(() -> {
filteredEvents.handleArtifactTagDeleted((BlackBoardArtifactTagDeletedEvent) evt);
});
break;
case CONTENT_TAG_ADDED:
filteredEvents.handleTagAdded((ContentTagAddedEvent) evt);
executor.submit(() -> {
filteredEvents.handleContentTagAdded((ContentTagAddedEvent) evt);
});
break;
case CONTENT_TAG_DELETED:
filteredEvents.handleTagDeleted((ContentTagDeletedEvent) evt);
executor.submit(() -> {
filteredEvents.handleContentTagDeleted((ContentTagDeletedEvent) evt);
});
break;
case DATA_SOURCE_ADDED:
// Content content = (Content) evt.getNewValue();
//if we are doing incremental updates, drop this
SwingUtilities.invokeLater(() -> {
if (isWindowOpen()) {
if (confirmOutOfDateRebuild()) {
rebuildRepo();
}
}
});
SwingUtilities.invokeLater(TimeLineController.this::confirmOutOfDateRebuildIfWindowOpen);
break;
case CURRENT_CASE:
OpenTimelineAction.invalidateController();

View File

@ -24,7 +24,7 @@ package org.sleuthkit.autopsy.timeline;
* Most implementations should install the relevant listeners in their
* {@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 {

View File

@ -23,7 +23,7 @@ import javafx.event.ActionEvent;
import org.controlsfx.control.action.Action;
import org.openide.util.NbBundle;
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.
@ -37,12 +37,12 @@ public class ResetFilters extends Action {
eventsModel = controller.getEventsModel();
disabledProperty().bind(new BooleanBinding() {
{
bind(eventsModel.getRequestedZoomParamters());
bind(eventsModel.zoomParametersProperty());
}
@Override
protected boolean computeValue() {
return eventsModel.getRequestedZoomParamters().getValue().getFilter().equals(eventsModel.getDefaultFilter());
return eventsModel.zoomParametersProperty().getValue().getFilter().equals(eventsModel.getDefaultFilter());
}
});
setEventHandler((ActionEvent t) -> {

View File

@ -82,9 +82,9 @@ public class SaveSnapshot extends Action {
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<>("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<>("Filters", get.getFilter().getHTMLReportString())); // NON-NLS

View File

@ -23,7 +23,7 @@ import javafx.event.ActionEvent;
import org.controlsfx.control.action.Action;
import org.openide.util.NbBundle;
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();
disabledProperty().bind(new BooleanBinding() {
{
bind(eventsModel.getRequestedZoomParamters());
bind(eventsModel.zoomParametersProperty());
}
@Override
protected boolean computeValue() {
return eventsModel.getRequestedZoomParamters().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
return eventsModel.zoomParametersProperty().getValue().getTimeRange().contains(eventsModel.getSpanningInterval());
}
});
setEventHandler((ActionEvent t) -> {

View File

@ -16,14 +16,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.timeline.events;
package org.sleuthkit.autopsy.timeline.datamodel;
import com.google.common.collect.Sets;
import java.util.Collections;
import java.util.Set;
import javax.annotation.concurrent.Immutable;
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.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);
}
/**
* 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
}
}

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.timeline.events;
package org.sleuthkit.autopsy.timeline.datamodel;
import com.google.common.eventbus.EventBus;
import java.util.Collection;
@ -27,6 +27,7 @@ import java.util.logging.Level;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javax.annotation.concurrent.GuardedBy;
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.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.events.db.EventsRepository;
import org.sleuthkit.autopsy.timeline.events.type.EventType;
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
import org.sleuthkit.autopsy.timeline.db.EventsRepository;
import org.sleuthkit.autopsy.timeline.events.RefreshRequestedEvent;
import org.sleuthkit.autopsy.timeline.events.TagsUpdatedEvent;
import org.sleuthkit.autopsy.timeline.filters.DataSourceFilter;
import org.sleuthkit.autopsy.timeline.filters.DataSourcesFilter;
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.HideKnownFilter;
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.zooming.DescriptionLOD;
import org.sleuthkit.autopsy.timeline.zooming.EventTypeZoomLevel;
import org.sleuthkit.autopsy.timeline.zooming.ZoomParams;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
@ -83,7 +91,6 @@ public final class FilteredEventsModel {
private static final Logger LOGGER = Logger.getLogger(FilteredEventsModel.class.getName());
/**
* time range that spans the filtered events
*/
@ -113,40 +120,25 @@ public final class FilteredEventsModel {
private final EventsRepository repo;
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) {
this.repo = repo;
this.autoCase = repo.getAutoCase();
repo.getDatasourcesMap().addListener((MapChangeListener.Change<? extends Long, ? extends String> change) -> {
DataSourceFilter dataSourceFilter = new DataSourceFilter(change.getValueAdded(), change.getKey());
RootFilter rootFilter = filter().get();
rootFilter.getDataSourcesFilter().addDataSourceFilter(dataSourceFilter);
RootFilter rootFilter = filterProperty().get();
rootFilter.getDataSourcesFilter().addSubFilter(dataSourceFilter);
requestedFilter.set(rootFilter.copyOf());
});
repo.getHashSetMap().addListener((MapChangeListener.Change<? extends Long, ? extends String> change) -> {
HashSetFilter hashSetFilter = new HashSetFilter(change.getValueAdded(), change.getKey());
RootFilter rootFilter = filter().get();
rootFilter.getHashHitsFilter().addHashSetFilter(hashSetFilter);
RootFilter rootFilter = filterProperty().get();
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(getDefaultFilter());
@ -156,14 +148,14 @@ public final class FilteredEventsModel {
if (zoomParams != null) {
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.getTimeRange().equals(requestedTimeRange.get()) == false) {
requestedTypeZoom.set(zoomParams.getTypeZoomLevel());
requestedFilter.set(zoomParams.getFilter().copyOf());
requestedTimeRange.set(zoomParams.getTimeRange());
requestedLOD.set(zoomParams.getDescrLOD());
requestedLOD.set(zoomParams.getDescriptionLOD());
}
}
});
@ -171,12 +163,75 @@ public final class FilteredEventsModel {
requestedZoomParamters.bind(currentStateProperty);
}
public Interval getBoundingEventsInterval() {
return repo.getBoundingEventsInterval(getRequestedZoomParamters().get().getTimeRange(), getRequestedZoomParamters().get().getFilter());
synchronized public ReadOnlyObjectProperty<ZoomParams> zoomParametersProperty() {
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) {
@ -187,6 +242,18 @@ public final class FilteredEventsModel {
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) {
final Interval overlap;
final RootFilter intersect;
@ -219,25 +286,6 @@ public final class FilteredEventsModel {
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
* repository, ignoring any filters or requested ranges
@ -303,58 +351,52 @@ public final class FilteredEventsModel {
return repo.getAggregatedEvents(params);
}
synchronized public ReadOnlyObjectProperty<EventTypeZoomLevel> eventTypeZoom() {
return requestedTypeZoom.getReadOnlyProperty();
synchronized public boolean handleContentTagAdded(ContentTagAddedEvent evt) {
ContentTag contentTag = evt.getTag();
Content content = contentTag.getContent();
Set<Long> updatedEventIDs = repo.addTag(content.getId(), null, contentTag);
return postTagsUpdated(updatedEventIDs);
}
synchronized public EventTypeZoomLevel getEventTypeZoom() {
return requestedTypeZoom.get();
synchronized public boolean handleArtifactTagAdded(BlackBoardArtifactTagAddedEvent evt) {
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() {
return requestedLOD.get();
}
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();
synchronized public boolean handleContentTagDeleted(ContentTagDeletedEvent evt) {
ContentTag contentTag = evt.getTag();
Content content = contentTag.getContent();
try {
boolean tagged = autoCase.getServices().getTagsManager().getContentTagsByContent(content).isEmpty() == false;
Set<Long> updatedEventIDs = repo.markEventsTagged(content.getId(), null, tagged);
if (!updatedEventIDs.isEmpty()) {
eventbus.post(new EventsUnTaggedEvent(updatedEventIDs));
}
Set<Long> updatedEventIDs = repo.deleteTag(content.getId(), null, contentTag, tagged);
return postTagsUpdated(updatedEventIDs);
} catch (TskCoreException 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) {
@ -364,4 +406,9 @@ public final class FilteredEventsModel {
synchronized public void unRegisterForEvents(Object o) {
eventbus.unregister(0);
}
public void refresh() {
eventbus.post(new RefreshRequestedEvent());
}
}

View File

@ -16,47 +16,49 @@
* See the License for the specific language governing permissions and
* 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 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;
/**
*
* A single event.
*/
@Immutable
public class TimeLineEvent {
private final Long eventID;
private final Long fileID;
private final Long time;
private final long eventID;
private final long fileID;
private final Long artifactID;
private final long dataSourceID;
private final long time;
private final EventType subType;
private final String fullDescription, medDescription, shortDescription;
private final ImmutableMap<DescriptionLOD, String> descriptions;
private final TskData.FileKnown known;
private final boolean hashHit;
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.fileID = objID;
this.artifactID = artifactID;
this.artifactID = artifactID == 0 ? null : artifactID;
this.time = time;
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.hashHit = hashHit;
this.tagged = tagged;
this.dataSourceID = dataSourceID;
}
public boolean isTagged() {
@ -72,18 +74,18 @@ public class TimeLineEvent {
return artifactID;
}
public Long getEventID() {
public long getEventID() {
return eventID;
}
public Long getFileID() {
public long getFileID() {
return fileID;
}
/**
* @return the time in seconds from unix epoch
*/
public Long getTime() {
public long getTime() {
return time;
}
@ -92,18 +94,26 @@ public class TimeLineEvent {
}
public String getFullDescription() {
return fullDescription;
return getDescription(DescriptionLOD.FULL);
}
public String getMedDescription() {
return medDescription;
return getDescription(DescriptionLOD.MEDIUM);
}
public String getShortDescription() {
return shortDescription;
return getDescription(DescriptionLOD.SHORT);
}
public TskData.FileKnown getKnown() {
return known;
}
public String getDescription(DescriptionLOD lod) {
return descriptions.get(lod);
}
public long getDataSourceID() {
return dataSourceID;
}
}

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* 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.List;

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* 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.List;
@ -70,8 +70,9 @@ public enum BaseTypes implements EventType {
private final String iconBase;
private Image image;
private final Image image;
@Override
public Image getFXImage() {
return image;
}

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* 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.Comparator;

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* 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.List;

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* 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.Collections;

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* 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.Collections;

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* 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.List;

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* 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.SetMultimap;
@ -32,6 +32,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -44,37 +45,30 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTimeZone;
import org.joda.time.Interval;
import org.joda.time.Period;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
import org.sleuthkit.autopsy.timeline.events.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.events.type.BaseTypes;
import org.sleuthkit.autopsy.timeline.events.type.EventType;
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.BaseTypes;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
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.TagsFilter;
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.DescriptionLOD.SHORT;
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.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TskData;
import org.sqlite.SQLiteJDBCLoader;
@ -88,7 +82,7 @@ import org.sqlite.SQLiteJDBCLoader;
public class EventDB {
/**
*
* enum to represent keys stored in db_info table
*/
private enum DBInfoKey {
@ -150,17 +144,21 @@ public class EventDB {
private PreparedStatement getMaxTimeStmt;
private PreparedStatement getMinTimeStmt;
private PreparedStatement getDataSourceIDsStmt;
private PreparedStatement getHashSetNamesStmt;
private PreparedStatement insertRowStmt;
private PreparedStatement recordDBInfoStmt;
private PreparedStatement insertHashSetStmt;
private PreparedStatement insertHashHitStmt;
private PreparedStatement insertTagStmt;
private PreparedStatement deleteTagStmt;
private PreparedStatement selectHashSetStmt;
private PreparedStatement countAllEventsStmt;
private PreparedStatement dropEventsTableStmt;
private PreparedStatement dropHashSetHitsTableStmt;
private PreparedStatement dropHashSetsTableStmt;
private PreparedStatement dropTagsTableStmt;
private PreparedStatement dropDBInfoTableStmt;
private PreparedStatement selectEventsFromOBjectAndArtifactStmt;
private PreparedStatement selectEventIDsFromOBjectAndArtifactStmt;
private final Set<PreparedStatement> preparedStatements = new HashSet<>();
@ -196,9 +194,9 @@ public class EventDB {
public Interval getSpanningInterval(Collection<Long> eventIDs) {
DBLock.lock();
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()) {
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) {
LOGGER.log(Level.SEVERE, "Error executing get spanning interval query.", ex); // NON-NLS
@ -212,11 +210,11 @@ public class EventDB {
return new EventTransaction();
}
void commitTransaction(EventTransaction tr, Boolean notify) {
void commitTransaction(EventTransaction tr) {
if (tr.isClosed()) {
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) {
if (params.getTimeRange() != null) {
return countEvents(params.getTimeRange().getStartMillis() / 1000,
return countEventsByType(params.getTimeRange().getStartMillis() / 1000,
params.getTimeRange().getEndMillis() / 1000,
params.getFilter(), params.getTypeZoomLevel());
} 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
* over.
@ -266,10 +291,27 @@ public class EventDB {
dropEventsTableStmt.executeUpdate();
dropHashSetHitsTableStmt.executeUpdate();
dropHashSetsTableStmt.executeUpdate();
dropTagsTableStmt.executeUpdate();
dropDBInfoTableStmt.executeUpdate();
initializeDB();;
initializeDB();
} 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 {
DBLock.unlock();
}
@ -281,8 +323,8 @@ public class EventDB {
final String sqlWhere = SQLHelper.getSQLWhere(filter);
DBLock.lock();
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,"
+ "(select Min(time) from from events" + useHashHitTablesHelper(filter) + " where time >= " + end + " and " + sqlWhere + ") as end")) { // NON-NLS
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 events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time >= " + end + " AND " + sqlWhere + ") AS end")) { // NON-NLS
while (rs.next()) {
long start2 = rs.getLong("start"); // NON-NLS
@ -332,10 +374,9 @@ public class EventDB {
Set<Long> resultIDs = new HashSet<>();
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();
ResultSet rs = stmt.executeQuery(query)) {
while (rs.next()) {
resultIDs.add(rs.getLong("event_id"));
}
@ -357,11 +398,11 @@ public class EventDB {
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
* value for the datasource_id column in the events table.
*/
boolean hasNewColumns() {
return hasHashHitColumn() && hasDataSourceIDColumn() && hasTaggedColumn()
&& (getDataSourceIDs().isEmpty() == false);
}
@ -388,7 +429,7 @@ public class EventDB {
Map<Long, String> getHashSetNames() {
Map<Long, String> hashSets = new HashMap<>();
DBLock.lock();
try (ResultSet rs = con.createStatement().executeQuery("select * from hash_sets")) {
try (ResultSet rs = getHashSetNamesStmt.executeQuery();) {
while (rs.next()) {
long hashSetID = rs.getLong("hash_set_id");
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
*
*
* @return the number of rows in the table , count > 0 indicating an
* existing table
*/
@ -488,8 +528,9 @@ public class EventDB {
+ " full_description TEXT, " // NON-NLS
+ " med_description TEXT, " // NON-NLS
+ " short_description TEXT, " // NON-NLS
+ " known_state INTEGER,"
+ " hash_hit INTEGER)"; //boolean // NON-NLS
+ " known_state INTEGER," //boolean // NON-NLS
+ " hash_hit INTEGER," //boolean // NON-NLS
+ " tagged INTEGER)"; //boolean // NON-NLS
stmt.execute(sql);
} catch (SQLException ex) {
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
stmt.execute(sql);
} catch (SQLException ex) {
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
stmt.execute(sql);
} catch (SQLException ex) {
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);
}
initializeTagsTable();
createIndex("events", Arrays.asList("file_id"));
createIndex("events", Arrays.asList("artifact_id"));
createIndex("events", Arrays.asList("time"));
createIndex("events", Arrays.asList("sub_type", "time"));
createIndex("events", Arrays.asList("base_type", "time"));
createIndex("events", Arrays.asList("known_state"));
@ -552,7 +594,7 @@ public class EventDB {
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
+ "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
getMaxTimeStmt = prepareStatement("SELECT Max(time) AS max 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 (?)");
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 (?,?)");
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");
dropEventsTableStmt = prepareStatement("DROP TABLE IF EXISTS events");
dropHashSetHitsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_set_hits");
dropHashSetsTableStmt = prepareStatement("DROP TABLE IF EXISTS hash_sets");
dropTagsTableStmt = prepareStatement("DROP TABLE IF EXISTS tags");
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) {
LOGGER.log(Level.SEVERE, "failed to prepareStatment", sQLException); // NON-NLS
}
} finally {
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
@ -628,11 +690,11 @@ public class EventDB {
void insertEvent(long time, EventType type, long datasourceID, long objID,
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();
insertEvent(time, type, datasourceID, objID, artifactID, fullDescription, medDescription, shortDescription, known, hashSets, tagged, transaction);
commitTransaction(transaction, true);
insertEvent(time, type, datasourceID, objID, artifactID, fullDescription, medDescription, shortDescription, known, hashSets, tags, transaction);
commitTransaction(transaction);
}
/**
@ -644,17 +706,13 @@ public class EventDB {
void insertEvent(long time, EventType type, long datasourceID, long objID,
Long artifactID, String fullDescription, String medDescription,
String shortDescription, TskData.FileKnown known, Set<String> hashSetNames,
boolean tagged,
EventTransaction transaction) {
List<? extends Tag> tags, EventTransaction transaction) {
if (transaction.isClosed()) {
throw new IllegalArgumentException("can't update database with closed transaction"); // NON-NLS
}
int typeNum;
int superTypeNum;
typeNum = RootEventType.allTypes.indexOf(type);
superTypeNum = type.getSuperType().ordinal();
int typeNum = RootEventType.allTypes.indexOf(type);
int superTypeNum = type.getSuperType().ordinal();
DBLock.lock();
try {
@ -684,7 +742,7 @@ public class EventDB {
insertRowStmt.setByte(10, known == null ? TskData.FileKnown.UNKNOWN.getFileKnownValue() : known.getFileKnownValue());
insertRowStmt.setInt(11, hashSetNames.isEmpty() ? 0 : 1);
insertRowStmt.setInt(12, tagged ? 1 : 0);
insertRowStmt.setInt(12, tags.isEmpty() ? 0 : 1);
insertRowStmt.executeUpdate();
@ -697,7 +755,7 @@ public class EventDB {
insertHashSetStmt.setString(1, name);
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 = ?"
selectHashSetStmt.setString(1, name);
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;
}
};
}
} catch (SQLException ex) {
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();
try {
selectEventsFromOBjectAndArtifactStmt.clearParameters();
selectEventsFromOBjectAndArtifactStmt.setLong(1, objectID);
if (Objects.isNull(artifactID)) {
selectEventsFromOBjectAndArtifactStmt.setNull(2, Types.NULL);
} else {
selectEventsFromOBjectAndArtifactStmt.setLong(2, artifactID);
Set<Long> eventIDs = markEventsTagged(objectID, artifactID, true);
for (Long eventID : eventIDs) {
insertTag(tag, eventID);
}
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()) {
eventIDs.add(executeQuery.getLong("event_id"));
}
}
//then update tagged state for all event with selected ids
try (Statement updateStatement = con.createStatement();) {
updateStatement.executeUpdate("UPDATE events SET tagged = " + (tagged ? 1 : 0)
+ " 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;
}
@ -768,19 +930,6 @@ public class EventDB {
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 {
for (PreparedStatement pStmt : preparedStatements) {
pStmt.close();
@ -789,7 +938,7 @@ public class EventDB {
private void configureDB() throws SQLException {
DBLock.lock();
//this should match Sleuthkit db setupt
//this should match Sleuthkit db setup
try (Statement statement = con.createStatement()) {
//reduce i/o operations, we have no OS crash recovery anyway
statement.execute("PRAGMA synchronous = OFF;"); // NON-NLS
@ -813,11 +962,13 @@ public class EventDB {
SQLiteJDBCLoader.getVersion(), SQLiteJDBCLoader.isNativeMode()
? "native" : "pure-java")); // NON-NLS
} 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 {
return new TimeLineEvent(rs.getLong("event_id"),
rs.getLong("datasource_id"),
rs.getLong("file_id"),
rs.getLong("artifact_id"),
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
* the counts in a hierarchy from date > eventtype> count
*
*
* @param startTime events before this time will be excluded (seconds from
* unix epoch)
* @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>
* 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)) {
endTime++;
}
@ -857,9 +1007,9 @@ public class EventDB {
final boolean useSubTypes = (zoomLevel == EventTypeZoomLevel.SUB_TYPE);
//get some info about the range of dates requested
final String queryString = "select count(*), " + useSubTypeHelper(useSubTypes)
+ " from events" + useHashHitTablesHelper(filter) + " where time >= " + startTime + " and time < " + endTime + " and " + SQLHelper.getSQLWhere(filter) // NON-NLS
+ " GROUP BY " + useSubTypeHelper(useSubTypes); // NON-NLS
final String queryString = "SELECT count(DISTINCT events.event_id) AS count, " + typeColumnHelper(useSubTypes)
+ " FROM events" + useHashHitTablesHelper(filter) + useTagTablesHelper(filter) + " WHERE time >= " + startTime + " AND time < " + endTime + " AND " + SQLHelper.getSQLWhere(filter) // NON-NLS
+ " GROUP BY " + typeColumnHelper(useSubTypes); // NON-NLS
DBLock.lock();
try (Statement stmt = con.createStatement();
@ -869,7 +1019,7 @@ public class EventDB {
? RootEventType.allTypes.get(rs.getInt("sub_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) {
@ -880,119 +1030,140 @@ public class EventDB {
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.
*
* 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
* get a list of {@link AggregateEvent}s, clustered according to the given
* zoom paramaters.
*
* @param params the zoom params that determine the zooming, filtering and
* clustering.
*
* @return a list of aggregate events within the given timerange, that pass
* the supplied filter, aggregated according to the given event type
* and description zoom levels
*/
private List<AggregateEvent> getAggregatedEvents(Interval timeRange, RootFilter filter, EventTypeZoomLevel zoomLevel, DescriptionLOD lod) {
String descriptionColumn = getDescriptionColumn(lod);
final boolean useSubTypes = (zoomLevel.equals(EventTypeZoomLevel.SUB_TYPE));
List<AggregateEvent> getAggregatedEvents(ZoomParams params) {
//unpack params
Interval timeRange = params.getTimeRange();
RootFilter filter = params.getFilter();
DescriptionLOD descriptionLOD = params.getDescriptionLOD();
EventTypeZoomLevel typeZoomLevel = params.getTypeZoomLevel();
//get some info about the time range requested
RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(timeRange);
//use 'rounded out' range
long start = timeRange.getStartMillis() / 1000;//.getLowerBound();
long end = timeRange.getEndMillis() / 1000;//Millis();//rangeInfo.getUpperBound();
if (Objects.equals(start, end)) {
//ensure length of querried interval is not 0
long start = timeRange.getStartMillis() / 1000;
long end = timeRange.getEndMillis() / 1000;
if (start == end) {
end++;
}
//get some info about the time range requested
RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(timeRange);
//get a sqlite srtftime format string
String strfTimeFormat = getStrfTimeFormat(rangeInfo.getPeriodSize());
//build dynamic parts of query
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)
Map<EventType, SetMultimap< String, AggregateEvent>> typeMap = new HashMap<>();
//compose query string
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();
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)
+ " from events" + useHashHitTablesHelper(filter) + " where " + "time >= " + start + " and time < " + end + " and " + SQLHelper.getSQLWhere(filter) // NON-NLS
+ " 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);) {
try (Statement createStatement = con.createStatement();
ResultSet rs = createStatement.executeQuery(query)) {
while (rs.next()) {
Interval interval = new Interval(rs.getLong("Min(time)") * 1000, rs.getLong("Max(time)") * 1000, TimeLineController.getJodaTimeZone());
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);
events.add(aggregateEventHelper(rs, useSubTypes, descriptionLOD, filter.getTagsFilter()));
}
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) {
Exceptions.printStackTrace(ex);
LOGGER.log(Level.SEVERE, "Failed to get aggregate events with query: " + query, ex); // NON-NLS
} finally {
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
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 (SetMultimap<String, AggregateEvent> descrMap : typeMap.values()) {
//for each description ...
for (String descr : descrMap.keySet()) {
//run through the sorted events, merging together adjacent events
Iterator<AggregateEvent> iterator = descrMap.get(descr).stream()
.sorted((AggregateEvent o1, AggregateEvent o2)
-> Long.compare(o1.getSpan().getStartMillis(), o2.getSpan().getStartMillis()))
.sorted(Comparator.comparing(event -> event.getSpan().getStartMillis()))
.iterator();
AggregateEvent current = iterator.next();
while (iterator.hasNext()) {
@ -1013,17 +1184,10 @@ public class EventDB {
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;
}
private String useHashHitTablesHelper(RootFilter filter) {
return SQLHelper.hasActiveHashFilter(filter) ? ", hash_set_hits" : "";
}
private static String useSubTypeHelper(final boolean useSubTypes) {
private static String typeColumnHelper(final boolean useSubTypes) {
return useSubTypes ? "sub_type" : "base_type";
}
@ -1050,37 +1214,6 @@ public class EventDB {
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 {
PreparedStatement prepareStatement = con.prepareStatement(queryString);
preparedStatements.add(prepareStatement);
@ -1143,16 +1276,13 @@ public class EventDB {
}
}
private void commit(Boolean notify) {
private void commit() {
if (!closed) {
try {
con.commit();
// make sure we close before we update, bc they'll need locks
close();
if (notify) {
// fireNewEvents(newEvents);
}
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "Error commiting events.db.", ex); // NON-NLS
rollback();

View File

@ -16,13 +16,12 @@
* See the License for the specific language governing permissions and
* 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.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -34,6 +33,7 @@ import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javax.annotation.concurrent.GuardedBy;
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.coreutils.Logger;
import org.sleuthkit.autopsy.timeline.ProgressWindow;
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.events.type.ArtifactEventType;
import org.sleuthkit.autopsy.timeline.events.type.EventType;
import org.sleuthkit.autopsy.timeline.events.type.FileSystemTypes;
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.ArtifactEventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.FileSystemTypes;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
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.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.Tag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* Provides public API (over EventsDB) to access events. In theory this
* insulates the rest of the timeline module form the details of the db
* Provides higher-level public API (over EventsDB) to access events. In theory
* 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
* 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:
*
@ -95,12 +102,17 @@ public class EventsRepository {
private final ObservableMap<Long, String> datasourcesMap = FXCollections.observableHashMap();
private final ObservableMap<Long, String> hashSetMap = FXCollections.observableHashMap();
private final ObservableList<TagName> tagNames = FXCollections.observableArrayList();
private final Case autoCase;
public Case getAutoCase() {
return autoCase;
}
public ObservableList<TagName> getTagNames() {
return tagNames;
}
synchronized public ObservableMap<Long, String> getDatasourcesMap() {
return datasourcesMap;
}
@ -125,7 +137,7 @@ public class EventsRepository {
this.autoCase = autoCase;
//TODO: we should check that case is open, or get passed a case object/directory -jm
this.eventDB = EventDB.getEventDB(autoCase);
populateFilterMaps(autoCase.getSleuthkitCase());
populateFilterData(autoCase.getSleuthkitCase());
idToEventCache = CacheBuilder.newBuilder()
.maximumSize(5000L)
.expireAfterAccess(10, TimeUnit.MINUTES)
@ -218,6 +230,94 @@ public class EventsRepository {
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) {
if (dbPopulationWorker != null) {
dbPopulationWorker.cancel(true);
@ -227,8 +327,110 @@ public class EventsRepository {
dbPopulationWorker.execute();
}
public boolean hasDataSourceInfo() {
return eventDB.hasNewColumns();
synchronized public void rebuildTags(Runnable r) {
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> {
@ -256,7 +458,7 @@ public class EventsRepository {
"progressWindow.msg.reinit_db=(re)initializing events database",
"progressWindow.msg.commitingDb=committing events db"})
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
//TODO: can we do more incremental updates? -jm
eventDB.reInitializeDB();
@ -265,7 +467,7 @@ public class EventsRepository {
List<Long> files = skCase.findAllFileIdsWhere("name != '.' AND name != '..'");
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
int i = 1;
@ -290,25 +492,25 @@ public class EventsRepository {
String shortDesc = datasourceName + "/" + StringUtils.defaultIfBlank(rootFolder, "");
String medD = datasourceName + parentPath;
final TskData.FileKnown known = f.getKnown();
Set<String> hashSets = f.getHashSetNames() ;
boolean tagged = !tagsManager.getContentTagsByContent(f).isEmpty();
Set<String> hashSets = f.getHashSetNames();
List<ContentTag> tags = tagsManager.getContentTagsByContent(f);
//insert it into the db if time is > 0 => time is legitimate (drops logical files)
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) {
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) {
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) {
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,
Bundle.progressWindow_msg_populateMacEventsFiles(), f.getName())));
publish(new ProgressWindow.ProgressUpdate(i, numFiles,
Bundle.progressWindow_msg_populateMacEventsFiles(), f.getName()));
}
} catch (TskCoreException tskCoreException) {
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()) {
eventDB.rollBackTransaction(trans);
} else {
eventDB.commitTransaction(trans, true);
eventDB.commitTransaction(trans);
}
populateFilterMaps(skCase);
populateFilterData(skCase);
invalidateCaches();
return null;
@ -363,7 +566,7 @@ public class EventsRepository {
progressDialog.close();
get();
} 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) {
LOGGER.log(Level.WARNING, "Exception while populating database.", ex); // NON-NLS
JOptionPane.showMessageDialog(null, Bundle.msgdlg_problem_text());
@ -388,64 +591,32 @@ public class EventsRepository {
final ArrayList<BlackboardArtifact> blackboardArtifacts = skCase.getBlackboardArtifacts(type.getArtifactType());
final int numArtifacts = blackboardArtifacts.size();
process(Arrays.asList(new ProgressWindow.ProgressUpdate(0, numArtifacts,
Bundle.progressWindow_populatingXevents(type.toString()), "")));
for (int i = 0; i < numArtifacts; i++) {
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
BlackboardArtifact bbart = blackboardArtifacts.get(i);
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
long datasourceID = skCase.getContentById(bbart.getObjectID()).getDataSource().getId();
AbstractFile f = skCase.getAbstractFileById(bbart.getObjectID());
//insert it into the db if time is > 0 => time is legitimate
if (eventDescription != null && eventDescription.getTime() > 0L) {
long objectID = bbart.getObjectID();
AbstractFile f = skCase.getAbstractFileById(objectID);
long datasourceID = f.getDataSource().getId();
long artifactID = bbart.getArtifactID();
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) {
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;
}
}

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.timeline.events.db;
package org.sleuthkit.autopsy.timeline.db;
/**
*

View 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() {
}
}

View File

@ -18,22 +18,13 @@
*/
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
* corresponds to an event
* A "local" event published by filteredEventsModel to indicate that the user
* requested that the current visualization be refreshed with out changing any
* of the parameters ( to include more up to date tag data for example.)
* <p>
* This event is not intended for use out side of the timeline module.
*/
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);
}
}

View File

@ -18,23 +18,26 @@
*/
package org.sleuthkit.autopsy.timeline.events;
import java.util.Collections;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
/**
* Posted to eventbus when a tag as been removed from a file artifact that
* corresponds to an event
* A "local" event published by filteredEventsModel to indicate that events have
* 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;
}
}

View File

@ -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), ",") + "))";
}
}

View File

@ -36,7 +36,7 @@ import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.events.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;

View File

@ -27,9 +27,9 @@ import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.events.type.BaseTypes;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.BaseTypes;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;

View File

@ -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()) {
return false;
}

View File

@ -85,4 +85,5 @@ public class DataSourceFilter extends AbstractFilter {
return isSelected() == other.isSelected();
}
}

View File

@ -18,6 +18,7 @@
*/
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;
@ -38,7 +39,7 @@ public class DataSourcesFilter extends UnionFilter<DataSourceFilter> {
filterCopy.setSelected(isSelected());
//add a copy of each subfilter
this.getSubFilters().forEach((DataSourceFilter t) -> {
filterCopy.addDataSourceFilter(t.copyOf());
filterCopy.addSubFilter(t.copyOf());
});
return filterCopy;
@ -63,13 +64,14 @@ public class DataSourcesFilter extends UnionFilter<DataSourceFilter> {
return string;
}
public void addDataSourceFilter(DataSourceFilter dataSourceFilter) {
public void addSubFilter(DataSourceFilter dataSourceFilter) {
if (getSubFilters().stream().map(DataSourceFilter.class::cast)
.map(DataSourceFilter::getDataSourceID)
.filter(t -> t == dataSourceFilter.getDataSourceID())
.findAny().isPresent() == false) {
dataSourceFilter.getDisabledProperty().bind(getDisabledProperty());
getSubFilters().add(dataSourceFilter);
getSubFilters().sort(Comparator.comparing(DataSourceFilter::getDisplayName));
}
if (getSubFilters().size() > 1) {
setSelected(Boolean.TRUE);
@ -90,7 +92,7 @@ public class DataSourcesFilter extends UnionFilter<DataSourceFilter> {
return false;
}
return hashEqualSubFilters(this, other);
return areSubFiltersEqual(this, other);
}
@ -98,5 +100,4 @@ public class DataSourcesFilter extends UnionFilter<DataSourceFilter> {
public int hashCode() {
return 9;
}
}

View File

@ -5,6 +5,7 @@
*/
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;
@ -31,7 +32,7 @@ public class HashHitsFilter extends UnionFilter<HashSetFilter> {
filterCopy.setSelected(isSelected());
//add a copy of each subfilter
this.getSubFilters().forEach((HashSetFilter t) -> {
filterCopy.addHashSetFilter(t.copyOf());
filterCopy.addSubFilter(t.copyOf());
});
return filterCopy;
}
@ -68,15 +69,16 @@ public class HashHitsFilter extends UnionFilter<HashSetFilter> {
return false;
}
return hashEqualSubFilters(this, other);
return areSubFiltersEqual(this, other);
}
public void addHashSetFilter(HashSetFilter hashSetFilter) {
if (getSubFilters().stream().map(HashSetFilter.class::cast)
public void addSubFilter(HashSetFilter hashSetFilter) {
if (getSubFilters().stream()
.map(HashSetFilter::getHashSetID)
.filter(t -> t == hashSetFilter.getHashSetID())
.findAny().isPresent() == false) {
getSubFilters().add(hashSetFilter);
getSubFilters().sort(Comparator.comparing(HashSetFilter::getDisplayName));
}
}
}

View File

@ -21,7 +21,7 @@ package org.sleuthkit.autopsy.timeline.filters;
import java.util.Objects;
/**
* Filter for an individual datasource
* Filter for an individual hash set
*/
public class HashSetFilter extends AbstractFilter {

View File

@ -24,7 +24,7 @@ import javafx.collections.FXCollections;
import org.openide.util.NbBundle;
/**
* Intersection(And) filter
* Intersection (And) filter
*/
public class IntersectionFilter<S extends Filter> extends CompoundFilter<S> {
@ -60,7 +60,10 @@ public class IntersectionFilter<S extends Filter> extends CompoundFilter<S> {
@Override
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

View File

@ -26,7 +26,8 @@ import javafx.collections.FXCollections;
*/
public class RootFilter extends IntersectionFilter<Filter> {
private final HideKnownFilter knwonFilter;
private final HideKnownFilter knownFilter;
private final TagsFilter tagsFilter;
private final HashHitsFilter hashFilter;
private final TextFilter textFilter;
private final TypeFilter typeFilter;
@ -36,13 +37,20 @@ public class RootFilter extends IntersectionFilter<Filter> {
return dataSourcesFilter;
}
public TagsFilter getTagsFilter() {
return tagsFilter;
}
public HashHitsFilter getHashHitsFilter() {
return hashFilter;
}
public RootFilter(HideKnownFilter knownFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter) {
super(FXCollections.observableArrayList(knownFilter, hashFilter, textFilter, dataSourceFilter, typeFilter));
this.knwonFilter = knownFilter;
public RootFilter(HideKnownFilter knownFilter, TagsFilter tagsFilter, HashHitsFilter hashFilter, TextFilter textFilter, TypeFilter typeFilter, DataSourcesFilter dataSourceFilter) {
super(FXCollections.observableArrayList(knownFilter, tagsFilter, hashFilter, textFilter, dataSourceFilter, typeFilter));
setSelected(Boolean.TRUE);
setDisabled(false);
this.knownFilter = knownFilter;
this.tagsFilter = tagsFilter;
this.hashFilter = hashFilter;
this.textFilter = textFilter;
this.typeFilter = typeFilter;
@ -51,7 +59,7 @@ public class RootFilter extends IntersectionFilter<Filter> {
@Override
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.setDisabled(isDisabled());
return filter;
@ -71,6 +79,6 @@ public class RootFilter extends IntersectionFilter<Filter> {
if (getClass() != obj.getClass()) {
return false;
}
return hashEqualSubFilters(this, (CompoundFilter<Filter>) obj);
return areSubFiltersEqual(this, (CompoundFilter<Filter>) obj);
}
}

View File

@ -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();
}
}

View File

@ -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));
}
}

View File

@ -91,5 +91,4 @@ public class TextFilter extends AbstractFilter {
hash = 29 * hash + Objects.hashCode(this.text.get());
return hash;
}
}

View File

@ -24,8 +24,8 @@ import javafx.collections.FXCollections;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.events.type.EventType;
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
/**
* 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) {
return false;
}
return hashEqualSubFilters(this, other);
return areSubFiltersEqual(this, other);
}
@Override
@ -142,5 +142,4 @@ public class TypeFilter extends UnionFilter<TypeFilter> {
hash = 67 * hash + Objects.hashCode(this.eventType);
return hash;
}
}

View File

@ -33,5 +33,4 @@ abstract public class UnionFilter<SubFilterType extends Filter> extends Compound
public UnionFilter() {
super(FXCollections.<SubFilterType>observableArrayList());
}
}

View File

@ -18,9 +18,11 @@
*/
package org.sleuthkit.autopsy.timeline.ui;
import com.google.common.eventbus.Subscribe;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyListProperty;
@ -48,12 +50,14 @@ import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
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.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
@ -169,8 +173,8 @@ public abstract class AbstractVisualization<X, Y, N extends Node, C extends XYCh
protected abstract Axis<Y> getYAxis();
/**
* * update this visualization based on current state of zoom /
* filters.Primarily this invokes the background {@link Task} returned by
* update this visualization based on current state of zoom /
* filters. Primarily this invokes the background {@link Task} returned by
* {@link #getUpdateTask()} which derived classes must implement.
*/
synchronized public void update() {
@ -191,7 +195,7 @@ public abstract class AbstractVisualization<X, Y, N extends Node, C extends XYCh
try {
this.hasEvents.set(updateTask.get());
} catch (InterruptedException | ExecutionException ex) {
Exceptions.printStackTrace(ex);
Logger.getLogger(AbstractVisualization.class.getName()).log(Level.SEVERE, "Unexpected exception updating visualization", ex);
}
break;
}
@ -203,7 +207,7 @@ public abstract class AbstractVisualization<X, Y, N extends Node, C extends XYCh
if (updateTask != null) {
updateTask.cancel(true);
}
this.filteredEvents.getRequestedZoomParamters().removeListener(invalidationListener);
this.filteredEvents.zoomParametersProperty().removeListener(invalidationListener);
invalidationListener = null;
}
@ -236,10 +240,23 @@ public abstract class AbstractVisualization<X, Y, N extends Node, C extends XYCh
}
@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.getRequestedZoomParamters().addListener(invalidationListener);
update();
}
@Subscribe
public void handleRefreshRequested(RefreshRequestedEvent event) {
update();
}

View File

@ -6,15 +6,28 @@
<?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">
<items><Label fx:id="refreshLabel">
<graphic><ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/info-icon-16.png" />
</image></ImageView>
</graphic></Label><Separator orientation="VERTICAL" /><Region fx:id="spacer" /><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>
<items>
<Label fx:id="refreshLabel">
<graphic>
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/info-icon-16.png" />
</image>
</ImageView>
</graphic>
</Label>
<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>

View File

@ -27,7 +27,7 @@ import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
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.corecomponentinterfaces.DataContent;
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.timeline.ui;
import com.google.common.eventbus.Subscribe;
import java.net.URL;
import java.time.Instant;
import java.time.LocalDateTime;
@ -45,6 +46,8 @@ import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar;
import javafx.scene.control.Tooltip;
import javafx.scene.effect.Lighting;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
@ -60,6 +63,7 @@ import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javax.annotation.concurrent.GuardedBy;
import jfxtras.scene.control.LocalDateTimeTextField;
import org.controlsfx.control.NotificationPane;
import org.controlsfx.control.RangeSlider;
import org.controlsfx.control.action.Action;
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.SaveSnapshot;
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.detailview.DetailViewPane;
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 {
@GuardedBy("this")
private LoggedTask<Void> histogramTask;
private static final Image INFORMATION = new Image("org/sleuthkit/autopsy/timeline/images/information.png", 16, 16, true, true); // NON-NLS
private static final Image REFRESH = new Image("org/sleuthkit/autopsy/timeline/images/arrow-circle-double-135.png"); // NON-NLS
private static final Logger LOGGER = Logger.getLogger(VisualizationPanel.class.getName());
@GuardedBy("this")
private LoggedTask<Void> histogramTask;
private final NavPanel navPanel;
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
@FXML // fx:id="histogramBox"
protected HBox histogramBox; // Value injected by FXMLLoader
@FXML
protected HBox histogramBox;
@FXML // fx:id="rangeHistogramStack"
protected StackPane rangeHistogramStack; // Value injected by FXMLLoader
@FXML
protected StackPane rangeHistogramStack;
private final RangeSlider rangeSlider = new RangeSlider(0, 1.0, .25, .75);
@ -167,7 +172,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
@FXML
private Label endLabel;
private double preDragPos;
private final NotificationPane notificationPane = new NotificationPane();
protected TimeLineController controller;
@ -186,14 +191,14 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
private final InvalidationListener endListener = (Observable observable) -> {
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()));
}
};
private final InvalidationListener startListener = (Observable observable) -> {
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()));
}
};
@ -208,6 +213,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
}
@FXML // This method is called by the FXMLLoader when initialization is complete
@NbBundle.Messages("VisualizationPanel.refresh=refresh")
protected void initialize() {
assert endPicker != null : "fx:id=\"endPicker\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
assert histogramBox != null : "fx:id=\"histogramBox\" was not injected: check your FXML file 'ViewWrapper.fxml'."; // NON-NLS
@ -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 detailsToggle != null : "fx:id=\"eventsToggle\" was not injected: check your FXML file 'VisToggle.fxml'."; // NON-NLS
visualizationModeLabel.setText(
NbBundle.getMessage(this.getClass(), "VisualizationPanel.visualizationModeLabel.text"));
startLabel.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.startLabel.text"));
endLabel.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.endLabel.text"));
notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
notificationPane.getActions().setAll(new Action(VisualizationPanel_refresh()) {
{
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(rightSeperator, Priority.ALWAYS);
@ -242,26 +258,12 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
countsToggle.getToggleGroup().selectedToggleProperty().addListener(toggleListener);
});
}
countsToggle.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.countsToggle.text"));
detailsToggle.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.detailsToggle.text"));
countsToggle.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.countsToggle.text")); // NON-NLS
detailsToggle.setText(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.detailsToggle.text")); // NON-NLS
//setup rangeslider
rangeSlider.setOpacity(.7);
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);
rangeHistogramStack.getChildren().add(rangeSlider);
@ -275,7 +277,6 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
zoomMenuButton.getItems().clear();
for (ZoomRanges b : ZoomRanges.values()) {
MenuItem menuItem = new MenuItem(b.getDisplayName());
menuItem.setOnAction((event) -> {
if (b != ZoomRanges.ALL) {
@ -286,7 +287,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
});
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 -> {
controller.pushZoomOutTime();
@ -307,69 +308,14 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
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
public synchronized void setController(TimeLineController controller) {
this.controller = controller;
setModel(controller.getEventsModel());
setViewMode(controller.getViewMode().get());
controller.getNeedsHistogramRebuild().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
if (newValue) {
refreshHistorgram();
@ -379,9 +325,29 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
controller.getViewMode().addListener((ObservableValue<? extends VisualizationMode> ov, VisualizationMode t, VisualizationMode t1) -> {
setViewMode(t1);
});
TimeLineController.getTimeZone().addListener(timeRangeInvalidationListener);
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) {
switch (visualizationMode) {
case COUNTS:
@ -407,29 +373,40 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
toolBar.getItems().addAll(newViz.getSettingsNodes());
visualization.setController(controller);
setCenter(visualization);
notificationPane.setContent(visualization);
if (visualization instanceof DetailViewPane) {
navPanel.setChart((DetailViewPane) visualization);
}
visualization.hasEvents.addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
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)));
setOpacity(.3);
}
}, new NoEventsDialog(() -> {
setCenter(visualization);
notificationPane.setContent(visualization);
})));
} 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() {
if (histogramTask != null) {
@ -437,12 +414,12 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
}
histogramTask = new LoggedTask<Void>(
NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.title"), true) {
NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.title"), true) { // NON-NLS
@Override
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;
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
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;
updateMessage(NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.queryDb"));
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.queryDb")); // NON-NLS
//query for current range
long count = filteredEvents.getEventCounts(interval).values().stream().mapToLong(Long::valueOf).sum();
bins.add(count);
@ -482,7 +459,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
final double fMax = Math.log(max);
final ArrayList<Long> fbins = new ArrayList<>(bins);
Platform.runLater(() -> {
updateMessage(NbBundle.getMessage(this.getClass(), "VisualizationPanel.histogramTask.updateUI2"));
updateMessage(NbBundle.getMessage(VisualizationPanel.class, "VisualizationPanel.histogramTask.updateUI2")); // NON-NLS
histogramBox.getChildren().clear();
@ -514,18 +491,13 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
controller.monitorTask(histogramTask);
}
@Override
public void setModel(FilteredEventsModel filteredEvents) {
this.filteredEvents = filteredEvents;
private InvalidationListener timeRangeInvalidationListener = (Observable observable) -> {
refreshTimeUI(filteredEvents.timeRangeProperty().get());
};
refreshTimeUI(filteredEvents.timeRange().get());
this.filteredEvents.timeRange().addListener((Observable observable) -> {
refreshTimeUI(filteredEvents.timeRange().get());
});
TimeLineController.getTimeZone().addListener((Observable observable) -> {
refreshTimeUI(filteredEvents.timeRange().get());
});
}
private InvalidationListener zoomListener = (Observable observable) -> {
notificationPane.hide();
};
private void refreshTimeUI(Interval interval) {
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 zoomButton != null : "fx:id=\"zoomButton\" was not injected: check your FXML file 'NoEventsDialog.fxml'."; // NON-NLS
noEventsDialogLabel.setText(
NbBundle.getMessage(this.getClass(), "VisualizationPanel.noEventsDialogLabel.text"));
zoomButton.setText(NbBundle.getMessage(this.getClass(), "VisualizationPanel.zoomButton.text"));
noEventsDialogLabel.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.noEventsDialogLabel.text")); // NON-NLS
zoomButton.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.zoomButton.text")); // NON-NLS
Action zoomOutAction = new ZoomOut(controller);
zoomButton.setOnAction(zoomOutAction);
@ -603,8 +574,7 @@ public class VisualizationPanel extends BorderPane implements TimeLineView {
Action defaultFiltersAction = new ResetFilters(controller);
resetFiltersButton.setOnAction(defaultFiltersAction);
resetFiltersButton.disableProperty().bind(defaultFiltersAction.disabledProperty());
resetFiltersButton.setText(
NbBundle.getMessage(this.getClass(), "VisualizationPanel.resetFiltersButton.text"));
resetFiltersButton.setText(NbBundle.getMessage(NoEventsDialog.class, "VisualizationPanel.resetFiltersButton.text")); // NON-NLS
}
}
}

View File

@ -38,7 +38,13 @@ import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedBarChart;
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.Effect;
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.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.type.EventType;
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
import org.sleuthkit.autopsy.timeline.utils.RangeDivisionInfo;
@ -141,7 +147,7 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
setCursor(Cursor.WAIT);
});
final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.timeRange().get());
final RangeDivisionInfo rangeInfo = RangeDivisionInfo.getRangeDivisionInfo(filteredEvents.timeRangeProperty().get());
chart.setRangeInfo(rangeInfo);
//extend range to block bounderies (ie day, month, year)
final long lowerBound = rangeInfo.getLowerBound();
@ -304,6 +310,9 @@ public class CountsViewPane extends AbstractVisualization<String, Number, Node,
});
}
@Override
protected NumberAxis getYAxis() {
return countAxis;

View File

@ -36,7 +36,7 @@ import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.actions.Back;
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.utils.RangeDivisionInfo;
@ -127,7 +127,7 @@ class EventCountsChart extends StackedBarChart<String, Number> implements TimeLi
@Override
public void setModel(FilteredEventsModel filteredEvents) {
filteredEvents.getRequestedZoomParamters().addListener(o -> {
filteredEvents.zoomParametersProperty().addListener(o -> {
clearIntervalSelector();
controller.selectEventIDs(Collections.emptyList());
});

View File

@ -18,7 +18,6 @@
*/
package org.sleuthkit.autopsy.timeline.ui.detailview;
import com.google.common.eventbus.Subscribe;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -63,21 +62,16 @@ import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ColorUtilities;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
import org.sleuthkit.autopsy.timeline.events.EventsTaggedEvent;
import org.sleuthkit.autopsy.timeline.events.EventsUnTaggedEvent;
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.TimeLineEvent;
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.TimeLineEvent;
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.zooming.DescriptionLOD;
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.TskCoreException;
@ -302,27 +296,8 @@ public class AggregateEventNode extends StackPane {
Map<String, Long> tagCounts = new HashMap<>();
if (!aggEvent.getEventIDsWithTags().isEmpty()) {
try {
for (TimeLineEvent tle : eventsModel.getEventsById(aggEvent.getEventIDsWithTags())) {
tagCounts.putAll( eventsModel.getTagCountsByTagName(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()
@ -375,6 +350,7 @@ public class AggregateEventNode extends StackPane {
/**
* @param descrVis the level of description that should be displayed
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
synchronized final void setDescriptionVisibility(DescriptionVisibility descrVis) {
this.descrVis = descrVis;
final int size = aggEvent.getEventIDs().size();
@ -469,7 +445,7 @@ public class AggregateEventNode extends StackPane {
chart.setRequiresLayout(true);
chart.requestChartLayout();
} 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
combinedFilter.getSubFilters().addAll(new TextFilter(aggEvent.getDescription()),
new TypeFilter(aggEvent.getType()));
@ -485,7 +461,7 @@ public class AggregateEventNode extends StackPane {
protected List<AggregateEventNode> call() throws Exception {
//query for the sub-clusters
List<AggregateEvent> aggregatedEvents = eventsModel.getAggregatedEvents(new ZoomParams(span,
eventsModel.eventTypeZoom().get(),
eventsModel.eventTypeZoomProperty().get(),
combinedFilter,
newDescriptionLOD));
//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);
});
}
}
}

View File

@ -67,11 +67,12 @@ import org.joda.time.DateTime;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.type.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.ui.AbstractVisualization;
import org.sleuthkit.autopsy.timeline.ui.countsview.CountsViewPane;
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) {
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
* EventType
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private XYChart.Series<DateTime, AggregateEvent> getSeries(final EventType et) {
XYChart.Series<DateTime, AggregateEvent> series = eventTypeToSeriesMap.get(et);
if (series == null) {
@ -302,7 +309,7 @@ public class DetailViewPane extends AbstractVisualization<DateTime, AggregateEve
updateProgress(-1, 1);
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 upperBound = rangeInfo.getUpperBound();
@ -315,9 +322,6 @@ public class DetailViewPane extends AbstractVisualization<DateTime, AggregateEve
}
dateAxis.setLowerBound(new DateTime(lowerBound, TimeLineController.getJodaTimeZone()));
dateAxis.setUpperBound(new DateTime(upperBound, TimeLineController.getJodaTimeZone()));
// if (chart == null) {
// initializeClusterChart();
// }
vertScrollBar.setValue(0);
eventTypeToSeriesMap.clear();
dataSets.clear();
@ -478,4 +482,5 @@ public class DetailViewPane extends AbstractVisualization<DateTime, AggregateEve
}
}
}

View File

@ -22,7 +22,7 @@ import java.util.Collections;
import java.util.List;
import javafx.scene.chart.Axis;
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

View File

@ -19,7 +19,6 @@
package org.sleuthkit.autopsy.timeline.ui.detailview;
import com.google.common.collect.Collections2;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -72,11 +71,9 @@ import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.actions.Back;
import org.sleuthkit.autopsy.timeline.actions.Forward;
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
import org.sleuthkit.autopsy.timeline.events.EventsTaggedEvent;
import org.sleuthkit.autopsy.timeline.events.EventsUnTaggedEvent;
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.type.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.ui.TimeLineChart;
/**
@ -344,12 +341,9 @@ public final class EventDetailChart extends XYChart<DateTime, AggregateEvent> im
@Override
public void setModel(FilteredEventsModel filteredEvents) {
if (this.filteredEvents != null) {
this.filteredEvents.unRegisterForEvents(this);
}
if (this.filteredEvents != filteredEvents) {
filteredEvents.registerForEvents(this);
filteredEvents.getRequestedZoomParamters().addListener(o -> {
filteredEvents.zoomParametersProperty().addListener(o -> {
clearGuideLine();
clearIntervalSelector();
@ -527,7 +521,7 @@ public final class EventDetailChart extends XYChart<DateTime, AggregateEvent> im
return nodes;
}
private Iterable<AggregateEventNode> getAllNodes() {
Iterable<AggregateEventNode> getAllNodes() {
return getNodes(x -> true);
}
@ -736,18 +730,4 @@ public final class EventDetailChart extends XYChart<DateTime, AggregateEvent> im
protected void 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);
}
}
}

View File

@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
import java.util.Comparator;
import javafx.scene.control.TreeItem;
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
/**
*

View File

@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.control.TreeItem;
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
class EventTypeTreeItem extends NavTreeItem {

View File

@ -26,7 +26,13 @@ import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.collections.ObservableList;
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.layout.BorderPane;
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.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.ui.detailview.AggregateEventNode;
import org.sleuthkit.autopsy.timeline.ui.detailview.DetailViewPane;

View File

@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
import java.util.Comparator;
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

View File

@ -19,7 +19,7 @@
package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
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

View File

@ -24,8 +24,8 @@ import java.util.Map;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.control.TreeItem;
import org.sleuthkit.autopsy.timeline.events.type.EventType;
import org.sleuthkit.autopsy.timeline.events.AggregateEvent;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.EventType;
import org.sleuthkit.autopsy.timeline.datamodel.AggregateEvent;
/**
*

View File

@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.timeline.ui.detailview.tree;
import java.util.Comparator;
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>> {

View File

@ -38,8 +38,8 @@ import org.sleuthkit.autopsy.timeline.FXMLConstructor;
import org.sleuthkit.autopsy.timeline.TimeLineController;
import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.actions.ResetFilters;
import org.sleuthkit.autopsy.timeline.events.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.events.type.RootEventType;
import org.sleuthkit.autopsy.timeline.datamodel.FilteredEventsModel;
import org.sleuthkit.autopsy.timeline.datamodel.eventtype.RootEventType;
import org.sleuthkit.autopsy.timeline.filters.AbstractFilter;
import org.sleuthkit.autopsy.timeline.filters.Filter;
import org.sleuthkit.autopsy.timeline.filters.RootFilter;
@ -174,14 +174,14 @@ public class FilterSetPanel extends BorderPane implements TimeLineView {
public void setModel(FilteredEventsModel filteredEvents) {
this.filteredEvents = filteredEvents;
refresh();
this.filteredEvents.filter().addListener((Observable o) -> {
this.filteredEvents.filterProperty().addListener((Observable o) -> {
refresh();
});
}
private void refresh() {
Platform.runLater(() -> {
filterTreeTable.setRoot(new FilterTreeItem(filteredEvents.filter().get().copyOf(), expansionMap));
filterTreeTable.setRoot(new FilterTreeItem(filteredEvents.filterProperty().get().copyOf(), expansionMap));
});
}
}

View File

@ -30,7 +30,7 @@ import javafx.scene.shape.Rectangle;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.timeline.TimeLineController;
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.TextFilter;
import org.sleuthkit.autopsy.timeline.filters.TypeFilter;
@ -74,7 +74,7 @@ class LegendCell extends TreeTableCell<AbstractFilter, AbstractFilter> implement
rect.setArcWidth(5);
rect.setStrokeWidth(3);
setLegendColor(filter, rect, this.filteredEvents.getEventTypeZoom());
this.filteredEvents.eventTypeZoom().addListener((obs, oldZoomLevel, newZoomLevel) -> {
this.filteredEvents.eventTypeZoomProperty().addListener((obs, oldZoomLevel, newZoomLevel) -> {
setLegendColor(filter, rect, newZoomLevel);
});

View File

@ -50,7 +50,7 @@ public class ZoomParams {
return filter;
}
public DescriptionLOD getDescrLOD() {
public DescriptionLOD getDescriptionLOD() {
return descrLOD;
}

View File

@ -34,7 +34,7 @@ import org.sleuthkit.autopsy.timeline.TimeLineView;
import org.sleuthkit.autopsy.timeline.VisualizationMode;
import org.sleuthkit.autopsy.timeline.actions.Back;
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.RangeDivisionInfo;
@ -141,12 +141,12 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
if (requestedUnit == TimeUnits.FOREVER) {
controller.showFullRange();
} 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();
timeUnitSlider.setValue(TimeUnits.fromChronoUnit(chronoUnit).ordinal() - 1);
@ -158,9 +158,9 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
if (controller.pushDescrLOD(newLOD) == false) {
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,
@ -168,9 +168,9 @@ public class ZoomSettingsPane extends TitledPane implements TimeLineView {
EventTypeZoomLevel newZoomLevel = EventTypeZoomLevel.values()[Math.round(typeZoomSlider.valueProperty().floatValue())];
controller.pushEventTypeZoom(newZoomLevel);
},
this.filteredEvents.eventTypeZoom(),
this.filteredEvents.eventTypeZoomProperty(),
() -> {
typeZoomSlider.setValue(this.filteredEvents.eventTypeZoom().get().ordinal());
typeZoomSlider.setValue(this.filteredEvents.eventTypeZoomProperty().get().ordinal());
});
}

View File

@ -77,5 +77,7 @@ file.reference.xmlbeans-2.3.0.jar=release/modules/ext/xmlbeans-2.3.0.jar
javac.source=1.7
javac.compilerargs=-Xlint -Xlint:-serial
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
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

View File

@ -685,10 +685,6 @@
<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>
</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/gson-1.4.jar</runtime-relative-path>
<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>
<binary-origin>release/modules/ext/jsr305-1.3.9.jar</binary-origin>
</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>
<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>
@ -713,10 +713,6 @@
<runtime-relative-path>ext/guava-11.0.2.jar</runtime-relative-path>
<binary-origin>release/modules/ext/guava-11.0.2.jar</binary-origin>
</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>
<runtime-relative-path>ext/mail-1.4.3.jar</runtime-relative-path>
<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>
<binary-origin>release/modules/ext/common-lang-3.1.1.jar</binary-origin>
</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>
<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>
@ -746,8 +746,8 @@
<binary-origin>release/modules/ext/joda-time-2.4-javadoc.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>
<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>
<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>
<binary-origin>release/modules/ext/servlet-api-2.5.jar</binary-origin>
</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>
<runtime-relative-path>ext/poi-excelant-3.8.jar</runtime-relative-path>
<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>
<binary-origin>release/modules/ext/avalon-framework-4.1.5.jar</binary-origin>
</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/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>
@ -801,30 +813,14 @@
<runtime-relative-path>ext/joda-time-2.4.jar</runtime-relative-path>
<binary-origin>release/modules/ext/joda-time-2.4.jar</binary-origin>
</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>
<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>
</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>
<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>
</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>
<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>
@ -861,6 +857,10 @@
<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>
</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>
<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>
@ -893,10 +893,6 @@
<runtime-relative-path>ext/commons-lang3-3.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/commons-lang3-3.0.jar</binary-origin>
</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>
<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>
@ -918,8 +914,8 @@
<binary-origin>release/modules/ext/gstreamer-java-1.5.jar</binary-origin>
</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>
<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>
<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>
<binary-origin>release/modules/ext/imgscalr-lib-4.2-sources.jar</binary-origin>
</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>
</configuration>
</project>

View File

@ -4,7 +4,7 @@
<configuration>
<data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
<code-name-base>org.sleuthkit.autopsy.imagegallery</code-name-base>
<standalone/>
<suite-component/>
<module-dependencies>
<dependency>
<code-name-base>org.netbeans.api.progress</code-name-base>

View File

@ -0,0 +1 @@
suite.dir=${basedir}/..

View File

@ -248,6 +248,7 @@ public final class ImageGalleryController {
return historyManager.getCanRetreat();
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
public void advance(GroupViewState newState, boolean forceShowTree) {
if (Objects.nonNull(navPanel) && forceShowTree) {
navPanel.showTree();
@ -414,7 +415,7 @@ public final class ImageGalleryController {
dbWorkerThread.addTask(innerTask);
}
public DrawableFile<?> getFileFromId(Long fileID) throws TskCoreException {
synchronized public DrawableFile<?> getFileFromId(Long fileID) throws TskCoreException {
return db.getFileFromID(fileID);
}
@ -776,7 +777,8 @@ public final class ImageGalleryController {
+ " AND (blackboard_attributes.value_text LIKE 'video/%'"
+ " OR blackboard_attributes.value_text LIKE 'image/%'"
+ " OR " + MIMETYPE_CLAUSE
+ " )";
+ " )"
+ ")";
private ProgressHandle progressHandle = ProgressHandleFactory.createHandle("populating analyzed image/video database");

View File

@ -38,7 +38,6 @@ import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javax.annotation.Nullable;
import javax.imageio.ImageIO;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.coreutils.ImageUtils;
import org.sleuthkit.autopsy.coreutils.Logger;
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.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.
*
* 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());
/** 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.
*/
private final Cache<Long, Optional<Image>> cache = CacheBuilder.newBuilder()
@ -72,7 +73,9 @@ public enum ThumbnailCache {
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);
/**
@ -82,7 +85,8 @@ public enum ThumbnailCache {
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
*
* @param file
@ -132,16 +136,15 @@ public enum ThumbnailCache {
if (cachFile.exists()) {
// If a thumbnail file is already saved locally, load it
try {
BufferedImage read = ImageIO.read(cachFile);
if (read.getWidth() < MAX_THUMBNAIL_SIZE) {
return read;
BufferedImage cachedThumbnail = ImageIO.read(cachFile);
if (cachedThumbnail.getWidth() < MAX_THUMBNAIL_SIZE) {
return cachedThumbnail;
}
} 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) {
Exceptions.printStackTrace(ex);
LOGGER.log(Level.WARNING, "Unable to read cache file " + cachFile.getPath(), ex);
}
}
return null;
@ -150,10 +153,6 @@ public enum ThumbnailCache {
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;
if (thumbnail == ImageUtils.getDefaultThumbnail()) {
// if we go the default icon, ignore it

View File

@ -62,6 +62,7 @@ public class NextUnseenGroup extends Action {
});
setEventHandler((ActionEvent t) -> {
//if there is a group assigned to the view, mark it as seen
Optional.ofNullable(controller.viewState())
.map(ObjectExpression<GroupViewState>::getValue)
.map(GroupViewState::getGroup)

View File

@ -15,10 +15,14 @@ import org.sleuthkit.datamodel.TskCoreException;
*/
public class HashSetManager {
/** The db that initial values are loaded from. */
/**
* The db that initial values are loaded from.
*/
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));
/**

View File

@ -27,6 +27,8 @@ import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.scene.control.TreeItem;
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;
/**
@ -37,11 +39,11 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
*/
class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeItem> {
@ThreadConfined(type = ThreadType.JFX)
static GroupTreeItem getTreeItemForGroup(GroupTreeItem root, DrawableGroup grouping) {
if (Objects.equals(root.getValue().getGroup(), grouping)) {
return root;
} else {
synchronized (root.getChildren()) {
for (TreeItem<TreeNode> child : root.getChildren()) {
final GroupTreeItem childGTI = (GroupTreeItem) child;
@ -51,7 +53,6 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
}
}
}
}
return null;
}
@ -107,11 +108,8 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
prefixTreeItem = newTreeItem;
childMap.put(prefix, prefixTreeItem);
Platform.runLater(() -> {
synchronized (getChildren()) {
getChildren().add(newTreeItem);
}
});
}
// recursively go into the path
@ -125,14 +123,11 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
childMap.put(path, newTreeItem);
Platform.runLater(() -> {
synchronized (getChildren()) {
getChildren().add(newTreeItem);
if (comp != null) {
FXCollections.sort(getChildren(), comp);
}
}
});
}
}
}
@ -160,11 +155,8 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
childMap.put(prefix, prefixTreeItem);
Platform.runLater(() -> {
synchronized (getChildren()) {
getChildren().add(newTreeItem);
}
});
}
// recursively go into the path
@ -179,12 +171,10 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
childMap.put(path.get(0), newTreeItem);
Platform.runLater(() -> {
synchronized (getChildren()) {
getChildren().add(newTreeItem);
if (comp != null) {
FXCollections.sort(getChildren(), comp);
}
}
});
}
}
@ -195,12 +185,13 @@ class GroupTreeItem extends TreeItem<TreeNode> implements Comparable<GroupTreeIt
return comp.compare(this, o);
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
GroupTreeItem getTreeItemForPath(List<String> path) {
// end of recursion
if (path.isEmpty()) {
// end of recursion
return this;
} else {
synchronized (getChildren()) {
String prefix = path.get(0);
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()));
}
}
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
void removeFromParent() {
final GroupTreeItem parent = (GroupTreeItem) getParent();
if (parent != null) {
parent.childMap.remove(getValue().getPath());
Platform.runLater(() -> {
synchronized (parent.getChildren()) {
parent.getChildren().removeAll(Collections.singleton(GroupTreeItem.this));
}
});
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) {
this.comp = newComp;
synchronized (getChildren()) {
FXCollections.sort(getChildren(), comp);
}
for (GroupTreeItem ti : childMap.values()) {
ti.resortChildren(comp);
}
}
}

View File

@ -55,15 +55,15 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
*/
public class NavPanel extends TabPane {
@FXML
private ComboBox<TreeNodeComparators> sortByBox;
@FXML
private TabPane navTabPane;
/**
* TreeView for folders with hash hits
*/
@FXML
private TreeView<TreeNode> hashTree;
@FXML
private TabPane navTabPane;
/**
* TreeView for all folders
*/
@ -76,17 +76,14 @@ public class NavPanel extends TabPane {
@FXML
private Tab navTab;
@FXML
private ComboBox<TreeNodeComparators> sortByBox;
/**
* contains the 'active tree'
*/
private final SimpleObjectProperty<TreeView<TreeNode>> activeTreeProperty = new SimpleObjectProperty<>();
private GroupTreeItem hashTreeRoot;
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;
@ -118,6 +115,7 @@ public class NavPanel extends TabPane {
sortByBox.setItems(FXCollections.observableArrayList(FXCollections.observableArrayList(TreeNodeComparators.values())));
sortByBox.getSelectionModel().select(TreeNodeComparators.HIT_COUNT);
sortByBox.getSelectionModel().selectedItemProperty().addListener((Observable o) -> {
//user action ->jfx thread
resortHashTree();
});
@ -143,14 +141,8 @@ public class NavPanel extends TabPane {
});
controller.getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
TreeItem<TreeNode> selectedItem = activeTreeProperty.get().getSelectionModel().getSelectedItem();
boolean wasPermuted = false;
//analyzed groups shoud only be modified on jfx thread
while (change.next()) {
if (change.wasPermutated()) {
// Handle this afterward
wasPermuted = true;
break;
}
for (DrawableGroup g : change.getAddedSubList()) {
insertIntoNavTree(g);
if (g.getHashSetHitsCount() > 0) {
@ -162,15 +154,6 @@ public class NavPanel extends TabPane {
removeFromHashTree(g);
}
}
if (wasPermuted) {
rebuildTrees();
}
if (selectedItem != null && selectedItem.getValue().getGroup() != null) {
Platform.runLater(() -> {
setFocusedGroup(selectedItem.getValue().getGroup());
});
}
});
rebuildTrees();
@ -208,6 +191,7 @@ public class NavPanel extends TabPane {
}
}
@ThreadConfined(type = ThreadType.JFX)
private void resortHashTree() {
hashTreeRoot.resortChildren(sortByBox.getSelectionModel().getSelectedItem());
}
@ -262,16 +246,19 @@ public class NavPanel extends TabPane {
}
}
@ThreadConfined(type = ThreadType.JFX)
private void insertIntoHashTree(DrawableGroup g) {
initHashTree();
hashTreeRoot.insert(groupingToPath(g), g, false);
}
@ThreadConfined(type = ThreadType.JFX)
private void insertIntoNavTree(DrawableGroup g) {
initNavTree();
navTreeRoot.insert(groupingToPath(g), g, true);
}
@ThreadConfined(type = ThreadType.JFX)
private void removeFromNavTree(DrawableGroup g) {
initNavTree();
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) {
initHashTree();
final GroupTreeItem treeItemForGroup = GroupTreeItem.getTreeItemForGroup(hashTreeRoot, g);
@ -310,6 +298,7 @@ public class NavPanel extends TabPane {
}
}
@ThreadConfined(type = ThreadType.JFX)
public void showTree() {
getSelectionModel().select(navTab);
}

View File

@ -27,11 +27,13 @@ modules=\
${project.org.sleuthkit.autopsy.testing}:\
${project.org.sleuthkit.autopsy.thunderbirdparser}:\
${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.corelibs=CoreLibs
project.org.sleuthkit.autopsy.keywordsearch=KeywordSearch
project.org.sleuthkit.autopsy.recentactivity=RecentActivity
project.org.sleuthkit.autopsy.testing=Testing
project.org.sleuthkit.autopsy.thunderbirdparser=thunderbirdparser
project.org.sleuthkit.autopsy.imagegallery=ImageGallery