From faf592a2bb3cb08e0e72c4824e2171e92b0156e1 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 6 Aug 2014 17:59:19 -0400 Subject: [PATCH 1/5] Add file times and size properties to ContentTagNode property sheet --- .../autopsy/datamodel/Bundle.properties | 10 +++++ .../autopsy/datamodel/ContentTagNode.java | 45 ++++++++++++++----- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index 378c308335..0966a935b2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -58,6 +58,16 @@ ContentTagNode.createSheet.filePath.name=File Path ContentTagNode.createSheet.filePath.displayName=File Path ContentTagNode.createSheet.comment.name=Comment ContentTagNode.createSheet.comment.displayName=Comment +ContentTagNode.createSheet.fileModifiedTime.name=Modified Time +ContentTagNode.createSheet.fileModifiedTime.displayName=Modified Time +ContentTagNode.createSheet.fileChangedTime.name=Changed Time +ContentTagNode.createSheet.fileChangedTime.displayName=Changed Time +ContentTagNode.createSheet.fileAccessedTime.name=Accessed Time +ContentTagNode.createSheet.fileAccessedTime.displayName=Accessed Time +ContentTagNode.createSheet.fileCreatedTime.name=Created Time +ContentTagNode.createSheet.fileCreatedTime.displayName=Created Time +ContentTagNode.createSheet.filesize.name=Size +ContentTagNode.createSheet.filesize.displayName=Size ContentTagTypeNode.displayName.text=File Tags ContentTagTypeNode.createSheet.name.name=Name ContentTagTypeNode.createSheet.name.displayName=Name diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java index 31343f6d81..30bfb573f4 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -20,13 +20,15 @@ package org.sleuthkit.autopsy.datamodel; import java.util.List; import java.util.logging.Level; -import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.Action; import org.openide.nodes.Children; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.actions.DeleteContentTagAction; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TskCoreException; @@ -51,24 +53,26 @@ class ContentTagNode extends DisplayableItemNode { @Override protected Sheet createSheet() { + Content content = tag.getContent(); + String contentPath; + try { + contentPath = content.getUniquePath(); + } catch (TskCoreException ex) { + Logger.getLogger(ContentTagNode.class.getName()).log(Level.SEVERE, "Failed to get path for content (id = " + content.getId() + ")", ex); //NON-NLS + contentPath = NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.unavail.path"); + } + AbstractFile file = content instanceof AbstractFile ? (AbstractFile)content : null; + Sheet propertySheet = super.createSheet(); Sheet.Set properties = propertySheet.get(Sheet.PROPERTIES); if (properties == null) { properties = Sheet.createPropertiesSet(); propertySheet.put(properties); } - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.file.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.file.displayName"), "", - tag.getContent().getName())); - String contentPath; - try { - contentPath = tag.getContent().getUniquePath(); - } catch (TskCoreException ex) { - Logger.getLogger(ContentTagNode.class.getName()).log(Level.SEVERE, "Failed to get path for content (id = " + tag.getContent().getId() + ")", ex); //NON-NLS - contentPath = NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.unavail.path"); - } + content.getName())); properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filePath.name"), NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filePath.displayName"), "", @@ -77,7 +81,26 @@ class ContentTagNode extends DisplayableItemNode { NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.comment.displayName"), "", tag.getComment())); - + properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.displayName"), + "", + file != null ? ContentUtils.getStringTime(file.getMtime(), file) : "")); + properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.displayName"), + "", + file != null ? ContentUtils.getStringTime(file.getCtime(), file) : "")); + properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.displayName"), + "", + file != null ? ContentUtils.getStringTime(file.getAtime(), file) : "")); + properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.displayName"), + "", + file != null ? ContentUtils.getStringTime(file.getCrtime(), file) : "")); + properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filesize.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filesize.displayName"), + "", + content.getSize())); return propertySheet; } From 01749ab3abc38c364c5a9fcbe528ef86f428c7db Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 6 Aug 2014 18:01:30 -0400 Subject: [PATCH 2/5] Add file times and size properties to ContentTagNode property sheet --- Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties | 4 ++-- Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index 0966a935b2..a9cbac245a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -66,8 +66,8 @@ ContentTagNode.createSheet.fileAccessedTime.name=Accessed Time ContentTagNode.createSheet.fileAccessedTime.displayName=Accessed Time ContentTagNode.createSheet.fileCreatedTime.name=Created Time ContentTagNode.createSheet.fileCreatedTime.displayName=Created Time -ContentTagNode.createSheet.filesize.name=Size -ContentTagNode.createSheet.filesize.displayName=Size +ContentTagNode.createSheet.fileSize.name=Size +ContentTagNode.createSheet.fileSize.displayName=Size ContentTagTypeNode.displayName.text=File Tags ContentTagTypeNode.createSheet.name.name=Name ContentTagTypeNode.createSheet.name.displayName=Name diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java index 30bfb573f4..62d56c09f9 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -97,8 +97,8 @@ class ContentTagNode extends DisplayableItemNode { NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.displayName"), "", file != null ? ContentUtils.getStringTime(file.getCrtime(), file) : "")); - properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filesize.name"), - NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.filesize.displayName"), + properties.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.displayName"), "", content.getSize())); return propertySheet; From 7466fb0a6d3266de2d9a8059fc0de819cbd35bce Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Fri, 8 Aug 2014 10:46:57 -0400 Subject: [PATCH 3/5] Fixed timing issue in scheduler where file could be stuck in running state even if it had been completed --- .../sleuthkit/autopsy/ingest/IngestJob.java | 76 ++++++++++--------- .../autopsy/ingest/IngestScheduler.java | 55 +++++++------- 2 files changed, 69 insertions(+), 62 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java index 0cdcdbeb24..f885b5c171 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJob.java @@ -193,46 +193,54 @@ final class IngestJob { } void process(DataSourceIngestTask task) throws InterruptedException { - if (!isCancelled() && !dataSourceIngestPipeline.isEmpty()) { - List errors = new ArrayList<>(); - errors.addAll(dataSourceIngestPipeline.process(task, dataSourceIngestProgress)); - if (!errors.isEmpty()) { - logIngestModuleErrors(errors); - } - } - if (null != dataSourceIngestProgress) { - dataSourceIngestProgress.finish(); - // This is safe because this method will be called at most once per - // ingest job and finish() will not be called while that single - // data source ingest task has not been reported complete by this - // code to the ingest scheduler. - dataSourceIngestProgress = null; - } - ingestTaskScheduler.notifyTaskCompleted(task); - } - - void process(FileIngestTask task) throws InterruptedException { - if (!isCancelled()) { - FileIngestPipeline pipeline = fileIngestPipelines.take(); - if (!pipeline.isEmpty()) { - AbstractFile file = task.getFile(); - synchronized (this) { - ++processedFiles; - if (processedFiles <= estimatedFilesToProcess) { - fileIngestProgress.progress(file.getName(), (int) processedFiles); - } else { - fileIngestProgress.progress(file.getName(), (int) estimatedFilesToProcess); - } - } + try { + if (!isCancelled() && !dataSourceIngestPipeline.isEmpty()) { List errors = new ArrayList<>(); - errors.addAll(pipeline.process(task)); + errors.addAll(dataSourceIngestPipeline.process(task, dataSourceIngestProgress)); if (!errors.isEmpty()) { logIngestModuleErrors(errors); } } - fileIngestPipelines.put(pipeline); + if (null != dataSourceIngestProgress) { + dataSourceIngestProgress.finish(); + // This is safe because this method will be called at most once per + // ingest job and finish() will not be called while that single + // data source ingest task has not been reported complete by this + // code to the ingest scheduler. + dataSourceIngestProgress = null; + } + } + finally { + ingestTaskScheduler.notifyTaskCompleted(task); + } + } + + void process(FileIngestTask task) throws InterruptedException { + try { + if (!isCancelled()) { + FileIngestPipeline pipeline = fileIngestPipelines.take(); + if (!pipeline.isEmpty()) { + AbstractFile file = task.getFile(); + synchronized (this) { + ++processedFiles; + if (processedFiles <= estimatedFilesToProcess) { + fileIngestProgress.progress(file.getName(), (int) processedFiles); + } else { + fileIngestProgress.progress(file.getName(), (int) estimatedFilesToProcess); + } + } + List errors = new ArrayList<>(); + errors.addAll(pipeline.process(task)); + if (!errors.isEmpty()) { + logIngestModuleErrors(errors); + } + } + fileIngestPipelines.put(pipeline); + } + } + finally { + ingestTaskScheduler.notifyTaskCompleted(task); } - ingestTaskScheduler.notifyTaskCompleted(task); } void finish() { diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestScheduler.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestScheduler.java index 5f74bb68c8..b5338cd773 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestScheduler.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestScheduler.java @@ -52,30 +52,34 @@ final class IngestScheduler { private final AtomicLong nextIngestJobId = new AtomicLong(0L); private final ConcurrentHashMap ingestJobsById = new ConcurrentHashMap<>(); private volatile boolean enabled = false; -// private volatile boolean cancellingAllTasks = false; TODO: Uncomment this with related code, if desired + // private volatile boolean cancellingAllTasks = false; TODO: Uncomment this with related code, if desired private final DataSourceIngestTaskQueue dataSourceTaskDispenser = new DataSourceIngestTaskQueue(); private final FileIngestTaskQueue fileTaskDispenser = new FileIngestTaskQueue(); + // The following five collections lie at the heart of the scheduler. - // // The pending tasks queues are used to schedule tasks for an ingest job. If // multiple jobs are scheduled, tasks from different jobs may become - // interleaved in these queues. Data source tasks go into a simple FIFO - // queue that is consumed by the ingest threads. File tasks are "shuffled" + // interleaved in these queues. + + // FIFO queue for data source-level tasks. + private final LinkedBlockingQueue pendingDataSourceTasks = new LinkedBlockingQueue<>(); // Guarded by this + + // File tasks are "shuffled" // through root directory (priority queue), directory (LIFO), and file tasks // queues (LIFO). If a file task makes it into the pending file tasks queue, // it is consumed by the ingest threads. - // - // The "tasks in progress" list is used to determine when an ingest job is - // completed and should be shut down, i.e., the job should shut down its - // ingest pipelines and finish its progress bars. Tasks stay in the "tasks - // in progress" list either until discarded by the scheduler or the ingest - // thread that is working on the task notifies the scheduler that the task - // is completed. - private final LinkedBlockingQueue pendingDataSourceTasks = new LinkedBlockingQueue<>(); private final TreeSet pendingRootDirectoryTasks = new TreeSet<>(new RootDirectoryTaskComparator()); // Guarded by this private final List pendingDirectoryTasks = new ArrayList<>(); // Guarded by this - private final BlockingDeque pendingFileTasks = new LinkedBlockingDeque<>(); - private final List tasksInProgress = new ArrayList<>(); // Guarded by this + private final BlockingDeque pendingFileTasks = new LinkedBlockingDeque<>(); // Not guarded + + // The "tasks in progress" list has: + // - File and data source tasks that are running + // - File tasks that are in the pending file queue + // It is used to determine when a job is done. It has both pending and running + // tasks because we do not lock the 'pendingFileTasks' and a task needs to be in + // at least one of the pending or inprogress lists at all times before it is completed. + // files are added to this when the are added to pendingFilesTasks and removed when they complete + private final List tasksInProgressAndPending = new ArrayList<>(); // Guarded by this synchronized static IngestScheduler getInstance() { if (instance == null) { @@ -133,12 +137,12 @@ final class IngestScheduler { synchronized private void scheduleDataSourceIngestTask(IngestJob job) throws InterruptedException { DataSourceIngestTask task = new DataSourceIngestTask(job); - tasksInProgress.add(task); + tasksInProgressAndPending.add(task); try { // Should not block, queue is (theoretically) unbounded. pendingDataSourceTasks.put(task); } catch (InterruptedException ex) { - tasksInProgress.remove(task); + tasksInProgressAndPending.remove(task); Logger.getLogger(IngestScheduler.class.getName()).log(Level.SEVERE, "Interruption of unexpected block on pending data source tasks queue", ex); //NON-NLS throw ex; } @@ -149,7 +153,6 @@ final class IngestScheduler { for (AbstractFile firstLevelFile : topLevelFiles) { FileIngestTask task = new FileIngestTask(job, firstLevelFile); if (shouldEnqueueFileTask(task)) { - tasksInProgress.add(task); pendingRootDirectoryTasks.add(task); } } @@ -212,9 +215,7 @@ final class IngestScheduler { if (shouldEnqueueFileTask(directoryTask)) { addToPendingFileTasksQueue(directoryTask); tasksEnqueuedForDirectory = true; - } else { - tasksInProgress.remove(directoryTask); - } + } // If the directory contains subdirectories or files, try to // enqueue tasks for them as well. @@ -227,13 +228,11 @@ final class IngestScheduler { if (file.hasChildren()) { // Found a subdirectory, put the task in the // pending directory tasks queue. - tasksInProgress.add(childTask); pendingDirectoryTasks.add(childTask); tasksEnqueuedForDirectory = true; } else if (shouldEnqueueFileTask(childTask)) { // Found a file, put the task directly into the // pending file tasks queue. - tasksInProgress.add(childTask); addToPendingFileTasksQueue(childTask); tasksEnqueuedForDirectory = true; } @@ -304,6 +303,7 @@ final class IngestScheduler { } synchronized private void addToPendingFileTasksQueue(FileIngestTask task) throws IllegalStateException { + tasksInProgressAndPending.add(task); try { // Should not block, queue is (theoretically) unbounded. /* add to top of list because we had one image that had a folder with @@ -313,7 +313,7 @@ final class IngestScheduler { */ pendingFileTasks.addFirst(task); } catch (IllegalStateException ex) { - tasksInProgress.remove(task); + tasksInProgressAndPending.remove(task); Logger.getLogger(IngestScheduler.class.getName()).log(Level.SEVERE, "Interruption of unexpected block on pending file tasks queue", ex); //NON-NLS throw ex; } @@ -326,7 +326,6 @@ final class IngestScheduler { // Send the file task directly to file tasks queue, no need to // update the pending root directory or pending directory tasks // queues. - tasksInProgress.add(task); addToPendingFileTasksQueue(task); } } @@ -344,7 +343,7 @@ final class IngestScheduler { boolean jobIsCompleted; IngestJob job = task.getIngestJob(); synchronized (this) { - tasksInProgress.remove(task); + tasksInProgressAndPending.remove(task); jobIsCompleted = ingestJobIsComplete(job); } if (jobIsCompleted) { @@ -382,7 +381,7 @@ final class IngestScheduler { while (iterator.hasNext()) { IngestTask task = (IngestTask) iterator.next(); if (task.getIngestJob().getId() == jobId) { - tasksInProgress.remove((IngestTask) task); + tasksInProgressAndPending.remove((IngestTask) task); iterator.remove(); } } @@ -420,13 +419,13 @@ final class IngestScheduler { synchronized private void removeAllPendingTasks(Collection taskQueue) { Iterator iterator = taskQueue.iterator(); while (iterator.hasNext()) { - tasksInProgress.remove((IngestTask) iterator.next()); + tasksInProgressAndPending.remove((IngestTask) iterator.next()); iterator.remove(); } } synchronized private boolean ingestJobIsComplete(IngestJob job) { - for (IngestTask task : tasksInProgress) { + for (IngestTask task : tasksInProgressAndPending) { if (task.getIngestJob().getId() == job.getId()) { return false; } From 69fcfd10985d4cd29e5f5d4d67bccae701ac9e76 Mon Sep 17 00:00:00 2001 From: jmillman Date: Fri, 8 Aug 2014 13:22:54 -0400 Subject: [PATCH 4/5] updated ImageAnalyze doxygen --- .../ImageAnalyzer/application_view_tile.png | Bin 0 -> 465 bytes docs/doxygen-user/ImageAnalyzer/bisque.png | Bin 0 -> 276 bytes .../ImageAnalyzer/drawabletile.png | Bin 0 -> 21419 bytes docs/doxygen-user/ImageAnalyzer/flag_gray.png | Bin 0 -> 15505 bytes docs/doxygen-user/ImageAnalyzer/flag_red.png | Bin 0 -> 665 bytes .../ImageAnalyzer/folder_picture.png | Bin 0 -> 713 bytes docs/doxygen-user/ImageAnalyzer/gray.png | Bin 0 -> 280 bytes docs/doxygen-user/ImageAnalyzer/green.png | Bin 0 -> 276 bytes .../ImageAnalyzer/hashset_hits.png | Bin 0 -> 1451 bytes docs/doxygen-user/ImageAnalyzer/orange.png | Bin 0 -> 276 bytes .../doxygen-user/ImageAnalyzer/purpledash.png | Bin 0 -> 445 bytes docs/doxygen-user/ImageAnalyzer/red.png | Bin 0 -> 276 bytes docs/doxygen-user/ImageAnalyzer/slide.png | Bin 0 -> 620 bytes .../doxygen-user/ImageAnalyzer/video-file.png | Bin 0 -> 718 bytes docs/doxygen-user/ImageAnalyzer/yellow.png | Bin 0 -> 276 bytes docs/doxygen-user/image_viewer.dox | 115 +++++++++++++++++- 16 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 docs/doxygen-user/ImageAnalyzer/application_view_tile.png create mode 100644 docs/doxygen-user/ImageAnalyzer/bisque.png create mode 100644 docs/doxygen-user/ImageAnalyzer/drawabletile.png create mode 100644 docs/doxygen-user/ImageAnalyzer/flag_gray.png create mode 100644 docs/doxygen-user/ImageAnalyzer/flag_red.png create mode 100644 docs/doxygen-user/ImageAnalyzer/folder_picture.png create mode 100644 docs/doxygen-user/ImageAnalyzer/gray.png create mode 100644 docs/doxygen-user/ImageAnalyzer/green.png create mode 100644 docs/doxygen-user/ImageAnalyzer/hashset_hits.png create mode 100644 docs/doxygen-user/ImageAnalyzer/orange.png create mode 100644 docs/doxygen-user/ImageAnalyzer/purpledash.png create mode 100644 docs/doxygen-user/ImageAnalyzer/red.png create mode 100644 docs/doxygen-user/ImageAnalyzer/slide.png create mode 100644 docs/doxygen-user/ImageAnalyzer/video-file.png create mode 100644 docs/doxygen-user/ImageAnalyzer/yellow.png diff --git a/docs/doxygen-user/ImageAnalyzer/application_view_tile.png b/docs/doxygen-user/ImageAnalyzer/application_view_tile.png new file mode 100644 index 0000000000000000000000000000000000000000..3bc0bd32fceb21d70368f7842a00a53d6369ba48 GIT binary patch literal 465 zcmV;?0WSWDP)zJNu3H-P zO&@UpeyZQXi7jKe-Hk?r-sue;aDce_XqkvXP+W#F_*ot`jB?BS93Uw71|U^ZjLH`yP%FO7U<6!nLCG} z$SDlWNn{1`8Hzopr05mv5f&c&j literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/ImageAnalyzer/drawabletile.png b/docs/doxygen-user/ImageAnalyzer/drawabletile.png new file mode 100644 index 0000000000000000000000000000000000000000..d6b97547bcad5f9d077b45de11b4b5c3c3b2172f GIT binary patch literal 21419 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 literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/ImageAnalyzer/flag_gray.png b/docs/doxygen-user/ImageAnalyzer/flag_gray.png new file mode 100644 index 0000000000000000000000000000000000000000..1888f8da96ea4b145de240a906f985540bcf8037 GIT binary patch literal 15505 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 literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/ImageAnalyzer/folder_picture.png b/docs/doxygen-user/ImageAnalyzer/folder_picture.png new file mode 100644 index 0000000000000000000000000000000000000000..052b33638eaa0f870a255bfdd5df5b79fb01a89e GIT binary patch literal 713 zcmV;)0yh1LP)Sp5G=H^urQrsC0eOy7edOd zZ7d~Lu~=e+5HQOw5_MxTbLZpEz4x5+{#{`tTev=tN#ehp|LXfMhP~b!qlMPY3^O+5 zlq{J!O})4fIKi;rd-I*MTbsRJ-?(RkrCAgCdT00TzzNEVZm-w3>$fIc-v5w|;a^m3 zm#KyF08Pzpe zb7ryOZdEg)0T~jLaO1mYch`nz&X^V4BRa)fD-uj=XaCldc=sB!STP zt;b%x&@nR*M+9c1pMS(1#*56;r$6GE{SWx%#sTA(KPSb605cl~l4`u|Hh+9WZdaff zVm1Js!Vp(~d!HC1SK5~L;STM!*NHK5?u9Ez5QtXDTXMUKT=?RE#qB-LTE(49d%VXn zE5Ley^+2!)QW-FW3lrJU+(Od^V#BJ zAQpu6Q*3UZXLH;6hw_9p`2lbCfQaG@q-RZ^I>Hr@UQU`nhdI;qPXi?DsEmmZ5T}!Pu vECtogXE(on?K#A&7z6%~0K44cy2R4oP`hnXtk|+}00000NkvXXu0mjf(HBcn literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/ImageAnalyzer/gray.png b/docs/doxygen-user/ImageAnalyzer/gray.png new file mode 100644 index 0000000000000000000000000000000000000000..538aaacd4601c0ba9fa7cc15c40f0629aa38b9cf GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^l0eMQ0VEhqc&#mg6id3JuOkD)#(wTUiL5|AV{wqX z6T`Z5GB1G~g=CK)Uj~LMH3o);76yi2K%s^g3=E|P3=FRl7#OT(FffQ0%-I!a1C(G& z@^*J&_}|`tWO>_%)r2R2!t6$HM|;tf`Xndjv*44lP!9<4SG&p@MU9I^hS2; zrW0#{atx{^t`Q|Ei6yC4$wjF^iowXh&_vh30*FEkjjW6ftxU{y4NR;I49=eq2Du!e iAvZrIGp!O$gOQn)fe}Q5_a?qXkQJV;elF{r5}E+Frbbf$ literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/ImageAnalyzer/green.png b/docs/doxygen-user/ImageAnalyzer/green.png new file mode 100644 index 0000000000000000000000000000000000000000..6aa2f632112a87d53a1f2b787a1f621600a4f5b5 GIT binary patch literal 276 zcmeAS@N?(olHy`uVBq!ia0vp^l0eMQ$P6U4SA1Irq*&4&eH|GXHuiJ>Nn{1`8H$N@T3GJ1;gBC_8 zUCa{6jK(-e_&_khOe5I{UzTOOECYmfAbgn_6W44GS$rFbCS$^=SD@$zG7~Skd!Fb2 z{C@xU|9wzdek^-e{w@FjvMpxPF0MI=SC%1u3;&2y;*uvAtA#4IRtQoY4U~FV7Y$l` zR2^-nDNjq|4Z0WrBwof*EmYgAgq!ujR00Esd;yUS0LA)HfO6N<0_dXa7{3mBbafO0 z8IKNntH_4h0tVX4m|Hozs{(XhSa{?h|4J z0yiK+y$;%Rs@hfw8d#17i(pjlMpYQ7Qo@)L)he~GfjEkx2!jx7WDHq!$=pfP4%@BM6n{8XMpWjN9m@*_p1rQ8I5uYy+*9Mvw?DRiw?1<(a zEdd&_(>&Y6xy5?a9!!$O+&$Y+0w{VzRB((~6smz_-Az8)FIY$&B;LRt#zUZ54T@oE zC91|zl?KB!8iUH9#ucPVWmGCnnxu}MxJHAT)EYIU%2cYtFe9ch8MQ_&X(}x>pd^VO zO>!-MUZDJLI%$^??QU@m&*TyYj-~{bbFgefvI8o;tibYKHUJu`ieL=1QEtYckR%GU z881n5Of&54U z;&2Sz`RIE9NSU{grH;@Y*GfFDm#Btr+q$IZ3-H>sJ?OxA4Jk7(N*amhGrwLL)sMb; zt48wswFkDcpU_^(D=*JPN`CI?`poju#~I9ecHc=w4q&aE*{l6AuMVt3$oej6+0|O|&Tz2F9!ou)@&fXO4^_U~| z@YUdBcCEGDuOFMd5X@bFx;hE=^^;8q4$vFa+gw) z`64nlSyz#FcVXnuJzshb%ni2nM`Y=IIR4kn^3vnNGqaa^6m2c5o%e3dlx4;44m)#> zfA>Hd4S$oD2?A#aGvo26r?NXcV)`)F*8b#l9Ga)-19^ zQTOdanq3;XI5%OI!Z3WIq&vHFKGO9@!Q7IL6rd(OwUIW3a}&Q$i?N)%c+`3NZ){KrSpWb4 literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/ImageAnalyzer/orange.png b/docs/doxygen-user/ImageAnalyzer/orange.png new file mode 100644 index 0000000000000000000000000000000000000000..97ef5d24b63076bfc20c42de1bf828d325e7606d GIT binary patch literal 276 zcmeAS@N?(olHy`uVBq!ia0vp^l0eMQ$P6U4SA1Irq*&4&eH|GXHuiJ>Nn{1`8HO=u<5X=vX`mpFr;B3<$Mxid1yVqsHv?mykLpq& zOSQx`q9i4;B-JXpC>2OC7#SFv=o(l6QHY_Dm9e3fiMg(UiIsuD`SZb+C>nC}Q!>*k a(KHyD!!;B!xiA4WFnGH9xvXNn{1`8H{p;7S zU%hzs>G!7(-#)y2@$%EJPp@9Rdinh2$DbeHe|`Vr`HR&FtJkHkd-vtt%a<=-JbUrs z$A{JNtDir6{#2e*8fYD7fk$L90|Vb-5N14{zaj-F80zWb7$R|b>B(q52Ll0y1B=#_ zYp#+z`qy7`T1U=hml^jH-#qxq$gj*&I4SMTw;6N%4lZcptAuJW(ls;zX)rakG63o~1!`bh5oit6 Oz~JfX=d#Wzp$P!>DYkw9 literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/ImageAnalyzer/red.png b/docs/doxygen-user/ImageAnalyzer/red.png new file mode 100644 index 0000000000000000000000000000000000000000..2e5d76720d9aa032a0e972ea753e4b49a550622e GIT binary patch literal 276 zcmeAS@N?(olHy`uVBq!ia0vp^l0eMQ$P6U4SA1Irq*&4&eH|GXHuiJ>Nn{1`8HEal|aXmRpF8FWQhbW?9;ba!ELWdK2BZ(?O2No`?g zWm08fWO;GPWjp`?0qIFZK~y+T#golT8$lSx-`*H8h|DaGB@Sq@QFJ5w4RD>Kn*kirwL0i#6sUS(pwh6ky%zpUHx>-rvQ+x1* zAJ5Fg?7K6w&$2KK>3@pH`p?BI%lfSAdI4x)OVhLrRDd?dS3wi#s;X+B2`oiXPC4iM zrBZ2_3n2>NHTVP;z#-TKHgG)8v%w}f^nHH~IAAFVf?IM2Di&>dbR`_O72c zst45ZAB$J5`r?~(xm;$lL2`*YOVdr$pd;6#NBShCc`EjMx_l${JC!sL}Qttcx+nud-|g&zU)A&OW;fXC^-)=!FGqvDxqY zt`D)+A|hvjHvSK3Ywedvxo9gEy)obS5K)MLh#-3ja_DCy5+NFm_5f7^JkP^%9hA~& ztq>8yFl23Qjg5^B9u1Gs-`_`j`}IBI3Rxu)C1|b5<#G%U4UtN9kj<|1c6ydn$2GdT zZqeL)4z2ay36uysJ3CBGPLr%}WPW~;*4C?}Qtc!Xi39B@rK$wgOhjmCXyD1yXMFy& zOm;O(ZEYR>eFJ1ND-7O$$l$#PM;u6b3*D~kj(NUM=Z!9oCr%KH#!y-#2qrWHB_Fro zFgf`er4(B09&7FM$bJV(DLT7u64f=FY-(bA`v=xSvOY=XYZhmB_kj9=77#&+@bbkg za=D*;+uGv$<|d_Lfz9=GYU5F+-%R~k?~xglT_lrrEPhjqAF^VljL44$AR(WQ3ZUW5l90 z=tu+sOlT;Te0)~Sc6@w11GH2&cwmT!kw_$n$KwP+fQT?LF|pFy+j|@MV9Ux?2Z1qFWn+vr#@H|n ztue+Hi^XtsboAYR)^j2E>me$3{OK+5i9m07*qoM6N<$f<6*D AG5`Po literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/ImageAnalyzer/yellow.png b/docs/doxygen-user/ImageAnalyzer/yellow.png new file mode 100644 index 0000000000000000000000000000000000000000..0e593b22d30ac923bd36f3dfea0e92121da94699 GIT binary patch literal 276 zcmeAS@N?(olHy`uVBq!ia0vp^l0eMQ$P6U4SA1Irq*&4&eH|GXHuiJ>Nn{1`8HUO_QmvAUQh^kMk%6I!u7L#*g%}!H85>%enCTjrSQ!{(?uh6`(U6;;l9^VC Zslmt)qCr~sx&=@JgQu&X%Q~loCIAk=LM{LR literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/image_viewer.dox b/docs/doxygen-user/image_viewer.dox index 56377ede01..e7d9666f77 100644 --- a/docs/doxygen-user/image_viewer.dox +++ b/docs/doxygen-user/image_viewer.dox @@ -1,10 +1,117 @@ /*! \page image_viewer_page Image and Video Viewer - Overview ---------- +======== +This document outlines the use of the new Image Analyzer feature of Autopsy. This feature was funded by DHS S&T to help provide free and open source digital forensics tools to law enforcement. + +The new image analyzer feature has been designed specifically with child-exploitation cases in mind, but can be used for a variety of other investigation types that involve images and videos. It offers the following features beyond the traditional long list of thumbnails that Autopsy and other tools currently provide. +- Groups images by folder (and other attributes) to help examiner break the large set of images into smaller groups and to help focus on areas with images of interest. +- Allows examiner to start viewing images immediately upon adding them to the case. As images are hashed, they are updated in the interface. You do not need to wait until the entire image is ingested. + +This document assumes basic familiarity with Autopsy. + +Quick Start +=========== +1. The Image Analysis tool can be configured to collect data about images/videos as ingest runs or all at once after ingest. To change this setting go to Tools->Options->Image /Video Analyzer. This setting is saved per case, but can not be changed during ingest. +2. Create a case as normal and add a disk image (or folder of files) as a data source. Ensure that you have the hash lookup module enabled with NSRL and known bad hashsets, the EXIF module enabled, and the File Type module enabled. +3. Click Tools->Analyze Images/Videos in the menu. This will open the Autopsy Image/Video Analysis tool in a new window. +4. Groups of images will be presented as they are analyzed by the background ingest modules. You can later resort and regroup, but it is required to keep it grouped by folder while ingest is still ongoing. +5. As each group is reviewed, the next highest priority group is presented, according to a sorting criteria (the default is the density of hash set hits). +6. Images that were hits from hashsets, will have a dashed border around them. +7. You can use the menu bar on the top of the group to categorize the entire group. +8. You can right click on an image to categorize or tag the individual image. +9. Tag files with customizable tags. A ‘Follow Up’ tag is already built into the tool and integrated into the filter options. Tags can be applied in addition to categorization. An image can only have one categorization, but can have many tags to support your work-flow. +10. Create a report containing the details of every tagged and/or categorized file, via the standard Autopsy report generation feature. + +Use Case Details +=============== +In addition to the basic ideas presented in the previous section, here are some hints on use cases that were designed into the tool. +- When you are viewing the groups, they are presented in an order based on density of hash hits(by default). If you find a group that has lots of interesting files and you want to see what is in the parent folder or nearby folders, use the navigation tree on the left. +- At any time, you can use the list on the left-hand side to see the groups with the largest hashset hits. +- To see which folders have the most images in them, sort the groups by group size (descending). +- Files that have hashset hits are not automatically tagged or categorized. You need to do that after reviewing them. The easiest way to do that is to wait until ingest is over and then group by hashsets. You can then review each group and categorize the entire group at a time using the group header. + +Categories +========== +The tool has been designed specifically with child-exploitation cases in mind and has a notion of categorizes. We will be changing this in the future to be more flexible with custom category names, but currently it is hard coded to use the names that Project Vic (and other international groups) use. We have assigned colors to each category to highlight each image. + + +Name|Description|Color +----|-----------------|------ +CAT-0|Uncategorized|![gray](ImageAnalyzer/gray.png) +CAT-1|Child Abuse Material |![red](ImageAnalyzer/red.png) +CAT-2|Child Exploitative / Age Difficult|![orange](ImageAnalyzer/orange.png) +CAT-3|CGI / Animation|![yellow](ImageAnalyzer/yellow.png) +CAT-4|Comparison Images |![bisque](ImageAnalyzer/bisque.png) +CAT-5|Non-pertinent|![green](ImageAnalyzer/green.png) + +GUI controls +================= +You can do your entire investigation using the mouse, but many examiners like to use keyboard shortcuts to quickly process large amounts of images. + +Keyboard Shortcuts +----------------- +shortcut | action +-----------|------ +digits 0-5 | assign the correspondingly numbered category to the selected file(s) +alt + 0-5 | assign the correspondingly numbered category to all files in the focused group +arrows | select the next file in the direction pressed +page up/down | scroll the list of files + +Additional Mouse Controls +------------------------- +mouse gesture| action +----------|---------- +ctrl + left click|toggle selection of clicked file, select multiple files +right click on file|bring up context menu allowing per file actions (tag, categorize, extract to local file, view in external viewer, view in Autopsy content viewer, add file to HashDB) +right click empty space of group|bring up context menu allowing per group actions (tag, categorize, extract to local file(s), add file(s) to HashDB) +double click on file|open selected file in slide show mode + +UI Details +========== +Group Display Area +------------------- +The central display area contains the list of files in the current group. Images in the group can be displayed in either thumbnail mode or slide show mode. Slide show mode provides larger images and playback of video files. At the right of the group header is a toggle for changing the viewing mode of the group (tiles vs slide-show ). + +Image/Video Tiles +----------------- + +Each file is represented in the main display area via a small tile. The tile shows: +- Thumbnail of the image/video +- Name of the file +- Indicators of other important details: + +| image | description | meaning| +|----|----|-----| +| | solid colored border | file’s assigned category.| +| ![](ImageAnalyzer/purpledash.png) "" | purple dashed border | file has a known bad hashset hit, but has not yet been categorized. | +| ![](ImageAnalyzer/hashset_hits.png) ""|pushpin | file has a known bad hashset hit| +| ![](ImageAnalyzer/video-file.png) ""| clapboard on document | video file| +| ![](ImageAnalyzer/flag_red.png) ""| a red flag | file has been 'flagged' as with the follow up tag| + + +Slide Show Mode +--------------- +In slide show mode a group shows only one file at a time at an increased size. Per file tag/category controls above the top right corner of the image, and large left and right buttons allow cycling through the files in the group. If the active file is an Autopsy supported video format, video playback controls appear below the video. + +Table/Tree of contents +---------------------- +The section in the top left with tabs labeled “Contents” and “Hash Hits” provides an overview of the groups of files in the case. It changes to reflect the current Group By setting: for hierarchical groupings (path) it shows a tree of folders (folders containing images/videos (groups) are marked with a distinctive icon ), and for other groupings it shows only a flat list. + +Each group shows the number of files that hit against configured Hash DBs during ingest (hash hits) and the total number of image/video files as a ratio (hash hits / total) after its name. By selecting groups in the tree/list you can navigate directly to them in the main display area. If the Hash Hits tab is selected only groups containing files that have hash hits are shown. + + + + + + + + + + + + + -Quick Start ----------- From 7c1945e203a7585a13e1c72bb0d59aaaa1352c39 Mon Sep 17 00:00:00 2001 From: jmillman Date: Mon, 11 Aug 2014 10:31:29 -0400 Subject: [PATCH 5/5] ensure GUI update on JFX thread --- .../sleuthkit/autopsy/imageanalyzer/gui/EurekaToolbar.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ImageAnalyzer/src/org/sleuthkit/autopsy/imageanalyzer/gui/EurekaToolbar.java b/ImageAnalyzer/src/org/sleuthkit/autopsy/imageanalyzer/gui/EurekaToolbar.java index 47debdf433..7c2ac9913c 100644 --- a/ImageAnalyzer/src/org/sleuthkit/autopsy/imageanalyzer/gui/EurekaToolbar.java +++ b/ImageAnalyzer/src/org/sleuthkit/autopsy/imageanalyzer/gui/EurekaToolbar.java @@ -47,6 +47,7 @@ import org.sleuthkit.autopsy.imageanalyzer.FXMLConstructor; import org.sleuthkit.autopsy.imageanalyzer.FileIDSelectionModel; import org.sleuthkit.autopsy.imageanalyzer.IconCache; import org.sleuthkit.autopsy.imageanalyzer.TagUtils; +import org.sleuthkit.autopsy.imageanalyzer.ThreadUtils; import org.sleuthkit.autopsy.imageanalyzer.datamodel.Category; import org.sleuthkit.autopsy.imageanalyzer.datamodel.DrawableAttribute; import org.sleuthkit.autopsy.imageanalyzer.grouping.GroupSortBy; @@ -142,8 +143,10 @@ public class EurekaToolbar extends ToolBar { assert tagSelectedMenuButton != null : "fx:id=\"tagSelectedMenubutton\" was not injected: check your FXML file 'EurekaToolbar.fxml'."; FileIDSelectionModel.getInstance().getSelected().addListener((Observable o) -> { - tagSelectedMenuButton.setDisable(FileIDSelectionModel.getInstance().getSelected().isEmpty()); - catSelectedMenuButton.setDisable(FileIDSelectionModel.getInstance().getSelected().isEmpty()); + ThreadUtils.runNowOrLater(() -> { + tagSelectedMenuButton.setDisable(FileIDSelectionModel.getInstance().getSelected().isEmpty()); + catSelectedMenuButton.setDisable(FileIDSelectionModel.getInstance().getSelected().isEmpty()); + }); }); tagSelectedMenuButton.setOnAction((ActionEvent t) -> {