Merge remote-tracking branch 'upstream/develop' into data_result_viewer_video_thumbnails

This commit is contained in:
jmillman
2015-07-07 13:00:40 -04:00
119 changed files with 3702 additions and 2341 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ correct C libraries.
STEPS:
1) Get Java Setup
1a) Download and install JDK version 1.8. You can now use 32-bit or 64-bit, but special work is needed to get The Sleuth Kit to compile as 64-bit. So, 32-bit is easier.
1a) Download and install JDK version 1.8. For the current version of JavaFX that we use, you'll need 1.8.0_40 or greater. You can now use 32-bit or 64-bit, but special work is needed to get The Sleuth Kit to compile as 64-bit. So, 32-bit is easier.
Autopsy has been used and tested with Oracle JavaSE and the included JavaFX support
(http://www.oracle.com/technetwork/java/javase/downloads/index.html).
+1 -1
View File
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
OpenIDE-Module: org.sleuthkit.autopsy.core/10
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/core/Bundle.properties
OpenIDE-Module-Layer: org/sleuthkit/autopsy/core/layer.xml
OpenIDE-Module-Implementation-Version: 13
OpenIDE-Module-Implementation-Version: 14
OpenIDE-Module-Requires: org.openide.windows.WindowManager
AutoUpdate-Show-In-Client: true
AutoUpdate-Essential-Module: true
+1 -1
View File
@@ -17,5 +17,5 @@ license.file=../LICENSE-2.0.txt
nbm.homepage=http://www.sleuthkit.org/
nbm.module.author=Brian Carrier
nbm.needs.restart=true
spec.version.base=10.2
spec.version.base=10.3
+1
View File
@@ -192,6 +192,7 @@
<package>org.sleuthkit.autopsy.coreutils</package>
<package>org.sleuthkit.autopsy.datamodel</package>
<package>org.sleuthkit.autopsy.directorytree</package>
<package>org.sleuthkit.autopsy.events</package>
<package>org.sleuthkit.autopsy.externalresults</package>
<package>org.sleuthkit.autopsy.filesearch</package>
<package>org.sleuthkit.autopsy.ingest</package>
@@ -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");
@@ -19,61 +19,69 @@
package org.sleuthkit.autopsy.actions;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import javax.swing.AbstractAction;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import org.openide.util.NbBundle;
import org.openide.util.actions.Presenter;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* An abstract base class for Actions that allow users to tag SleuthKit data
* model objects.
* An abstract base class for Actions that allow users to tag SleuthKit data
* model objects.
*/
abstract class AddTagAction extends TagAction implements Presenter.Popup {
abstract class AddTagAction extends AbstractAction implements Presenter.Popup {
private static final String NO_COMMENT = "";
AddTagAction(String menuText) {
super(menuText);
}
@Override
public JMenuItem getPopupPresenter() {
public JMenuItem getPopupPresenter() {
return new TagMenu();
}
@Override
protected void doAction(ActionEvent event) {
}
/**
* Template method to allow derived classes to provide a string for for a
* menu item label.
* Subclasses of AddTagAction, should not override actionPerformed, but
* instead override addTag.
*
* @param event
*/
@Override
@SuppressWarnings("NoopMethodInAbstractClass")
public void actionPerformed(ActionEvent event) {
}
/**
* Template method to allow derived classes to provide a string for a menu
* item label.
*/
abstract protected String getActionDisplayName();
/**
* Template method to allow derived classes to add the indicated tag and
* comment to one or more a SleuthKit data model objects.
* Template method to allow derived classes to add the indicated tag and
* comment to one or more SleuthKit data model objects.
*/
abstract protected void addTag(TagName tagName, String comment);
/**
* Instances of this class implement a context menu user interface for
* creating or selecting a tag name for a tag and specifying an optional tag
* comment.
* Instances of this class implement a context menu user interface for
* creating or selecting a tag name for a tag and specifying an optional tag
* comment.
*/
// @@@ This user interface has some significant usability issues and needs
// to be reworked.
private class TagMenu extends JMenu {
TagMenu() {
super(getActionDisplayName());
@@ -83,35 +91,29 @@ abstract class AddTagAction extends TagAction implements Presenter.Popup {
try {
tagNames = tagsManager.getAllTagNames();
Collections.sort(tagNames);
}
catch (TskCoreException ex) {
} catch (TskCoreException ex) {
Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex); //NON-NLS
}
// Create a "Quick Tag" sub-menu.
JMenu quickTagMenu = new JMenu(NbBundle.getMessage(this.getClass(), "AddTagAction.quickTag"));
add(quickTagMenu);
add(quickTagMenu);
// Each tag name in the current set of tags gets its own menu item in
// the "Quick Tags" sub-menu. Selecting one of these menu items adds
// a tag with the associated tag name.
if (null != tagNames && !tagNames.isEmpty()) {
for (final TagName tagName : tagNames) {
JMenuItem tagNameItem = new JMenuItem(tagName.getDisplayName());
tagNameItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
addTag(tagName, NO_COMMENT);
refreshDirectoryTree();
}
tagNameItem.addActionListener((ActionEvent e) -> {
addTag(tagName, NO_COMMENT);
});
quickTagMenu.add(tagNameItem);
}
}
else {
} else {
JMenuItem empty = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.noTags"));
empty.setEnabled(false);
quickTagMenu.add(empty);
quickTagMenu.add(empty);
}
quickTagMenu.addSeparator();
@@ -120,14 +122,10 @@ abstract class AddTagAction extends TagAction implements Presenter.Popup {
// Selecting this item initiates a dialog that can be used to create
// or select a tag name and adds a tag with the resulting name.
JMenuItem newTagMenuItem = new JMenuItem(NbBundle.getMessage(this.getClass(), "AddTagAction.newTag"));
newTagMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TagName tagName = GetTagNameDialog.doDialog();
if (tagName != null) {
addTag(tagName, NO_COMMENT);
refreshDirectoryTree();
}
newTagMenuItem.addActionListener((ActionEvent e) -> {
TagName tagName = GetTagNameDialog.doDialog();
if (null != tagName) {
addTag(tagName, NO_COMMENT);
}
});
quickTagMenu.add(newTagMenuItem);
@@ -137,17 +135,13 @@ abstract class AddTagAction extends TagAction implements Presenter.Popup {
// optional comment and adds a tag with the resulting name.
JMenuItem tagAndCommentItem = new JMenuItem(
NbBundle.getMessage(this.getClass(), "AddTagAction.tagAndComment"));
tagAndCommentItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog();
if (null != tagNameAndComment) {
addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
refreshDirectoryTree();
}
tagAndCommentItem.addActionListener((ActionEvent e) -> {
GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog();
if (null != tagNameAndComment) {
addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
}
});
add(tagAndCommentItem);
add(tagAndCommentItem);
}
}
}
}
}
@@ -21,19 +21,19 @@ package org.sleuthkit.autopsy.actions;
import java.awt.event.ActionEvent;
import java.util.Collection;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.AbstractAction;
import javax.swing.JOptionPane;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Instances of this Action allow users to delete tags applied to blackboard artifacts.
*/
public class DeleteBlackboardArtifactTagAction extends TagAction {
public class DeleteBlackboardArtifactTagAction extends AbstractAction {
private static final String MENU_TEXT = NbBundle.getMessage(DeleteBlackboardArtifactTagAction.class,
"DeleteBlackboardArtifactTagAction.deleteTags");
@@ -54,7 +54,7 @@ public class DeleteBlackboardArtifactTagAction extends TagAction {
}
@Override
protected void doAction(ActionEvent event) {
public void actionPerformed(ActionEvent event) {
Collection<? extends BlackboardArtifactTag> selectedTags = Utilities.actionsGlobalContext().lookupAll(BlackboardArtifactTag.class);
for (BlackboardArtifactTag tag : selectedTags) {
try {
@@ -21,19 +21,19 @@ package org.sleuthkit.autopsy.actions;
import java.awt.event.ActionEvent;
import java.util.Collection;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.AbstractAction;
import javax.swing.JOptionPane;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Instances of this Action allow users to delete tags applied to content.
*/
public class DeleteContentTagAction extends TagAction {
public class DeleteContentTagAction extends AbstractAction {
private static final String MENU_TEXT = NbBundle.getMessage(DeleteContentTagAction.class,
"DeleteContentTagAction.deleteTags");
@@ -54,7 +54,7 @@ public class DeleteContentTagAction extends TagAction {
}
@Override
protected void doAction(ActionEvent e) {
public void actionPerformed(ActionEvent e) {
Collection<? extends ContentTag> selectedTags = Utilities.actionsGlobalContext().lookupAll(ContentTag.class);
for (ContentTag tag : selectedTags) {
try {
@@ -1,60 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.actions;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.BlackboardArtifact;
/**
* Abstract base class for Actions involving tags.
*/
abstract class TagAction extends AbstractAction {
public TagAction(String menuText) {
super(menuText);
}
@Override
public void actionPerformed(ActionEvent event) {
doAction(event);
refreshDirectoryTree();
}
/**
* Derived classes must implement this Template Method for actionPerformed().
* @param event ActionEvent object passed to actionPerformed()
*/
abstract protected void doAction(ActionEvent event);
/**
* Derived classes should call this method any time a tag is created, updated
* or deleted outside of an actionPerformed() call.
*/
@SuppressWarnings("deprecation")
protected void refreshDirectoryTree() {
/* Note: this is a hack. In an ideal world, TagsManager would fire events so
* that the directory tree would refresh. But, we haven't had a chance to add
* that so, we fire these events and the tree refreshes based on them.
*/
IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent("TagAction", BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE)); //NON-NLS
}
}
@@ -19,12 +19,15 @@
package org.sleuthkit.autopsy.casemodule;
import java.awt.Frame;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;
@@ -49,8 +52,19 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.coreutils.Version;
import org.sleuthkit.datamodel.*;
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.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.Report;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.SleuthkitJNI.CaseDbHandle.AddImageProcess;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskException;
/**
* Stores all information for a given case. Only a single case can currently be
@@ -126,7 +140,19 @@ public class Case implements SleuthkitCase.ErrorObserver {
* case. The old value supplied by the event object is null and the new
* value is a reference to a Report object representing the new report.
*/
REPORT_ADDED;
REPORT_ADDED,
/** Property name for the event when a new BlackBoardArtifactTag is
* added. The new value is tag added, the old value is empty */
BLACKBOARD_ARTIFACT_TAG_ADDED,
/** Property name for the event when a new BlackBoardArtifactTag is
* deleted. The new value is empty, the old value is the deleted tag */
BLACKBOARD_ARTIFACT_TAG_DELETED,
/** Property name for the event when a new ContentTag is
* added. The new value is tag added, the old value is empty */
CONTENT_TAG_ADDED,
/** Property name for the event when a new ContentTag is
* deleted. The new value is empty, the old value is the deleted tag */
CONTENT_TAG_DELETED;
};
private String name;
@@ -267,12 +293,13 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* Creates a new case (create the XML config file and database)
*
* @param caseDir The directory to store case data in. Will be created if it
* doesn't already exist. If it exists, it should have all of the needed sub
* dirs that createCaseDirectory() will create.
* @param caseName the name of case
* @param caseDir The directory to store case data in. Will be created if
* it
* doesn't already exist. If it exists, it should have all of the needed sub
* dirs that createCaseDirectory() will create.
* @param caseName the name of case
* @param caseNumber the case number
* @param examiner the examiner for this case
* @param examiner the examiner for this case
*/
public static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException {
logger.log(Level.INFO, "Creating new case.\ncaseDir: {0}\ncaseName: {1}", new Object[]{caseDir, caseName}); //NON-NLS
@@ -418,7 +445,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* Sends out event and reopens windows if needed.
*
* @param imgPaths the paths of the image that being added
* @param imgId the ID of the image that being added
* @param imgId the ID of the image that being added
* @param timeZone the timeZone of the image where it's added
*/
@Deprecated
@@ -463,9 +490,54 @@ public class Case implements SleuthkitCase.ErrorObserver {
* @param newDataSource new data source added
*/
void notifyNewDataSource(Content newDataSource) {
notifyPropertyChangeEvent(new PropertyChangeEvent(Case.class, Events.DATA_SOURCE_ADDED.toString(), null, newDataSource));
CoreComponentControl.openCoreWindows();
}
/**
* Notifies the UI that a new ContentTag has been added.
*
* @param newTag new ContentTag added
*/
public void notifyContentTagAdded(ContentTag newTag) {
notifyPropertyChangeEvent(new ContentTagAddedEvent(newTag));
}
/**
* Notifies the UI that a ContentTag has been deleted.
*
* @param deletedTag ContentTag deleted
*/
public void notifyContentTagDeleted(ContentTag deletedTag) {
notifyPropertyChangeEvent(new ContentTagDeletedEvent(deletedTag));
}
/**
* Notifies the UI that a new BlackboardArtifactTag has been added.
*
* @param newTag new BlackboardArtifactTag added
*/
public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
notifyPropertyChangeEvent(new BlackBoardArtifactTagAddedEvent(newTag));
}
/**
* Notifies the UI that a BlackboardArtifactTag has been.
*
* @param deletedTag BlackboardArtifactTag deleted
*/
public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
notifyPropertyChangeEvent(new BlackBoardArtifactTagDeletedEvent(deletedTag));
}
/**
* Notifies the UI about a Case level event.
*
* @param propertyChangeEvent the event to distribute
*/
private void notifyPropertyChangeEvent(final PropertyChangeEvent propertyChangeEvent) {
try {
pcs.firePropertyChange(Events.DATA_SOURCE_ADDED.toString(), null, newDataSource);
pcs.firePropertyChange(propertyChangeEvent);
} catch (Exception e) {
logger.log(Level.SEVERE, "Case threw exception", e); //NON-NLS
MessageNotifyUtil.Notify.show(NbBundle.getMessage(this.getClass(), "Case.moduleErr"),
@@ -473,7 +545,6 @@ public class Case implements SleuthkitCase.ErrorObserver {
"Case.changeCase.errListenToCaseUpdates.msg"),
MessageNotifyUtil.MessageType.ERROR);
}
CoreComponentControl.openCoreWindows();
}
/**
@@ -540,9 +611,9 @@ public class Case implements SleuthkitCase.ErrorObserver {
* Updates the case name.
*
* @param oldCaseName the old case name that wants to be updated
* @param oldPath the old path that wants to be updated
* @param oldPath the old path that wants to be updated
* @param newCaseName the new case name
* @param newPath the new path
* @param newPath the new path
*/
void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException {
try {
@@ -801,6 +872,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
* Get the data model Content objects in the root of this case's hierarchy.
*
* @return a list of the root objects
*
* @throws org.sleuthkit.datamodel.TskCoreException
*/
public List<Content> getDataSources() throws TskCoreException {
@@ -934,7 +1006,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* to create the case directory
*
* @param caseDir Path to the case directory (typically base + case name)
* @param caseDir Path to the case directory (typically base + case name)
* @param caseName the case name (used only for error messages)
*
* @throws CaseActionException throw if could not create the case dir
@@ -1154,16 +1226,25 @@ public class Case implements SleuthkitCase.ErrorObserver {
/**
* Adds a report to the case.
*
* @param [in] localPath The path of the report file, must be in the case
* directory or one of its subdirectories.
* @param [in] sourceModuleName The name of the module that created the
* report.
* @param [in] reportName The report name, may be empty.
* @param localPath The path of the report file, must be in the case
* directory or one of its subdirectories.
* @param sourceModuleName The name of the module that created the
* report.
* @param reportName The report name, may be empty.
*
* @return A Report data transfer object (DTO) for the new row.
*
* @throws TskCoreException
*/
public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
Report report = this.db.addReport(localPath, srcModuleName, reportName);
String normalizedLocalPath;
try {
normalizedLocalPath = Paths.get(localPath).normalize().toString();
} catch (InvalidPathException ex) {
String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
throw new TskCoreException(errorMsg, ex);
}
Report report = this.db.addReport(normalizedLocalPath, srcModuleName, reportName);
try {
Case.pcs.firePropertyChange(Events.REPORT_ADDED.toString(), null, report);
} catch (Exception ex) {
@@ -14,4 +14,8 @@ FileManager.addLocalDirInt2.exception.closed.msg=Attempted to use FileManager af
TagsManager.addContentTag.exception.beginByteOffsetOOR.msg=beginByteOffset \= {0} out of content size range (0 - {1})
TagsManager.addContentTag.exception.endByteOffsetOOR.msg=endByteOffset \= {0} out of content size range (0 - {1})
TagsManager.addContentTag.exception.endLTbegin.msg=endByteOffset < beginByteOffset
TagsManager.predefTagNames.bookmark.text=Bookmark
TagsManager.predefTagNames.bookmark.text=Bookmark
TagsManager.addContentTag.noCaseWarning=Failed to add publish new content tag event. There is no case open.
TagsManager.deleteContentTag.noCaseWarning=Failed to add publish content tag deleted event. There is no case open.
TagsManager.addBlackboardArtifactTag.noCaseWarning=Failed to add publish new blackboard artifact tag event. There is no case open.
TagsManager.deleteBlackboardArtifactTag.noCaseWarning=Failed to add publish blackboard artifact tag deleted event. There is no case open.
@@ -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");
@@ -24,9 +24,9 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
@@ -37,28 +37,32 @@ import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A per case instance of this class functions as an Autopsy service that
* manages the creation, updating, and deletion of tags applied to content and
* blackboard artifacts by users.
* A per case instance of this class functions as an Autopsy service that
* manages the creation, updating, and deletion of tags applied to content and
* blackboard artifacts by users.
*/
public class TagsManager implements Closeable {
private static final String TAGS_SETTINGS_NAME = "Tags"; //NON-NLS
private static final String TAG_NAMES_SETTING_KEY = "TagNames"; //NON-NLS
private final SleuthkitCase tskCase;
private final SleuthkitCase tskCase;
private final HashMap<String, TagName> uniqueTagNames = new HashMap<>();
private boolean tagNamesInitialized = false; // @@@ This is part of a work around to be removed when database access on the EDT is correctly synchronized.
// Use this exception and the member hash map to manage uniqueness of hash
// names. This is deemed more proactive and informative than leaving this to
// the UNIQUE constraint on the display_name field of the tag_names table in
// the case database.
public class TagNameAlreadyExistsException extends Exception {
}
/**
* Package-scope constructor for use of the Services class. An instance of
* Use this exception and the member hash map to manage uniqueness of hash
* names. This is deemed more proactive and informative than leaving this to
* the UNIQUE constraint on the display_name field of the tag_names table in
* the case database.
*/
public static class TagNameAlreadyExistsException extends Exception {
}
/**
* Package-scope constructor for use of the Services class. An instance of
* TagsManager should be created for each case that is opened.
* @param [in] tskCase The SleuthkitCase object for the current case.
*
* @param tskCase The SleuthkitCase object for the current case.
*/
TagsManager(SleuthkitCase tskCase) {
this.tskCase = tskCase;
@@ -67,38 +71,44 @@ public class TagsManager implements Closeable {
}
/**
* Gets a list of all tag names currently available for tagging content or
* Gets a list of all tag names currently available for tagging content or
* blackboard artifacts.
* @return A list, possibly empty, of TagName data transfer objects (DTOs).
* @throws TskCoreException
*
* @return A list, possibly empty, of TagName data transfer objects (DTOs).
*
* @throws TskCoreException
*/
public synchronized List<TagName> getAllTagNames() throws TskCoreException {
public synchronized List<TagName> getAllTagNames() throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
return tskCase.getAllTagNames();
}
/**
* Gets a list of all tag names currently used for tagging content or
* Gets a list of all tag names currently used for tagging content or
* blackboard artifacts.
* @return A list, possibly empty, of TagName data transfer objects (DTOs).
* @throws TskCoreException
*
* @return A list, possibly empty, of TagName data transfer objects (DTOs).
*
* @throws TskCoreException
*/
public synchronized List<TagName> getTagNamesInUse() throws TskCoreException {
public synchronized List<TagName> getTagNamesInUse() throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
return tskCase.getTagNamesInUse();
}
/**
* Checks whether a tag name with a given display name exists.
* @param [in] tagDisplayName The display name for which to check.
*
* @param tagDisplayName The display name for which to check.
*
* @return True if the tag name exists, false otherwise.
*/
public synchronized boolean tagNameExists(String tagDisplayName) {
@@ -106,15 +116,19 @@ public class TagsManager implements Closeable {
if (!tagNamesInitialized) {
getExistingTagNames();
}
return uniqueTagNames.containsKey(tagDisplayName);
}
/**
* Adds a new tag name to the current case and to the tags settings.
* @param [in] displayName The display name for the new tag name.
* @return A TagName data transfer object (DTO) representing the new tag name.
* @throws TagNameAlreadyExistsException, TskCoreException
*
* @param displayName The display name for the new tag name.
*
* @return A TagName data transfer object (DTO) representing the new tag
* name.
*
* @throws TagNameAlreadyExistsException, TskCoreException
*/
public TagName addTagName(String displayName) throws TagNameAlreadyExistsException, TskCoreException {
return addTagName(displayName, "", TagName.HTML_COLOR.NONE);
@@ -122,10 +136,14 @@ public class TagsManager implements Closeable {
/**
* Adds a new tag name to the current case and to the tags settings.
* @param [in] displayName The display name for the new tag name.
* @param [in] description The description for the new tag name.
* @return A TagName data transfer object (DTO) representing the new tag name.
* @throws TagNameAlreadyExistsException, TskCoreException
*
* @param displayName The display name for the new tag name.
* @param description The description for the new tag name.
*
* @return A TagName data transfer object (DTO) representing the new tag
* name.
*
* @throws TagNameAlreadyExistsException, TskCoreException
*/
public TagName addTagName(String displayName, String description) throws TagNameAlreadyExistsException, TskCoreException {
return addTagName(displayName, description, TagName.HTML_COLOR.NONE);
@@ -133,82 +151,95 @@ public class TagsManager implements Closeable {
/**
* Adds a new tag name to the current case and to the tags settings.
* @param [in] displayName The display name for the new tag name.
* @param [in] description The description for the new tag name.
* @param [in] color The HTML color to associate with the new tag name.
* @return A TagName data transfer object (DTO) representing the new tag name.
* @throws TagNameAlreadyExistsException, TskCoreException
*
* @param displayName The display name for the new tag name.
* @param description The description for the new tag name.
* @param color The HTML color to associate with the new tag name.
*
* @return A TagName data transfer object (DTO) representing the new tag
* name.
*
* @throws TagNameAlreadyExistsException, TskCoreException
*/
public synchronized TagName addTagName(String displayName, String description, TagName.HTML_COLOR color) throws TagNameAlreadyExistsException, TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
if (uniqueTagNames.containsKey(displayName)) {
throw new TagNameAlreadyExistsException();
}
// Add the tag name to the case.
TagName newTagName = tskCase.addTagName(displayName, description, color);
TagName newTagName = tskCase.addTagName(displayName, description, color);
// Add the tag name to the tags settings.
uniqueTagNames.put(newTagName.getDisplayName(), newTagName);
saveTagNamesToTagsSettings();
saveTagNamesToTagsSettings();
return newTagName;
}
/**
* Tags a content object.
* @param [in] content The content to tag.
* @param [in] tagName The name to use for the tag.
* @return A ContentTag data transfer object (DTO) representing the new tag.
* @throws TskCoreException
*
* @param content The content to tag.
* @param tagName The name to use for the tag.
*
* @return A ContentTag data transfer object (DTO) representing the new tag.
*
* @throws TskCoreException
*/
public ContentTag addContentTag(Content content, TagName tagName) throws TskCoreException {
return addContentTag(content, tagName, "", -1, -1);
}
}
/**
* Tags a content object.
* @param [in] content The content to tag.
* @param [in] tagName The name to use for the tag.
* @param [in] comment A comment to store with the tag.
* @return A ContentTag data transfer object (DTO) representing the new tag.
* @throws TskCoreException
*
* @param content The content to tag.
* @param tagName The name to use for the tag.
* @param comment A comment to store with the tag.
*
* @return A ContentTag data transfer object (DTO) representing the new tag.
*
* @throws TskCoreException
*/
public ContentTag addContentTag(Content content, TagName tagName, String comment) throws TskCoreException {
return addContentTag(content, tagName, comment, -1, -1);
}
}
/**
* Tags a content object or a section of a content object.
* @param [in] content The content to tag.
* @param [in] tagName The name to use for the tag.
* @param [in] comment A comment to store with the tag.
* @param [in] beginByteOffset Designates the beginning of a tagged section.
* @param [in] endByteOffset Designates the end of a tagged section.
* @return A ContentTag data transfer object (DTO) representing the new tag.
* @throws IllegalArgumentException, TskCoreException
*
* @param content The content to tag.
* @param tagName The name to use for the tag.
* @param comment A comment to store with the tag.
* @param beginByteOffset Designates the beginning of a tagged section.
* @param endByteOffset Designates the end of a tagged section.
*
* @return A ContentTag data transfer object (DTO) representing the new tag.
*
* @throws IllegalArgumentException, TskCoreException
*/
public synchronized ContentTag addContentTag(Content content, TagName tagName, String comment, long beginByteOffset, long endByteOffset) throws IllegalArgumentException, TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
if (beginByteOffset >= 0 && endByteOffset >= 1) {
if (beginByteOffset > content.getSize() - 1) {
throw new IllegalArgumentException(NbBundle.getMessage(this.getClass(),
"TagsManager.addContentTag.exception.beginByteOffsetOOR.msg",
beginByteOffset, content.getSize() - 1));
"TagsManager.addContentTag.exception.beginByteOffsetOOR.msg",
beginByteOffset, content.getSize() - 1));
}
if (endByteOffset > content.getSize() - 1) {
throw new IllegalArgumentException(
NbBundle.getMessage(this.getClass(), "TagsManager.addContentTag.exception.endByteOffsetOOR.msg",
endByteOffset, content.getSize() - 1));
endByteOffset, content.getSize() - 1));
}
if (endByteOffset < beginByteOffset) {
@@ -216,28 +247,42 @@ public class TagsManager implements Closeable {
NbBundle.getMessage(this.getClass(), "TagsManager.addContentTag.exception.endLTbegin.msg"));
}
}
return tskCase.addContentTag(content, tagName, comment, beginByteOffset, endByteOffset);
final ContentTag newContentTag = tskCase.addContentTag(content, tagName, comment, beginByteOffset, endByteOffset);
try {
Case.getCurrentCase().notifyContentTagAdded(newContentTag);
} catch (IllegalStateException ex) {
Logger.getLogger(TagsManager.class.getName()).log(Level.WARNING, NbBundle.getMessage(TagsManager.class, "TagsManager.addContentTag.noCaseWarning"));
}
return newContentTag;
}
/**
* Deletes a content tag.
* @param [in] tag The tag to delete.
* @throws TskCoreException
*
* @param tag The tag to delete.
*
* @throws TskCoreException
*/
public synchronized void deleteContentTag(ContentTag tag) throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
tskCase.deleteContentTag(tag);
try {
Case.getCurrentCase().notifyContentTagDeleted(tag);
} catch (IllegalArgumentException e) {
Logger.getLogger(TagsManager.class.getName()).log(Level.WARNING, NbBundle.getMessage(TagsManager.class, "TagsManager.deleteContentTag.noCaseWarning"));
}
}
/**
* Gets all content tags for the current case.
*
* @return A list, possibly empty, of content tags.
* @throws TskCoreException
*
* @throws TskCoreException
*/
public List<ContentTag> getAllContentTags() throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
@@ -245,100 +290,134 @@ public class TagsManager implements Closeable {
getExistingTagNames();
}
return tskCase.getAllContentTags();
return tskCase.getAllContentTags();
}
/**
* Gets content tags count by tag name.
* @param [in] tagName The tag name of interest.
*
* @param tagName The tag name of interest.
*
* @return A count of the content tags with the specified tag name.
* @throws TskCoreException
*
* @throws TskCoreException
*/
public synchronized long getContentTagsCountByTagName(TagName tagName) throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
return tskCase.getContentTagsCountByTagName(tagName);
return tskCase.getContentTagsCountByTagName(tagName);
}
/**
* Gets content tags by tag name.
* @param [in] tagName The tag name of interest.
* @return A list, possibly empty, of the content tags with the specified tag name.
* @throws TskCoreException
*
* @param tagName The tag name of interest.
*
* @return A list, possibly empty, of the content tags with the specified
* tag name.
*
* @throws TskCoreException
*/
public synchronized List<ContentTag> getContentTagsByTagName(TagName tagName) throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
return tskCase.getContentTagsByTagName(tagName);
return tskCase.getContentTagsByTagName(tagName);
}
/**
* Gets content tags count by content.
* @param [in] content The content of interest.
* @return A list, possibly empty, of the tags that have been applied to the artifact.
* @throws TskCoreException
*
* @param content The content of interest.
*
* @return A list, possibly empty, of the tags that have been applied to the
* artifact.
*
* @throws TskCoreException
*/
public synchronized List<ContentTag> getContentTagsByContent(Content content) throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
return tskCase.getContentTagsByContent(content);
return tskCase.getContentTagsByContent(content);
}
/**
* Tags a blackboard artifact object.
* @param [in] artifact The blackboard artifact to tag.
* @param [in] tagName The name to use for the tag.
* @return A BlackboardArtifactTag data transfer object (DTO) representing the new tag.
* @throws TskCoreException
*
* @param artifact The blackboard artifact to tag.
* @param tagName The name to use for the tag.
*
* @return A BlackboardArtifactTag data transfer object (DTO) representing
* the new tag.
*
* @throws TskCoreException
*/
public BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName) throws TskCoreException {
return addBlackboardArtifactTag(artifact, tagName, "");
return addBlackboardArtifactTag(artifact, tagName, "");
}
/**
* Tags a blackboard artifact object.
* @param [in] artifact The blackboard artifact to tag.
* @param [in] tagName The name to use for the tag.
* @param [in] comment A comment to store with the tag.
* @return A BlackboardArtifactTag data transfer object (DTO) representing the new tag.
* @throws TskCoreException
*
* @param artifact The blackboard artifact to tag.
* @param tagName The name to use for the tag.
* @param comment A comment to store with the tag.
*
* @return A BlackboardArtifactTag data transfer object (DTO) representing
* the new tag.
*
* @throws TskCoreException
*/
public synchronized BlackboardArtifactTag addBlackboardArtifactTag(BlackboardArtifact artifact, TagName tagName, String comment) throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
return tskCase.addBlackboardArtifactTag(artifact, tagName, comment);
BlackboardArtifactTag addBlackboardArtifactTag = tskCase.addBlackboardArtifactTag(artifact, tagName, comment);
try {
Case.getCurrentCase().notifyBlackBoardArtifactTagAdded(addBlackboardArtifactTag);
} catch (IllegalArgumentException e) {
Logger.getLogger(TagsManager.class.getName()).log(Level.WARNING, NbBundle.getMessage(TagsManager.class, "TagsManager.addBlackboardArtifactTag.noCaseWarning"));
}
return addBlackboardArtifactTag;
}
/**
* Deletes a blackboard artifact tag.
* @param [in] tag The tag to delete.
* @throws TskCoreException
*
* @param tag The tag to delete.
*
* @throws TskCoreException
*/
public synchronized void deleteBlackboardArtifactTag(BlackboardArtifactTag tag) throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
tskCase.deleteBlackboardArtifactTag(tag);
try {
Case.getCurrentCase().notifyBlackBoardArtifactTagDeleted(tag);
} catch (IllegalArgumentException e) {
Logger.getLogger(TagsManager.class.getName()).log(Level.WARNING, NbBundle.getMessage(TagsManager.class, "TagsManager.deleteBlackboardArtifactTag.noCaseWarning"));
}
}
/**
* Gets all blackboard artifact tags for the current case.
*
* @return A list, possibly empty, of blackboard artifact tags.
* @throws TskCoreException
*
* @throws TskCoreException
*/
public List<BlackboardArtifactTag> getAllBlackboardArtifactTags() throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
@@ -346,100 +425,110 @@ public class TagsManager implements Closeable {
getExistingTagNames();
}
return tskCase.getAllBlackboardArtifactTags();
return tskCase.getAllBlackboardArtifactTags();
}
/**
* Gets blackboard artifact tags count by tag name.
* @param [in] tagName The tag name of interest.
* @return A count of the blackboard artifact tags with the specified tag name.
* @throws TskCoreException
*
* @param tagName The tag name of interest.
*
* @return A count of the blackboard artifact tags with the specified tag
* name.
*
* @throws TskCoreException
*/
public synchronized long getBlackboardArtifactTagsCountByTagName(TagName tagName) throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
return tskCase.getBlackboardArtifactTagsCountByTagName(tagName);
return tskCase.getBlackboardArtifactTagsCountByTagName(tagName);
}
/**
* Gets blackboard artifact tags by tag name.
* @param [in] tagName The tag name of interest.
* @return A list, possibly empty, of the blackboard artifact tags with the specified tag name.
* @throws TskCoreException
*
* @param tagName The tag name of interest.
*
* @return A list, possibly empty, of the blackboard artifact tags with the
* specified tag name.
*
* @throws TskCoreException
*/
public synchronized List<BlackboardArtifactTag> getBlackboardArtifactTagsByTagName(TagName tagName) throws TskCoreException {
public synchronized List<BlackboardArtifactTag> getBlackboardArtifactTagsByTagName(TagName tagName) throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
return tskCase.getBlackboardArtifactTagsByTagName(tagName);
return tskCase.getBlackboardArtifactTagsByTagName(tagName);
}
/**
* Gets blackboard artifact tags for a particular blackboard artifact.
* @param [in] artifact The blackboard artifact of interest.
* @return A list, possibly empty, of the tags that have been applied to the artifact.
* @throws TskCoreException
*
* @param artifact The blackboard artifact of interest.
*
* @return A list, possibly empty, of the tags that have been applied to the
* artifact.
*
* @throws TskCoreException
*/
public synchronized List<BlackboardArtifactTag> getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact) throws TskCoreException {
public synchronized List<BlackboardArtifactTag> getBlackboardArtifactTagsByArtifact(BlackboardArtifact artifact) throws TskCoreException {
// @@@ This is a work around to be removed when database access on the EDT is correctly synchronized.
if (!tagNamesInitialized) {
getExistingTagNames();
}
return tskCase.getBlackboardArtifactTagsByArtifact(artifact);
return tskCase.getBlackboardArtifactTagsByArtifact(artifact);
}
@Override
public void close() throws IOException {
saveTagNamesToTagsSettings();
}
public void close() throws IOException {
saveTagNamesToTagsSettings();
}
private void getExistingTagNames() {
getTagNamesFromCurrentCase();
getTagNamesFromTagsSettings();
getPredefinedTagNames();
saveTagNamesToTagsSettings();
saveTagNamesToTagsSettings();
tagNamesInitialized = true; // @@@ This is part of a work around to be removed when database access on the EDT is correctly synchronized.
}
private void getTagNamesFromCurrentCase() {
try {
List<TagName> currentTagNames = tskCase.getAllTagNames();
for (TagName tagName : currentTagNames) {
uniqueTagNames.put(tagName.getDisplayName(), tagName);
}
}
catch (TskCoreException ex) {
} catch (TskCoreException ex) {
Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag types from the current case", ex); //NON-NLS
}
}
}
private void getTagNamesFromTagsSettings() {
String setting = ModuleSettings.getConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY);
if (null != setting && !setting.isEmpty()) {
if (null != setting && !setting.isEmpty()) {
// Read the tag name setting and break it into tag name tuples.
List<String> tagNameTuples = Arrays.asList(setting.split(";"));
List<String> tagNameTuples = Arrays.asList(setting.split(";"));
// Parse each tuple and add the tag names to the current case, one
// at a time to gracefully discard any duplicates or corrupt tuples.
for (String tagNameTuple : tagNameTuples) {
for (String tagNameTuple : tagNameTuples) {
String[] tagNameAttributes = tagNameTuple.split(",");
if (!uniqueTagNames.containsKey(tagNameAttributes[0])) {
if (!uniqueTagNames.containsKey(tagNameAttributes[0])) {
try {
TagName tagName = tskCase.addTagName(tagNameAttributes[0], tagNameAttributes[1], TagName.HTML_COLOR.getColorByName(tagNameAttributes[2]));
uniqueTagNames.put(tagName.getDisplayName(), tagName);
}
catch (TskCoreException ex) {
} catch (TskCoreException ex) {
Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to add saved tag name " + tagNameAttributes[0], ex); //NON-NLS
}
}
}
}
}
}
private void getPredefinedTagNames() {
@@ -448,13 +537,12 @@ public class TagsManager implements Closeable {
TagName tagName = tskCase.addTagName(
NbBundle.getMessage(this.getClass(), "TagsManager.predefTagNames.bookmark.text"), "", TagName.HTML_COLOR.NONE);
uniqueTagNames.put(tagName.getDisplayName(), tagName);
}
catch (TskCoreException ex) {
} catch (TskCoreException ex) {
Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to add predefined 'Bookmark' tag name", ex); //NON-NLS
}
}
}
}
private void saveTagNamesToTagsSettings() {
if (!uniqueTagNames.isEmpty()) {
StringBuilder setting = new StringBuilder();
@@ -466,7 +554,7 @@ public class TagsManager implements Closeable {
setting.append(tagName.getDescription()).append(",");
setting.append(tagName.getColor().name());
}
ModuleSettings.setConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY, setting.toString());
ModuleSettings.setConfigSetting(TAGS_SETTINGS_NAME, TAG_NAMES_SETTING_KEY, setting.toString());
}
}
}
}
@@ -147,3 +147,7 @@ AutopsyOptionsPanel.jLabelNumThreads.text=Number of threads to use for file inge
FXVideoPanel.progress.bufferingCancelled=media buffering was canceled
FXVideoPanel.progress.bufferingInterrupted=media buffering was interrupted
FXVideoPanel.progress.errorWritingVideoToDisk=Error writing video to disk
DataContentViewerHex.goToOffsetLabel.text=Jump to Offset
DataContentViewerHex.goToOffsetTextField.text=
DataContentViewerHex.goToOffsetTextField.msgDlg=Invalid Offset: {0}
DataContentViewerHex.setDataView.invalidOffset.negativeOffsetValue=Cannot jump to the resultant offset
View File
@@ -88,9 +88,13 @@
<Component id="goToPageLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="goToPageTextField" min="-2" pref="79" max="-2" attributes="0"/>
<EmptySpace pref="205" max="32767" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Component id="goToOffsetLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="goToOffsetTextField" min="-2" pref="79" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
<Component id="jScrollPane1" alignment="0" pref="622" max="32767" attributes="0"/>
<Component id="jScrollPane1" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
@@ -108,9 +112,11 @@
<Component id="prevPageButton" alignment="0" min="-2" pref="23" max="-2" attributes="0"/>
<Component id="goToPageLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="goToPageTextField" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="goToOffsetLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="goToOffsetTextField" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
<Component id="jScrollPane1" pref="388" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jScrollPane1" pref="382" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
@@ -131,6 +137,9 @@
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
<Font name="Courier New" size="11" style="0"/>
</Property>
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
<Color id="Default Cursor"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[700, 20]"/>
</Property>
@@ -140,7 +149,7 @@
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new JTextPane(){&#xd; public boolean getScrollableTracksViewportWidth() {&#xd; return (getSize().width &lt; 400);&#xd; }};"/>
<AuxValue name="JavaCodeGenerator_CreateCodePost" type="java.lang.String" value="this.outputViewPane.setBackground(new java.awt.Color(255, 255, 255)); // to make sure the background color is white"/>
<AuxValue name="JavaCodeGenerator_CreateCodePost" type="java.lang.String" value="this.outputViewPane.setBackground(new java.awt.Color(255, 255, 255)); // to make sure the background color is white&#xd;&#xa;this.outputViewPane.requestFocusInWindow();&#xd;&#xa;this.outputViewPane.setCursor(Cursor.getDefaultCursor());&#xd;&#xa;"/>
</AuxValues>
</Component>
</SubComponents>
@@ -278,6 +287,23 @@
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="goToOffsetLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataContentViewerHex.goToOffsetLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JTextField" name="goToOffsetTextField">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="DataContentViewerHex.goToOffsetTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="goToOffsetTextFieldActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Container>
</SubComponents>
@@ -28,6 +28,8 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Utilities;
import org.openide.nodes.Node;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
@@ -94,6 +96,8 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
return (getSize().width < 400);
}};
this.outputViewPane.setBackground(new java.awt.Color(255, 255, 255)); // to make sure the background color is white
this.outputViewPane.requestFocusInWindow();
this.outputViewPane.setCursor(Cursor.getDefaultCursor());
totalPageLabel = new javax.swing.JLabel();
ofLabel = new javax.swing.JLabel();
currentPageLabel = new javax.swing.JLabel();
@@ -103,6 +107,8 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
pageLabel2 = new javax.swing.JLabel();
goToPageTextField = new javax.swing.JTextField();
goToPageLabel = new javax.swing.JLabel();
goToOffsetLabel = new javax.swing.JLabel();
goToOffsetTextField = new javax.swing.JTextField();
copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.copyMenuItem.text")); // NOI18N
rightClickMenu.add(copyMenuItem);
@@ -113,7 +119,8 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
jScrollPane1.setBackground(new java.awt.Color(255, 255, 255));
outputViewPane.setEditable(false);
outputViewPane.setFont(new Font("Courier New", Font.PLAIN, 11)); // NOI18N NON-NLS
outputViewPane.setFont(new java.awt.Font("Courier New", 0, 11)); // NOI18N
outputViewPane.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
outputViewPane.setMinimumSize(new java.awt.Dimension(700, 20));
outputViewPane.setPreferredSize(new java.awt.Dimension(700, 400));
jScrollPane1.setViewportView(outputViewPane);
@@ -132,28 +139,28 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
pageLabel.setMinimumSize(new java.awt.Dimension(33, 14));
pageLabel.setPreferredSize(new java.awt.Dimension(33, 14));
prevPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N NON-NLS
prevPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N
prevPageButton.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.prevPageButton.text")); // NOI18N
prevPageButton.setBorderPainted(false);
prevPageButton.setContentAreaFilled(false);
prevPageButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N NON-NLS
prevPageButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
prevPageButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
prevPageButton.setPreferredSize(new java.awt.Dimension(23, 23));
prevPageButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N NON-NLS
prevPageButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N
prevPageButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
prevPageButtonActionPerformed(evt);
}
});
nextPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N NON-NLS
nextPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N
nextPageButton.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.nextPageButton.text")); // NOI18N
nextPageButton.setBorderPainted(false);
nextPageButton.setContentAreaFilled(false);
nextPageButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N NON-NLS
nextPageButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
nextPageButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
nextPageButton.setPreferredSize(new java.awt.Dimension(23, 23));
nextPageButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N NON-NLS
nextPageButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N
nextPageButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
nextPageButtonActionPerformed(evt);
@@ -174,6 +181,15 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
goToPageLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.goToPageLabel.text")); // NOI18N
goToOffsetLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.goToOffsetLabel.text")); // NOI18N
goToOffsetTextField.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.goToOffsetTextField.text")); // NOI18N
goToOffsetTextField.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
goToOffsetTextFieldActionPerformed(evt);
}
});
javax.swing.GroupLayout hexViewerPanelLayout = new javax.swing.GroupLayout(hexViewerPanel);
hexViewerPanel.setLayout(hexViewerPanelLayout);
hexViewerPanelLayout.setHorizontalGroup(
@@ -197,8 +213,12 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
.addComponent(goToPageLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(goToPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(205, Short.MAX_VALUE))
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 622, Short.MAX_VALUE)
.addGap(18, 18, 18)
.addComponent(goToOffsetLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(goToOffsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(jScrollPane1)
);
hexViewerPanelLayout.setVerticalGroup(
hexViewerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -213,9 +233,11 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
.addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(goToPageLabel)
.addComponent(goToPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(0, 0, 0)
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 388, Short.MAX_VALUE))
.addComponent(goToPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(goToOffsetLabel)
.addComponent(goToOffsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 382, Short.MAX_VALUE))
);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
@@ -241,11 +263,13 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
}// </editor-fold>//GEN-END:initComponents
private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed
setDataView(currentPage - 1);
setDataViewByPageNumber(currentPage - 1);
goToPageTextField.setText(Integer.toString(currentPage));
}//GEN-LAST:event_prevPageButtonActionPerformed
private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed
setDataView(currentPage + 1);
setDataViewByPageNumber(currentPage + 1);
goToPageTextField.setText(Integer.toString(currentPage));
}//GEN-LAST:event_nextPageButtonActionPerformed
private void goToPageTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_goToPageTextFieldActionPerformed
@@ -267,11 +291,59 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
JOptionPane.WARNING_MESSAGE);
return;
}
setDataView(pageNumber);
setDataViewByPageNumber(pageNumber);
}//GEN-LAST:event_goToPageTextFieldActionPerformed
/***
* Calculates the offset relative to the current caret position.
* @param userInput the user provided signed offset value.
* @return returns the resultant offset value relative to the current caret
* position. -1L is returned if the resultant offset cannot be calculated.
*/
private long getOffsetRelativeToCaretPosition(Long userInput) {
String userSelectedLine;
try {
// get the selected line. Extract the current hex offset location.
userSelectedLine = outputViewPane.getText().subSequence(
Utilities.getRowStart(outputViewPane, outputViewPane.getCaretPosition()),
Utilities.getRowEnd(outputViewPane, outputViewPane.getCaretPosition()))
.toString();
// NOTE: This needs to change if the outputFormat of outputViewPane changes.
String hexForUserSelectedLine = userSelectedLine.substring(0, userSelectedLine.indexOf(":"));
return Long.decode(hexForUserSelectedLine) + userInput;
} catch (BadLocationException | StringIndexOutOfBoundsException | NumberFormatException ex) {
// thrown in case the caret location is out of the range of the outputViewPane.
return -1L;
}
}
private void goToOffsetTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_goToOffsetTextFieldActionPerformed
long offset;
try {
if (goToOffsetTextField.getText().startsWith("+") || goToOffsetTextField.getText().startsWith("-")) {
offset = getOffsetRelativeToCaretPosition(Long.decode(goToOffsetTextField.getText()));
} else {
offset = Long.decode(goToOffsetTextField.getText());
}
} catch (NumberFormatException ex) {
// notify the user and return
JOptionPane.showMessageDialog(this, NbBundle.getMessage(this.getClass(), "DataContentViewerHex.goToOffsetTextField.msgDlg", goToOffsetTextField.getText()));
return;
}
if (offset >= 0) {
setDataViewByOffset(offset);
} else {
outputViewPane.setText(NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.setDataView.invalidOffset.negativeOffsetValue"));
}
}//GEN-LAST:event_goToOffsetTextFieldActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JMenuItem copyMenuItem;
private javax.swing.JLabel currentPageLabel;
private javax.swing.JLabel goToOffsetLabel;
private javax.swing.JTextField goToOffsetTextField;
private javax.swing.JLabel goToPageLabel;
private javax.swing.JTextField goToPageTextField;
private javax.swing.JPanel hexViewerPanel;
@@ -289,26 +361,41 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
/**
* Sets the DataView (The tabbed panel)
* Sets the DataView (The tabbed panel) by page number
*
* @param page Page to display (1-based counting)
*/
private void setDataView(int page) {
private void setDataViewByPageNumber(int page) {
if (this.dataSource == null) {
return;
}
if (page == 0) {
return;
}
currentPage = page;
long offset = (currentPage - 1) * pageLength;
setDataView(offset);
goToOffsetTextField.setText(Long.toString(offset));
}
/**
* Sets the DataView (The tabbed panel) by offset
*
* @param page Page to display (1-based counting)
*/
private void setDataViewByOffset(long offset) {
if (this.dataSource == null) {
return;
}
currentPage = (int) (offset / pageLength) + 1;
setDataView(offset);
goToPageTextField.setText(Integer.toString(currentPage));
}
private void setDataView(long offset) {
// change the cursor to "waiting cursor" for this operation
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
String errorText = null;
String errorText = null;
int bytesRead = 0;
if (dataSource.getSize() > 0) {
@@ -316,7 +403,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
bytesRead = dataSource.read(data, offset, pageLength); // read the data
} catch (TskException ex) {
errorText = NbBundle.getMessage(this.getClass(), "DataContentViewerHex.setDataView.errorText", offset,
offset + pageLength);
offset + pageLength);
logger.log(Level.WARNING, "Error while trying to show the hex content.", ex); //NON-NLS
}
}
@@ -324,25 +411,22 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
// set the data on the bottom and show it
if (bytesRead <= 0) {
errorText = NbBundle.getMessage(this.getClass(), "DataContentViewerHex.setDataView.errorText", offset,
offset + pageLength);
offset + pageLength);
}
// disable or enable the next button
if ((errorText == null) && (currentPage < totalPages)) {
nextPageButton.setEnabled(true);
}
else {
} else {
nextPageButton.setEnabled(false);
}
if ((errorText == null) && (currentPage > 1)) {
prevPageButton.setEnabled(true);
}
else {
} else {
prevPageButton.setEnabled(false);
}
currentPageLabel.setText(Integer.toString(currentPage));
setComponentsVisibility(true); // shows the components that not needed
@@ -350,8 +434,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
if (errorText == null) {
int showLength = bytesRead < pageLength ? bytesRead : (int) pageLength;
outputViewPane.setText(DataConversion.byteArrayToHex(data, showLength, offset));
}
else {
} else {
outputViewPane.setText(errorText);
}
@@ -379,7 +462,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
}
totalPageLabel.setText(Integer.toString(totalPages));
this.setDataView(1);
this.setDataViewByPageNumber(1);
}
@Override
@@ -423,6 +506,8 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
pageLabel2.setVisible(isVisible);
goToPageTextField.setVisible(isVisible);
goToPageLabel.setVisible(isVisible);
goToOffsetTextField.setVisible(isVisible);
goToOffsetLabel.setVisible(isVisible);
}
@Override
@@ -79,7 +79,7 @@ public final class Logger extends java.util.logging.Logger {
+ record.getSourceMethodName() + "\n"
+ record.getLevel() + ": "
+ this.formatMessage(record) + "\n"
+ record.getThrown().toString() + ": "
+ record.getThrown().toString() + ":\n"
+ StackTrace
+ "\n";
} else {
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -20,8 +20,10 @@ package org.sleuthkit.autopsy.coreutils;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import javax.swing.Icon;
@@ -52,8 +54,8 @@ public class MessageNotifyUtil {
INFO(NotifyDescriptor.INFORMATION_MESSAGE, "info-icon-16.png"), //NON-NLS
ERROR(NotifyDescriptor.ERROR_MESSAGE, "error-icon-16.png"), //NON-NLS
WARNING(NotifyDescriptor.WARNING_MESSAGE, "warning-icon-16.png"); //NON-NLS
private int notifyDescriptorType;
private Icon icon;
private final int notifyDescriptorType;
private final Icon icon;
private MessageType(int notifyDescriptorType, String resourceName) {
this.notifyDescriptorType = notifyDescriptorType;
@@ -138,24 +140,25 @@ public class MessageNotifyUtil {
}
/**
* Utility to display notifications with baloons
* Utility to display notifications with balloons
*/
public static class Notify {
private static final SimpleDateFormat TIME_STAMP_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss z");
//notifications to keep track of and to reset when case is closed
private static final List<Notification> notifications = Collections.synchronizedList(new ArrayList<Notification>());
private Notify() {
}
/**
* Clear pending notifications
* Should really only be used by Case
* Clear pending notifications Should really only be used by Case
*/
public static void clear() {
for (Notification n : notifications) {
notifications.stream().forEach((n) -> {
n.clear();
}
});
notifications.clear();
}
@@ -163,8 +166,8 @@ public class MessageNotifyUtil {
* Show message with the specified type and action listener
*/
public static void show(String title, String message, MessageType type, ActionListener actionListener) {
Notification newNotification =
NotificationDisplayer.getDefault().notify(title, type.getIcon(), message, actionListener);
Notification newNotification
= NotificationDisplayer.getDefault().notify(addTimeStampToTitle(title), type.getIcon(), message, actionListener);
notifications.add(newNotification);
}
@@ -178,11 +181,8 @@ public class MessageNotifyUtil {
* @param type type of the message
*/
public static void show(String title, final String message, final MessageType type) {
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
MessageNotifyUtil.Message.show(message, type);
}
ActionListener actionListener = (ActionEvent e) -> {
MessageNotifyUtil.Message.show(message, type);
};
show(title, message, type, actionListener);
@@ -217,5 +217,17 @@ public class MessageNotifyUtil {
public static void warn(String title, String message) {
show(title, message, MessageType.WARNING);
}
/**
* Adds a time stamp prefix to the title of notifications so that they
* will be in order (they are sorted alphabetically) in the
* notifications area.
*
* @param title A notification title without a time stamp prefix.
* @return The notification title with a time stamp prefix.
*/
private static String addTimeStampToTitle(String title) {
return TIME_STAMP_FORMAT.format(new Date()) + " " + title;
}
}
}
@@ -176,11 +176,7 @@ abstract class AbstractContentChildren<T> extends Keys<T> {
@Override
public AbstractNode visit(DataSources i) {
try {
return new DataSourcesNode(Case.getCurrentCase().getDataSources());
} catch (TskCoreException ex) {
return defaultVisit(i);
}
return new DataSourcesNode();
}
@Override
@@ -18,46 +18,38 @@
*/
package org.sleuthkit.autopsy.datamodel;
interface AutopsyItemVisitor<T> {
T visit(ExtractedContent ec);
/**
* This visitor goes over the AutopsyVisitableItems, which are currently
* the nodes in the tree that are structural and not nodes that are from
* Sleuthkit-based data model objects.
*/
interface AutopsyItemVisitor<T> {
T visit(DataSources i);
T visit(Views v);
T visit(FileTypeExtensionFilters sf);
T visit(FileTypeExtensionFilters.RootFilter fsf);
T visit(FileTypeExtensionFilters.DocumentFilter df);
T visit(FileTypeExtensionFilters.ExecutableFilter ef);
T visit(RecentFiles rf);
T visit(RecentFiles.RecentFilesFilter rff);
T visit(DeletedContent dc);
T visit(DeletedContent.DeletedContentFilter dcf);
T visit(FileSize fs);
T visit(FileSize.FileSizeFilter fsf);
T visit(ExtractedContent ec);
T visit(KeywordHits kh);
T visit(HashsetHits hh);
T visit(EmailExtracted ee);
T visit(InterestingHits ih);
T visit(Results r);
T visit(Tags tagsNodeKey);
T visit(InterestingHits ih);
T visit(DataSources i);
T visit(Views v);
T visit(Results r);
T visit(Reports reportsItem);
static abstract public class Default<T> implements AutopsyItemVisitor<T> {
@@ -19,7 +19,9 @@
package org.sleuthkit.autopsy.datamodel;
/**
* Abstraction for certain top level items displayed in the GUI
* AutopsyVisitableItems are the nodes in the directory tree that
* are for structure only. They are not associated with content
* objects.
*/
interface AutopsyVisitableItem {
@@ -22,12 +22,14 @@ import org.openide.nodes.Children;
import org.openide.util.Lookup;
/**
* Interface class that all Data nodes inherit from.
* Content Nodes are for the content objects in the data
* source area of the tree. Other parts of the directory
* tree do not extend this.
*
* Provides basic information such as ID, parent ID, etc.
*/
abstract class ContentNode extends DisplayableItemNode{
abstract class ContentNode extends DisplayableItemNode {
public ContentNode(Children children) {
super(children);
}
@@ -19,24 +19,24 @@
package org.sleuthkit.autopsy.datamodel;
/**
* Interface for visitor pattern on ContentNodes
* Visitor Pattern interface that goes over Content nodes
* in the data source area of the tree.
*
* The DisplayableItemNodeVisitor goes over all nodes in the tree.
*
* @param <T> visit method return type
*/
interface ContentNodeVisitor<T> {
T visit(DirectoryNode dn);
T visit(FileNode fn);
T visit(ImageNode in);
T visit(VirtualDirectoryNode lcn);
T visit(VolumeNode vn);
T visit(LayoutFileNode lcn);
T visit(DirectoryNode dn);
T visit(FileNode fn);
T visit(LayoutFileNode lcn);
T visit(LocalFileNode dfn);
T visit(VirtualDirectoryNode lcn);
/**
* Visitor with an implementable default behavior for all types. Override
@@ -20,9 +20,9 @@ package org.sleuthkit.autopsy.datamodel;
/**
* Images nodes support
* Root node to store the data sources in a case
*/
public class DataSources implements AutopsyVisitableItem{
public class DataSources implements AutopsyVisitableItem {
public DataSources() {
}
@@ -18,12 +18,18 @@
*/
package org.sleuthkit.autopsy.datamodel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Nodes for the images
@@ -32,13 +38,84 @@ public class DataSourcesNode extends DisplayableItemNode {
public static final String NAME = NbBundle.getMessage(DataSourcesNode.class, "DataSourcesNode.name");
// NOTE: The images passed in via argument will be ignored.
@Deprecated
public DataSourcesNode(List<Content> images) {
super(new RootContentChildren(images), Lookups.singleton(NAME));
super(new DataSourcesNodeChildren(), Lookups.singleton(NAME));
init();
}
public DataSourcesNode() {
super(new DataSourcesNodeChildren(), Lookups.singleton(NAME));
init();
}
private void init() {
setName(NAME);
setDisplayName(NAME);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/image.png"); //NON-NLS
}
/* Custom Keys implementation that listens for new data sources being added. */
// @@@ This can become private once DirectoryTree doesn't want to refresh the entire tree
public static class DataSourcesNodeChildren extends AbstractContentChildren<Content> {
private static final Logger logger = Logger.getLogger(DataSourcesNodeChildren.class.getName());
List<Content> currentKeys;
public DataSourcesNodeChildren() {
super();
this.currentKeys = new ArrayList<>();
}
private final PropertyChangeListener pcl = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
reloadKeys();
}
}
};
@Override
protected void addNotify() {
Case.addPropertyChangeListener(pcl);
reloadKeys();
}
@Override
protected void removeNotify() {
Case.removePropertyChangeListener(pcl);
currentKeys.clear();
setKeys(Collections.<Content>emptySet());
}
private void reloadKeys() {
try {
currentKeys = Case.getCurrentCase().getDataSources();
setKeys(currentKeys);
} catch (TskCoreException | IllegalStateException ex) {
logger.severe("Error getting data sources: " + ex.getMessage()); // NON-NLS
setKeys(Collections.<Content>emptySet());
}
}
/**
* Refresh all content keys
* This creates new nodes of keys have changed.
*/
// I think this goes away once we get more listeners in place
// It was added as an interim stage
@Deprecated
public void refreshContentKeys() {
for (Content key : currentKeys) {
refreshKey(key);
}
}
}
@Override
public boolean isLeafTypeNode() {
return false;
@@ -18,9 +18,13 @@
*/
package org.sleuthkit.autopsy.datamodel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
@@ -31,7 +35,10 @@ import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor;
@@ -105,6 +112,7 @@ public class DeletedContent implements AutopsyVisitableItem {
private static final String NAME = NbBundle.getMessage(DeletedContent.class,
"DeletedContent.deletedContentsNode.name");
private SleuthkitCase skCase;
DeletedContentsNode(SleuthkitCase skCase) {
super(Children.create(new DeletedContentsChildren(skCase), true), Lookups.singleton(NAME));
@@ -144,11 +152,64 @@ public class DeletedContent implements AutopsyVisitableItem {
public static class DeletedContentsChildren extends ChildFactory<DeletedContent.DeletedContentFilter> {
private SleuthkitCase skCase;
private Observable notifier;
// true if we have already told user that not all files will be shown
private static boolean maxFilesDialogShown = false;
public DeletedContentsChildren(SleuthkitCase skCase) {
this.skCase = skCase;
this.notifier = new DeletedContentsChildrenObservable();
}
/**
* Listens for case and ingest invest. Updates observers when events are
* fired. Other nodes are listening to this for changes.
*/
private final class DeletedContentsChildrenObservable extends Observable {
DeletedContentsChildrenObservable() {
IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl);
Case.addPropertyChangeListener(pcl);
}
private void removeListeners() {
deleteObservers();
IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
Case.removePropertyChangeListener(pcl);
}
private final PropertyChangeListener pcl = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
// new file was added
if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) {
// @@@ COULD CHECK If the new file is deleted before notifying...
update();
} else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|| eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
update();
} else if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
update();
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
// case was closed. Remove listeners so that we don't get called with a stale case handle
if (evt.getNewValue() == null) {
removeListeners();
}
maxFilesDialogShown = false;
}
}
};
private void update() {
setChanged();
notifyObservers();
}
}
@Override
protected boolean createKeys(List<DeletedContent.DeletedContentFilter> list) {
@@ -158,7 +219,7 @@ public class DeletedContent implements AutopsyVisitableItem {
@Override
protected Node createNodeForKey(DeletedContent.DeletedContentFilter key) {
return new DeletedContentNode(skCase, key);
return new DeletedContentNode(skCase, key, notifier);
}
public class DeletedContentNode extends DisplayableItemNode {
@@ -166,17 +227,41 @@ public class DeletedContent implements AutopsyVisitableItem {
private DeletedContent.DeletedContentFilter filter;
private final Logger logger = Logger.getLogger(DeletedContentNode.class.getName());
// Use version that has observer for updates
@Deprecated
DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter) {
super(Children.create(new DeletedContentChildren(filter, skCase), true), Lookups.singleton(filter.getDisplayName()));
super.setName(filter.getName());
super(Children.create(new DeletedContentChildren(filter, skCase, null), true), Lookups.singleton(filter.getDisplayName()));
this.filter = filter;
init();
}
DeletedContentNode(SleuthkitCase skCase, DeletedContent.DeletedContentFilter filter, Observable o) {
super(Children.create(new DeletedContentChildren(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName()));
this.filter = filter;
init();
o.addObserver(new DeletedContentNodeObserver());
}
private void init() {
super.setName(filter.getName());
String tooltip = filter.getDisplayName();
this.setShortDescription(tooltip);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-icon-deleted.png"); //NON-NLS
updateDisplayName();
}
// update the display name when new events are fired
private class DeletedContentNodeObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
updateDisplayName();
}
}
private void updateDisplayName() {
//get count of children without preloading all children nodes
final long count = new DeletedContentChildren(filter, skCase).calculateItems();
final long count = DeletedContentChildren.calculateItems(skCase, filter);
//final long count = getChildren().getNodesCount(true);
super.setDisplayName(filter.getDisplayName() + " (" + count + ")");
}
@@ -210,37 +295,68 @@ public class DeletedContent implements AutopsyVisitableItem {
}
}
class DeletedContentChildren extends ChildFactory<AbstractFile> {
static class DeletedContentChildren extends ChildFactory.Detachable<AbstractFile> {
private SleuthkitCase skCase;
private DeletedContent.DeletedContentFilter filter;
private final Logger logger = Logger.getLogger(DeletedContentChildren.class.getName());
private static final Logger logger = Logger.getLogger(DeletedContentChildren.class.getName());
private static final int MAX_OBJECTS = 10001;
private final Observable notifier;
DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase) {
DeletedContentChildren(DeletedContent.DeletedContentFilter filter, SleuthkitCase skCase, Observable o) {
this.skCase = skCase;
this.filter = filter;
this.notifier = o;
}
private final Observer observer = new DeletedContentChildrenObserver();
// Cause refresh of children if there are changes
private class DeletedContentChildrenObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
}
@Override
protected void addNotify() {
if (notifier != null) {
notifier.addObserver(observer);
}
}
@Override
protected void removeNotify() {
if (notifier != null) {
notifier.deleteObserver(observer);
}
}
@Override
protected boolean createKeys(List<AbstractFile> list) {
List<AbstractFile> queryList = runFsQuery();
if (queryList.size() == MAX_OBJECTS) {
queryList.remove(queryList.size() - 1);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JOptionPane.showMessageDialog(null, NbBundle.getMessage(this.getClass(),
"DeletedContent.createKeys.maxObjects.msg",
MAX_OBJECTS - 1));
}
});
// only show the dialog once - not each time we refresh
if (maxFilesDialogShown == false) {
maxFilesDialogShown = true;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), NbBundle.getMessage(this.getClass(),
"DeletedContent.createKeys.maxObjects.msg",
MAX_OBJECTS - 1));
}
});
}
}
list.addAll(queryList);
return true;
}
private String makeQuery() {
static private String makeQuery(DeletedContent.DeletedContentFilter filter) {
String query = "";
switch (filter) {
case FS_DELETED_FILTER:
@@ -280,7 +396,7 @@ public class DeletedContent implements AutopsyVisitableItem {
private List<AbstractFile> runFsQuery() {
List<AbstractFile> ret = new ArrayList<>();
String query = makeQuery();
String query = makeQuery(filter);
try {
ret = skCase.findAllFilesWhere(query);
} catch (TskCoreException e) {
@@ -296,9 +412,9 @@ public class DeletedContent implements AutopsyVisitableItem {
*
* @return
*/
long calculateItems() {
static long calculateItems(SleuthkitCase sleuthkitCase, DeletedContent.DeletedContentFilter filter) {
try {
return skCase.countFilesWhere(makeQuery());
return sleuthkitCase.countFilesWhere(makeQuery(filter));
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting deleted files search view count", ex); //NON-NLS
return 0;
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011 - 2013 Basis Technology Corp.
* Copyright 2011 - 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,10 +23,11 @@ import org.openide.nodes.Children;
import org.openide.util.Lookup;
/**
* Interface for all displayable Nodes
* A DisplayableItem is any node in the Autopsy directory tree.
* All of the nodes in the tree will eventually extend this.
* This includes the data source, views, extracted results, etc. areas.
*/
public abstract class DisplayableItemNode extends AbstractNode {
public DisplayableItemNode(Children children) {
super(children);
}
@@ -35,6 +36,6 @@ public abstract class DisplayableItemNode extends AbstractNode {
super(children, lookup);
}
abstract public boolean isLeafTypeNode();
public abstract boolean isLeafTypeNode();
public abstract <T> T accept(DisplayableItemNodeVisitor<T> v);
}
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011 - 2013 Basis Technology Corp.
* Copyright 2011 - 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,86 +24,62 @@ import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNod
import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode;
/**
* Visitor pattern implementation for DisplayableItemNodes
* Visitor pattern that goes over all nodes in the directory tree.
* This includes extracted content, reports, and the data sources area.
*/
public interface DisplayableItemNodeVisitor<T> {
/* Data Sources Area */
T visit(DataSourcesNode in);
T visit(LayoutFileNode lfn);
T visit(LocalFileNode dfn);
T visit(VirtualDirectoryNode ldn);
T visit(DirectoryNode dn);
T visit(FileNode fn);
T visit(ImageNode in);
T visit(VolumeNode vn);
T visit(BlackboardArtifactNode ban);
T visit(ExtractedContent.TypeNode atn);
T visit(ExtractedContent.RootNode ecn);
/* Views Area */
T visit(ViewsNode vn);
T visit(FileTypeNode fsfn);
T visit(DeletedContentNode dcn);
T visit(DeletedContentsNode dcn);
T visit(FileSizeRootNode fsrn);
T visit(FileSizeNode fsn);
T visit(FileTypesNode sfn);
T visit(RecentFilesNode rfn);
T visit(RecentFilesFilterNode rffn);
T visit(KeywordHits.RootNode khrn);
T visit(KeywordHits.ListNode khsn);
T visit(KeywordHits.TermNode khmln);
T visit(HashsetHits.RootNode hhrn);
T visit(HashsetHits.HashsetNameNode hhsn);
T visit(EmailExtracted.RootNode eern);
T visit(EmailExtracted.AccountNode eean);
T visit(EmailExtracted.FolderNode eefn);
T visit(Tags.RootNode node);
/* Extracted Results Area */
T visit(ResultsNode rn);
T visit(BlackboardArtifactNode ban);
T visit(ExtractedContent.TypeNode atn);
T visit(ExtractedContent.RootNode ecn);
T visit(KeywordHits.RootNode khrn);
T visit(KeywordHits.ListNode khsn);
T visit(KeywordHits.TermNode khmln);
T visit(HashsetHits.RootNode hhrn);
T visit(HashsetHits.HashsetNameNode hhsn);
T visit(EmailExtracted.RootNode eern);
T visit(EmailExtracted.AccountNode eean);
T visit(EmailExtracted.FolderNode eefn);
T visit(InterestingHits.RootNode ihrn);
T visit(InterestingHits.SetNameNode ihsn);
/* Tags */
T visit(Tags.RootNode node);
T visit(Tags.TagNameNode node);
T visit(Tags.ContentTagTypeNode node);
T visit(ContentTagNode node);
T visit(Tags.BlackboardArtifactTagTypeNode node);
T visit(BlackboardArtifactTagNode node);
T visit(ViewsNode vn);
T visit(ResultsNode rn);
T visit(DataSourcesNode in);
T visit(LayoutFileNode lfn);
T visit(LocalFileNode dfn);
T visit(VirtualDirectoryNode ldn);
/* Reports */
T visit(Reports.ReportsListNode reportsNode);
T visit(Reports.ReportNode reportNode);
/**
@@ -177,6 +177,9 @@ public class FileNode extends AbstractFsContentNode<AbstractFile> {
@Override
public boolean isLeafTypeNode() {
// This seems wrong, but it also seems that it is never called
// because the visitor to figure out if there are children or
// not will check if it has children using the Content API
return true;
}
}
@@ -18,9 +18,13 @@
*/
package org.sleuthkit.autopsy.datamodel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
@@ -29,7 +33,9 @@ import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentVisitor;
@@ -98,6 +104,7 @@ public class FileSize implements AutopsyVisitableItem {
return this.skCase;
}
/* Root node. Children are nodes for specific sizes. */
public static class FileSizeRootNode extends DisplayableItemNode {
private static final String NAME = NbBundle.getMessage(FileSize.class, "FileSize.fileSizeRootNode.name");
@@ -136,14 +143,65 @@ public class FileSize implements AutopsyVisitableItem {
}
}
/* Makes the children for specific sizes */
public static class FileSizeRootChildren extends ChildFactory<org.sleuthkit.autopsy.datamodel.FileSize.FileSizeFilter> {
private SleuthkitCase skCase;
private Observable notifier;
public FileSizeRootChildren(SleuthkitCase skCase) {
this.skCase = skCase;
notifier = new FileSizeRootChildrenObservable();
}
/**
* Listens for case and ingest invest. Updates observers when events are
* fired. Size-based nodes are listening to this for changes.
*/
private final class FileSizeRootChildrenObservable extends Observable {
FileSizeRootChildrenObservable() {
IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl);
Case.addPropertyChangeListener(pcl);
}
private void removeListeners() {
deleteObservers();
IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
Case.removePropertyChangeListener(pcl);
}
private final PropertyChangeListener pcl = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
// new file was added
if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) {
// @@@ could check the size here and only fire off updates if we know the file meets the min size criteria
update();
} else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|| eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
update();
} else if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
update();
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
// case was closed. Remove listeners so that we don't get called with a stale case handle
if (evt.getNewValue() == null) {
removeListeners();
}
}
}
};
private void update() {
setChanged();
notifyObservers();
}
}
@Override
protected boolean createKeys(List<FileSizeFilter> list) {
@@ -153,26 +211,55 @@ public class FileSize implements AutopsyVisitableItem {
@Override
protected Node createNodeForKey(FileSizeFilter key) {
return new FileSizeNode(skCase, key);
return new FileSizeNode(skCase, key, notifier);
}
/* Node for a specific size range. Children are files. */
public class FileSizeNode extends DisplayableItemNode {
private FileSizeFilter filter;
private final Logger logger = Logger.getLogger(FileSizeNode.class.getName());
// use version with observer instead so that it updates
@Deprecated
FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter) {
super(Children.create(new FileSizeChildren(filter, skCase), true), Lookups.singleton(filter.getDisplayName()));
super.setName(filter.getName());
super(Children.create(new FileSizeChildren(filter, skCase, null), true), Lookups.singleton(filter.getDisplayName()));
this.filter = filter;
init();
}
/**
*
* @param skCase
* @param filter
* @param o Observable that provides updates when events are fired
*/
FileSizeNode(SleuthkitCase skCase, FileSizeFilter filter, Observable o) {
super(Children.create(new FileSizeChildren(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName()));
this.filter = filter;
init();
o.addObserver(new FileSizeNodeObserver());
}
private void init() {
super.setName(filter.getName());
String tooltip = filter.getDisplayName();
this.setShortDescription(tooltip);
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-size-16.png"); //NON-NLS
//get count of children without preloading all children nodes
final long count = new FileSizeChildren(filter, skCase).calculateItems();
//final long count = getChildren().getNodesCount(true);
updateDisplayName();
}
// update the display name when new events are fired
private class FileSizeNodeObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
updateDisplayName();
}
}
private void updateDisplayName() {
final long count = FileSizeChildren.calculateItems(skCase, filter);
super.setDisplayName(filter.getDisplayName() + " (" + count + ")");
}
@@ -204,15 +291,49 @@ public class FileSize implements AutopsyVisitableItem {
}
}
class FileSizeChildren extends ChildFactory<AbstractFile> {
/* Makes children, which are nodes for files of a given range */
static class FileSizeChildren extends ChildFactory.Detachable<AbstractFile> {
private SleuthkitCase skCase;
private FileSizeFilter filter;
private final Logger logger = Logger.getLogger(FileSizeChildren.class.getName());
private final SleuthkitCase skCase;
private final FileSizeFilter filter;
private final Observable notifier;
private static final Logger logger = Logger.getLogger(FileSizeChildren.class.getName());
FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase) {
/**
*
* @param filter
* @param skCase
* @param o Observable that provides updates when new files are added to case
*/
FileSizeChildren(FileSizeFilter filter, SleuthkitCase skCase, Observable o) {
this.skCase = skCase;
this.filter = filter;
this.notifier = o;
}
@Override
protected void addNotify() {
if (notifier != null) {
notifier.addObserver(observer);
}
}
@Override
protected void removeNotify() {
if (notifier != null) {
notifier.deleteObserver(observer);
}
}
private final Observer observer = new FileSizeChildrenObserver();
// Cause refresh of children if there are changes
private class FileSizeChildrenObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
}
@Override
@@ -225,7 +346,7 @@ public class FileSize implements AutopsyVisitableItem {
return true;
}
private String makeQuery() {
private static String makeQuery(FileSizeFilter filter) {
String query;
switch (filter) {
case SIZE_50_200:
@@ -252,7 +373,7 @@ public class FileSize implements AutopsyVisitableItem {
private List<AbstractFile> runFsQuery() {
List<AbstractFile> ret = new ArrayList<>();
String query = makeQuery();
String query = makeQuery(filter);
if (query == null) {
return null;
}
@@ -272,9 +393,9 @@ public class FileSize implements AutopsyVisitableItem {
*
* @return
*/
long calculateItems() {
static long calculateItems(SleuthkitCase sleuthkitCase, FileSizeFilter filter) {
try {
return skCase.countFilesWhere(makeQuery());
return sleuthkitCase.countFilesWhere(makeQuery(filter));
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting files by size search view count", ex); //NON-NLS
return 0;
@@ -19,6 +19,8 @@
package org.sleuthkit.autopsy.datamodel;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
@@ -42,35 +44,58 @@ import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* Node for a specific file type / extension
* Node for a specific file type / extension. Children of it will be the files
* of that type.
*/
public class FileTypeNode extends DisplayableItemNode {
FileTypeExtensionFilters.SearchFilterInterface filter;
SleuthkitCase skCase;
// deprecated in favor of the version that takes an observable to provide refresh updates
@Deprecated
FileTypeNode(FileTypeExtensionFilters.SearchFilterInterface filter, SleuthkitCase skCase) {
super(Children.create(new FileTypeChildFactory(filter, skCase), true), Lookups.singleton(filter.getDisplayName()));
this.filter = filter;
this.skCase = skCase;
init();
}
/**
*
* @param filter Extensions that will be shown for this node
* @param skCase
* @param o Observable that sends updates when the child factories should
* refresh
*/
FileTypeNode(FileTypeExtensionFilters.SearchFilterInterface filter, SleuthkitCase skCase, Observable o) {
super(Children.create(new FileTypeChildFactory(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName()));
this.filter = filter;
this.skCase = skCase;
init();
o.addObserver(new FileTypeNodeObserver());
}
private void init() {
super.setName(filter.getName());
/* There is a bug in the below code. The count becomes inaccurate when ZIP files
* blow up and when a 2nd data source is added. I think we need to change the overall
* design of this and have a singleton class (or some other place) that will store
* the results and each level can either ask it for the count or specific ids and
* it would be responsible for listening for case events and data events.
*/
//get count of children without preloading all children nodes
final long count = new FileTypeChildFactory(filter, skCase).calculateItems();
//final long count = getChildren().getNodesCount(true);
super.setDisplayName(filter.getDisplayName() + " (" + count + ")");
updateDisplayName();
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-filter-icon.png"); //NON-NLS
}
// update the display name when new events are fired
private class FileTypeNodeObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
updateDisplayName();
}
}
private void updateDisplayName() {
final long count = FileTypeChildFactory.calculateItems(skCase, filter);
super.setDisplayName(filter.getDisplayName() + " (" + count + ")");
}
@Override
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
return v.visit(this);
@@ -110,24 +135,68 @@ public class FileTypeNode extends DisplayableItemNode {
/**
* Child node factory for a specific file type - does the database query.
*/
public static class FileTypeChildFactory extends ChildFactory<Content> {
public static class FileTypeChildFactory extends ChildFactory.Detachable<Content> {
private final SleuthkitCase skCase;
private final FileTypeExtensionFilters.SearchFilterInterface filter;
private final Logger logger = Logger.getLogger(FileTypeChildFactory.class.getName());
private final static Logger logger = Logger.getLogger(FileTypeChildFactory.class.getName());
private Observable notifier;
// use the constructor that gets an observable passed in for updates
@Deprecated
FileTypeChildFactory(FileTypeExtensionFilters.SearchFilterInterface filter, SleuthkitCase skCase) {
super();
this.filter = filter;
this.skCase = skCase;
notifier = null;
}
/**
*
* @param filter Extensions to display
* @param skCase
* @param o Observable that will notify when there could be new data to
* display
*/
FileTypeChildFactory(FileTypeExtensionFilters.SearchFilterInterface filter, SleuthkitCase skCase, Observable o) {
super();
this.filter = filter;
this.skCase = skCase;
notifier = o;
}
@Override
protected void addNotify() {
if (notifier != null) {
notifier.addObserver(observer);
}
}
@Override
protected void removeNotify() {
if (notifier != null) {
notifier.deleteObserver(observer);
}
}
private final Observer observer = new FileTypeChildFactoryObserver();
// Cause refresh of children if there are changes
private class FileTypeChildFactoryObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
refresh(true);
}
}
/**
* Get children count without actually loading all nodes
*
* @return
*/
long calculateItems() {
static long calculateItems(SleuthkitCase sleuthkitCase, FileTypeExtensionFilters.SearchFilterInterface filter) {
try {
return skCase.countFilesWhere(createQuery());
return sleuthkitCase.countFilesWhere(createQuery(filter));
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting file search view count", ex); //NON-NLS
return 0;
@@ -137,7 +206,7 @@ public class FileTypeNode extends DisplayableItemNode {
@Override
protected boolean createKeys(List<Content> list) {
try {
List<AbstractFile> files = skCase.findAllFilesWhere(createQuery());
List<AbstractFile> files = skCase.findAllFilesWhere(createQuery(filter));
list.addAll(files);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS
@@ -145,7 +214,7 @@ public class FileTypeNode extends DisplayableItemNode {
return true;
}
private String createQuery() {
private static String createQuery(FileTypeExtensionFilters.SearchFilterInterface filter) {
StringBuilder query = new StringBuilder();
query.append("(dir_type = ").append(TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue()).append(")"); //NON-NLS
if (UserPreferences.hideKnownFilesInViewsTree()) {
@@ -18,18 +18,23 @@
*/
package org.sleuthkit.autopsy.datamodel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.List;
import java.util.Observable;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.SleuthkitCase;
/**
* Node for extension/file type filter view
* Node for root of file types view. Children are nodes for specific types.
*/
public class FileTypesNode extends DisplayableItemNode {
@@ -42,7 +47,23 @@ public class FileTypesNode extends DisplayableItemNode {
* something to provide a sub-node.
*/
FileTypesNode(SleuthkitCase skCase, FileTypeExtensionFilters.RootFilter filter) {
super(Children.create(new FileTypesChildren(skCase, filter), true), Lookups.singleton(filter == null ? FNAME : filter.getName()));
super(Children.create(new FileTypesChildren(skCase, filter, null), true), Lookups.singleton(filter == null ? FNAME : filter.getName()));
init(filter);
}
/**
*
* @param skCase
* @param filter
* @param o Observable that was created by a higher-level node that provides
* updates on events
*/
private FileTypesNode(SleuthkitCase skCase, FileTypeExtensionFilters.RootFilter filter, Observable o) {
super(Children.create(new FileTypesChildren(skCase, filter, o), true), Lookups.singleton(filter == null ? FNAME : filter.getName()));
init(filter);
}
private void init(FileTypeExtensionFilters.RootFilter filter) {
// root node of tree
if (filter == null) {
super.setName(FNAME);
@@ -52,7 +73,7 @@ public class FileTypesNode extends DisplayableItemNode {
super.setName(filter.getName());
super.setDisplayName(filter.getDisplayName());
}
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file_types.png"); //NON-NLS
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file_types.png"); //NON-NLS
}
@Override
@@ -88,16 +109,71 @@ public class FileTypesNode extends DisplayableItemNode {
private SleuthkitCase skCase;
private FileTypeExtensionFilters.RootFilter filter;
private Observable notifier;
/**
*
* @param skCase
* @param filter Is null for root node
* @param o Observable that provides updates based on events being fired
* (or null if one needs to be created)
*/
public FileTypesChildren(SleuthkitCase skCase, FileTypeExtensionFilters.RootFilter filter) {
public FileTypesChildren(SleuthkitCase skCase, FileTypeExtensionFilters.RootFilter filter, Observable o) {
super();
this.skCase = skCase;
this.filter = filter;
if (o == null) {
this.notifier = new FileTypesChildrenObservable();
} else {
this.notifier = o;
}
}
/**
* Listens for case and ingest invest. Updates observers when events are
* fired. FileType and FileTypes nodes are all listening to this.
*/
private final class FileTypesChildrenObservable extends Observable {
FileTypesChildrenObservable() {
IngestManager.getInstance().addIngestJobEventListener(pcl);
IngestManager.getInstance().addIngestModuleEventListener(pcl);
Case.addPropertyChangeListener(pcl);
}
private void removeListeners() {
deleteObservers();
IngestManager.getInstance().removeIngestJobEventListener(pcl);
IngestManager.getInstance().removeIngestModuleEventListener(pcl);
Case.removePropertyChangeListener(pcl);
}
private final PropertyChangeListener pcl = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
// new file was added
if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) {
update();
} else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
|| eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
update();
} else if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
update();
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
// case was closed. Remove listeners so that we don't get called with a stale case handle
if (evt.getNewValue() == null) {
removeListeners();
}
}
}
};
private void update() {
setChanged();
notifyObservers();
}
}
@Override
@@ -105,8 +181,7 @@ public class FileTypesNode extends DisplayableItemNode {
// root node
if (filter == null) {
list.addAll(Arrays.asList(FileTypeExtensionFilters.RootFilter.values()));
}
// document and executable has another level of nodes
} // document and executable has another level of nodes
else if (filter.equals(FileTypeExtensionFilters.RootFilter.TSK_DOCUMENT_FILTER)) {
list.addAll(Arrays.asList(FileTypeExtensionFilters.DocumentFilter.values()));
} else if (filter.equals(FileTypeExtensionFilters.RootFilter.TSK_EXECUTABLE_FILTER)) {
@@ -119,11 +194,11 @@ public class FileTypesNode extends DisplayableItemNode {
protected Node createNodeForKey(FileTypeExtensionFilters.SearchFilterInterface key) {
// make new nodes for the sub-nodes
if (key.getName().equals(FileTypeExtensionFilters.RootFilter.TSK_DOCUMENT_FILTER.getName())) {
return new FileTypesNode(skCase, FileTypeExtensionFilters.RootFilter.TSK_DOCUMENT_FILTER);
return new FileTypesNode(skCase, FileTypeExtensionFilters.RootFilter.TSK_DOCUMENT_FILTER, notifier);
} else if (key.getName().equals(FileTypeExtensionFilters.RootFilter.TSK_EXECUTABLE_FILTER.getName())) {
return new FileTypesNode(skCase, FileTypeExtensionFilters.RootFilter.TSK_EXECUTABLE_FILTER);
return new FileTypesNode(skCase, FileTypeExtensionFilters.RootFilter.TSK_EXECUTABLE_FILTER, notifier);
} else {
return new FileTypeNode(key, skCase);
return new FileTypeNode(key, skCase, notifier);
}
}
}
@@ -110,6 +110,9 @@ public class LocalFileNode extends AbstractAbstractFileNode<AbstractFile> {
@Override
public boolean isLeafTypeNode() {
// This seems wrong, but it also seems that it is never called
// because the visitor to figure out if there are children or
// not will check if it has children using the Content API
return true; //!this.hasContentChildren();
}
}
@@ -23,6 +23,8 @@ import org.sleuthkit.datamodel.SleuthkitCase;
/**
* Recent files node support
* NOTE: As of june '15 we do not display this in the tree.
* It can be added back when we have filtering in the results area.
*/
class RecentFiles implements AutopsyVisitableItem {
@@ -35,8 +35,6 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
@@ -119,19 +117,14 @@ public class Tags implements AutopsyVisitableItem {
private final PropertyChangeListener pcl = new PropertyChangeListener() {
@Override
@SuppressWarnings("deprecation")
public void propertyChange(PropertyChangeEvent evt) {
String eventType = evt.getPropertyName();
if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
/* Note: this is a hack. In an ideal world, TagsManager
* would fire events so that the directory tree would
* refresh. But, we haven't had a chance to add that so, we
* fire these events and the tree refreshes based on them. */
if ((((ModuleDataEvent) evt.getOldValue()).getArtifactType() == BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT)
|| ((ModuleDataEvent) evt.getOldValue()).getArtifactType() == BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE) {
if (eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString())
|| eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString())
|| eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())
|| eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) {
refresh(true);
tagResults.update();
}
} else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
refresh(true);
tagResults.update();
@@ -36,7 +36,9 @@ public class ViewsNode extends DisplayableItemNode {
public ViewsNode(SleuthkitCase sleuthkitCase) {
super(new RootContentChildren(Arrays.asList(
new FileTypeExtensionFilters(sleuthkitCase),
new RecentFiles(sleuthkitCase),
// June '15: Recent Files was removed because it was not useful w/out filtering
// add it back in if we can filter the results to a more managable size.
// new RecentFiles(sleuthkitCase),
new DeletedContent(sleuthkitCase),
new FileSize(sleuthkitCase))),
Lookups.singleton(NAME));
@@ -69,25 +69,34 @@ class DirectoryTreeFilterChildren extends FilterNode.Children {
return new DirectoryTreeFilterNode(arg0, createChildren);
}
/* This method takes in a node as an argument and will create
* a new one if it should be displayed in the tree. If it is
* to be displayed, it also figures out if it is a leaf or not
* (i.e. should it have a + sign in the tree).
*
* It does NOT create children nodes
*/
@Override
protected Node[] createNodes(Node origNode) {
if (origNode == null || !(origNode instanceof DisplayableItemNode)) {
return new Node[]{};
}
// Shoudl this node be displayed in the tree or not
final DisplayableItemNode diNode = (DisplayableItemNode) origNode;
if (diNode.accept(showItemV) == false) {
//do not show
return new Node[]{};
}
// filter out the FileNode and the "." and ".." directories
// do not set children, if we know the node type is a leaf node type
// If it is going to be displayed, then determine if it should
// have a '+' next to it based on if it has children of itself.
// We will filter out the "." and ".." directories
final boolean isLeaf = diNode.accept(isLeafItemV);
return new Node[]{this.copyNode(origNode, !isLeaf)};
}
/**
@@ -573,7 +573,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
}
} // if the image is added to the case
else if (changed.equals(Case.Events.DATA_SOURCE_ADDED.toString())) {
componentOpened();
// we don't need to do anything in here.
// DataSourcesNode is listening for these events and updates itself
}
// change in node selection
else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) {
@@ -778,7 +779,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
Node imagesNode = imagesNodeOrig.getNode();
RootContentChildren contentRootChildren = (RootContentChildren) imagesNode.getChildren();
DataSourcesNode.DataSourcesNodeChildren contentRootChildren = (DataSourcesNode.DataSourcesNodeChildren) imagesNode.getChildren();
contentRootChildren.refreshContentKeys();
//final TreeView tree = getTree();
@@ -0,0 +1,19 @@
/*
* 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.events;
import java.beans.PropertyChangeEvent;
/**
* This is a place holder class to be overwritten by the version in the
* Collaborative branch.
*/
abstract class AutopsyEvent extends PropertyChangeEvent {
AutopsyEvent(Object source, String propertyName, Object oldValue, Object newValue) {
super(source, propertyName, oldValue, newValue);
}
}
@@ -0,0 +1,33 @@
/*
* 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.events;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
/**
*
*/
public class BlackBoardArtifactTagAddedEvent extends TagAddedEvent<BlackboardArtifactTag> {
public BlackBoardArtifactTagAddedEvent(BlackboardArtifactTag newTag) {
super(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString(), newTag
);
}
}
@@ -0,0 +1,32 @@
/*
* 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.events;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
/**
*
*/
public class BlackBoardArtifactTagDeletedEvent extends TagDeletedEvent<BlackboardArtifactTag> {
public BlackBoardArtifactTagDeletedEvent(BlackboardArtifactTag oldValue) {
super(Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString(), oldValue);
}
}
@@ -0,0 +1,35 @@
/*
* 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.events;
import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.ContentTag;
/**
* An event that is fired when a ContentTag is added.
*/
@Immutable
public class ContentTagAddedEvent extends TagAddedEvent<ContentTag> {
public ContentTagAddedEvent(ContentTag newTag) {
super(Case.Events.CONTENT_TAG_ADDED.toString(), newTag);
}
}
@@ -0,0 +1,34 @@
/*
* 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.events;
import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.ContentTag;
/**
* An event that is fired when a ContentTag is deleted.
*/
@Immutable
public class ContentTagDeletedEvent extends TagDeletedEvent<ContentTag> {
public ContentTagDeletedEvent(ContentTag deletedTag) {
super(Case.Events.CONTENT_TAG_DELETED.toString(), deletedTag);
}
}
@@ -16,29 +16,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.datamodel;
package org.sleuthkit.autopsy.events;
import java.util.Collection;
import java.util.Collections;
import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.Tag;
/**
* Event broadcast to various UI componenets when one or more files' category
* has been changed
* Base Class for events that are fired when a Tag is added
*/
@Immutable
public class CategoryChangeEvent {
abstract class TagAddedEvent<T extends Tag> extends TagEvent<T> {
private final Collection<Long> ids;
protected TagAddedEvent(String propertyName, T newValue) {
super(Case.class, propertyName, null, newValue);
}
/**
* @return the fileIDs of the files whose categories have changed
* get the Tag that was added
*
* @return the tTag
*/
public Collection<Long> getIds() {
return Collections.unmodifiableCollection(ids);
@SuppressWarnings("unchecked")
@Override
public T getTag() {
return (T) getNewValue();
}
public CategoryChangeEvent(Collection<Long> ids) {
this.ids = ids;
}
}
@@ -0,0 +1,45 @@
/*
* 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.events;
import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.Tag;
/**
* Base Class for events that are fired when a Tag is deleted
*/
@Immutable
abstract class TagDeletedEvent<T extends Tag> extends TagEvent<T> {
protected TagDeletedEvent(String propertyName, T oldValue) {
super(Case.class, propertyName, oldValue, null);
}
/**
* get the Tag that was deleted
*
* @return the Tag
*/
@SuppressWarnings("unchecked")
@Override
public T getTag() {
return (T) getOldValue();
}
}
@@ -0,0 +1,26 @@
/*
* 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.events;
import org.sleuthkit.datamodel.Tag;
/**
*
*/
abstract public class TagEvent<T extends Tag> extends AutopsyEvent {
public TagEvent(Object source, String propertyName, Object oldValue, Object newValue) {
super(source, propertyName, oldValue, newValue);
}
/**
* get the Tag that this event is for
*
* @return the Tag
*/
public abstract T getTag();
}
@@ -269,6 +269,9 @@ public class IngestJobSettings {
case "E01 Verify": //NON-NLS
moduleNames.add("E01 Verifier"); //NON-NLS
break;
case "Archive Extractor": //NON-NLS
moduleNames.add("Embedded File Extractor");
break;
default:
moduleNames.add(name);
}
@@ -17,9 +17,9 @@ EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.msg=Error
EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errInitModule.details=Error initializing output dir\: {0}\: {1}
EmbeddedFileExtractorIngestModule.ArchiveExtractor.init.errCantInitLib=Could not initialize 7-ZIP library\: {0}
EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg=Possible ZIP bomb detected in archive\: {0}, item\: {1}
EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails=The archive item compression ratio is {0}, skipping processing of this archive item.
EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails=Compression ratio is {0}, skipping item in {1}.
EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb=Possible ZIP bomb detected\: {0}
EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb=The archive is {0} levels deep, skipping processing of this archive and its contents
EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb=The archive is {0} levels deep, skipping processing of {1}
EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg=Unknown item path in archive\: {0}, will use\: {1}
EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg=Not enough disk space to unpack archive item\: {0}, {1}
EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details=The archive item is too large to unpack, skipping unpacking this item.
@@ -1,31 +1,31 @@
OpenIDE-Module-Display-Category=\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB
OpenIDE-Module-Display-Category=\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb
OpenIDE-Module-Long-Description=\
7Zip\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB\n\n7Zip\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB\u306F\u30A2\u30FC\u30AB\u30A4\u30D6\u30D5\u30A1\u30A4\u30EB\u3092\u51E6\u7406\u3057\u307E\u3059\uFF08zip\u30847zip\u30A8\u30AF\u30B9\u30C8\u30E9\u30AF\u30BF\u30FC\u306B\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u308B\u305D\u306E\u4ED6\u306E\u30A2\u30FC\u30AB\u30A4\u30D6\u30BF\u30A4\u30D7\u306A\u3069\uFF09\u3002\n\
\u30A2\u30FC\u30AB\u30A4\u30D6\u306E\u30B3\u30F3\u30C6\u30F3\u30C4\u306F\u62BD\u51FA\u3055\u308C\u3001\u6D3E\u751F\u30D5\u30A1\u30A4\u30EB\u306F\u8A2D\u5B9A\u3055\u308C\u305F\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB\u306B\u51E6\u7406\u3055\u308C\u308B\u305F\u3081\u3001\u73FE\u5728\u306E\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u306B\u8FFD\u52A0\u3055\u308C\u307E\u3059\u3002\n\
\u3082\u3057\u6D3E\u751F\u30D5\u30A1\u30A4\u30EB\u304C\u30A2\u30FC\u30AB\u30A4\u30D6\u30D5\u30A1\u30A4\u30EB\u3067\u3042\u308C\u3070\u30017Zip\u30A8\u30AF\u30B9\u30C8\u30E9\u30AF\u30BF\u30FC\u306B\u3088\u308A\u3001\u518D\u5EA6\u51E6\u7406\u3055\u308C\u307E\u3059 - \u30A8\u30AF\u30B9\u30C8\u30E9\u30AF\u30BF\u30FC\u306F\u30A2\u30FC\u30AB\u30A4\u30D6\u30D5\u30A1\u30A4\u30EB\u3092N-\u30EC\u30D9\u30EB\u306E\u6DF1\u3055\u3067\u51E6\u7406\u3057\u307E\u3059\u3002\n\n\
\u62BD\u51FA\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB\u306F\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u30C4\u30EA\u30FC\u3067\u30CA\u30D3\u30B2\u30FC\u30C8\u3067\u304D\u307E\u3059\u3002\n\n\
\u3053\u306E\u30E2\u30B8\u30E5\u30FC\u30EB\u306FWindows\u3001Linux\u3001Mac\u306E\u30AA\u30DA\u30EC\u30FC\u30C6\u30A3\u30F3\u30B0\u30B7\u30B9\u30C6\u30E0\u74B0\u5883\u3092\u30B5\u30DD\u30FC\u30C8\u3057\u3066\u3044\u307E\u3059\u3002
7Zip\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\n\n7Zip\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u306f\u30a2\u30fc\u30ab\u30a4\u30d6\u30d5\u30a1\u30a4\u30eb\u3092\u51e6\u7406\u3057\u307e\u3059\uff08zip\u30847zip\u30a8\u30af\u30b9\u30c8\u30e9\u30af\u30bf\u30fc\u306b\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u308b\u305d\u306e\u4ed6\u306e\u30a2\u30fc\u30ab\u30a4\u30d6\u30bf\u30a4\u30d7\u306a\u3069\uff09\u3002\n\
\u30a2\u30fc\u30ab\u30a4\u30d6\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u306f\u62bd\u51fa\u3055\u308c\u3001\u6d3e\u751f\u30d5\u30a1\u30a4\u30eb\u306f\u8a2d\u5b9a\u3055\u308c\u305f\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb\u306b\u51e6\u7406\u3055\u308c\u308b\u305f\u3081\u3001\u73fe\u5728\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u306b\u8ffd\u52a0\u3055\u308c\u307e\u3059\u3002\n\
\u3082\u3057\u6d3e\u751f\u30d5\u30a1\u30a4\u30eb\u304c\u30a2\u30fc\u30ab\u30a4\u30d6\u30d5\u30a1\u30a4\u30eb\u3067\u3042\u308c\u3070\u30017Zip\u30a8\u30af\u30b9\u30c8\u30e9\u30af\u30bf\u30fc\u306b\u3088\u308a\u3001\u518d\u5ea6\u51e6\u7406\u3055\u308c\u307e\u3059 - \u30a8\u30af\u30b9\u30c8\u30e9\u30af\u30bf\u30fc\u306f\u30a2\u30fc\u30ab\u30a4\u30d6\u30d5\u30a1\u30a4\u30eb\u3092N-\u30ec\u30d9\u30eb\u306e\u6df1\u3055\u3067\u51e6\u7406\u3057\u307e\u3059\u3002\n\n\
\u62bd\u51fa\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb\u306f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30c4\u30ea\u30fc\u3067\u30ca\u30d3\u30b2\u30fc\u30c8\u3067\u304d\u307e\u3059\u3002\n\n\
\u3053\u306e\u30e2\u30b8\u30e5\u30fc\u30eb\u306fWindows\u3001Linux\u3001Mac\u306e\u30aa\u30da\u30ec\u30fc\u30c6\u30a3\u30f3\u30b0\u30b7\u30b9\u30c6\u30e0\u74b0\u5883\u3092\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u3044\u307e\u3059\u3002
OpenIDE-Module-Name=7Zip
OpenIDE-Module-Short-Description=7Zip\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u30E2\u30B8\u30E5\u30FC\u30EB
SevenZipContentReadStream.seek.exception.invalidOrigin=\u7121\u52B9\u306A\u30B7\u30FC\u30AF\u30AA\u30EA\u30B8\u30F3\uFF1A {0}
SevenZipContentReadStream.read.exception.errReadStream=\u30B3\u30F3\u30C6\u30F3\u30C4\u30B9\u30C8\u30EA\u30FC\u30E0\u306E\u8AAD\u307F\u53D6\u308A\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\u3002
SevenZipIngestModule.moduleName=\u30A2\u30FC\u30AB\u30A4\u30D6\u30A8\u30AF\u30B9\u30C8\u30E9\u30AF\u30BF\u30FC
SevenZipIngestModule.moduleDesc.text=\u30A2\u30FC\u30AB\u30A4\u30D6\u30D5\u30A1\u30A4\u30EB(zip, rar, arj, 7z, gzip, bzip2, tar)\u3092\u62BD\u51FA\u3057\u3001\u73FE\u5728\u306E\u30A4\u30F3\u30B8\u30A7\u30B9\u30C8\u306B\u30EA\u30B9\u30B1\u3057\u3001\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u30C4\u30EA\u30FC\u306B\u65B0\u898F\u30D5\u30A1\u30A4\u30EB\u3092\u6295\u5165\u3057\u307E\u3059\u3002
SevenZipIngestModule.encryptionFileLevel=\u30D5\u30A1\u30A4\u30EB\u30EC\u30D9\u30EB\u6697\u53F7\u5316
SevenZipIngestModule.encryptionFull=\u5168\u4F53\u6697\u53F7\u5316
SevenZipIngestModule.init.errInitModule.msg={0}\u306E\u521D\u671F\u5316\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F
SevenZipIngestModule.init.errInitModule.details=\u30A2\u30A6\u30C8\u30D7\u30C3\u30C8\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\: {0}\: {1}\u306E\u521D\u671F\u5316\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F
SevenZipIngestModule.init.errCantInitLib=7-Zip\u30E9\u30A4\u30D6\u30E9\u30EA\: {0}\u3092\u521D\u671F\u5316\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F
SevenZipIngestModule.isZipBombCheck.warnMsg=Zip\u7206\u5F3E\u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u3082\u306E\u304C\u30A2\u30FC\u30AB\u30A4\u30D6\: {0}, item\: {1}\u306B\u691C\u51FA\u3055\u308C\u307E\u3057\u305F
SevenZipIngestModule.isZipBombCheck.warnDetails=\u30A2\u30FC\u30AB\u30A4\u30D6\u30A2\u30A4\u30C6\u30E0\u306E\u5727\u7E2E\u7387\u306F{0}\u3001\u3053\u306E\u30A2\u30FC\u30AB\u30A4\u30D6\u30A2\u30A4\u30C6\u30E0\u306E\u51E6\u7406\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3059\u3002
SevenZipIngestModule.unpack.warnMsg.zipBomb=Zip\u7206\u5F3E\u306E\u53EF\u80FD\u6027\u304C\u3042\u308B\u3082\u306E\u304C\u691C\u51FA\u3055\u308C\u307E\u3057\u305F\uFF1A {0}
SevenZipIngestModule.unpack.warnDetails.zipBomb=\u30A2\u30FC\u30AB\u30A4\u30D6\u306F {0}\u30EC\u30D9\u30EB\u306E\u6DF1\u3055\u3067\u3059\u3001\u3053\u306E\u30A2\u30FC\u30AB\u30A4\u30D6\u3068\u305D\u306E\u30B3\u30F3\u30C6\u30F3\u30C4\u306E\u51E6\u7406\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3059
SevenZipIngestModule.unpack.unknownPath.msg=\u30A2\u30FC\u30AB\u30A4\u30D6\: {0}\u306B\u4E0D\u660E\u306E\u30A2\u30A4\u30C6\u30E0\u30D1\u30B9\u304C\u5B58\u5728\u3057\u307E\u3059\u3001{1}\u3092\u4F7F\u7528\u3057\u307E\u3059
SevenZipIngestModule.unpack.notEnoughDiskSpace.msg=\u30A2\u30FC\u30AB\u30A4\u30D6\u30A2\u30A4\u30C6\u30E0\: {0}, {1}\u3092\u89E3\u51CD\u3059\u308B\u306E\u306B\u5341\u5206\u306A\u30C7\u30A3\u30B9\u30AF\u30B9\u30DA\u30FC\u30B9\u304C\u3042\u308A\u307E\u305B\u3093
SevenZipIngestModule.unpack.notEnoughDiskSpace.details=\u30A2\u30FC\u30AB\u30A4\u30D6\u30A2\u30A4\u30C6\u30E0\u306F\u89E3\u51CD\u3059\u308B\u306E\u306B\u5927\u304D\u3059\u304E\u307E\u3059\u3001\u3053\u306E\u30A2\u30A4\u30C6\u30E0\u306E\u89E3\u51CD\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3059\u3002
SevenZipIngestModule.unpack.errUnpacking.msg={0}\u89E3\u51CD\u30A8\u30E9\u30FC
SevenZipIngestModule.unpack.errUnpacking.details={0}. {1}\u306E\u89E3\u51CD\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F
SevenZipIngestModule.unpack.encrFileDetected.msg=\u30A2\u30FC\u30AB\u30A4\u30D6\u306B\u6697\u53F7\u5316\u30D5\u30A1\u30A4\u30EB\u304C\u691C\u51FA\u3055\u308C\u307E\u3057\u305F\u3002
SevenZipIngestModule.unpack.encrFileDetected.details=\u30A2\u30FC\u30AB\u30A4\u30D6\: {0}\u306E\u4E00\u90E8\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u6697\u53F7\u5316\u3055\u308C\u3066\u3044\u307E\u3059\u3002{1}\u30A8\u30AF\u30B9\u30C8\u30E9\u30AF\u30BF\u30FC\u306F\u3053\u306E\u30A2\u30FC\u30AB\u30A4\u30D6\u304B\u3089\u5168\u3066\u306E\u30D5\u30A1\u30A4\u30EB\u306F\u62BD\u51FA\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002
SevenZipIngestModule.UnpackStream.write.exception.msg=\u89E3\u51CD\u3057\u305F\u30D5\u30A1\u30A4\u30EB\u306E\u4E0B\u8A18\u3078\u306E\u66F8\u304D\u8FBC\u307F\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F\: {0}
SevenZipIngestModule.UnpackedTree.exception.msg=\u6D3E\u751F\u30D5\u30A1\u30A4\u30EB\u3092\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\:{0}\u306B\u8FFD\u52A0\u4E2D\u306B\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F
OpenIDE-Module-Short-Description=7Zip\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u30e2\u30b8\u30e5\u30fc\u30eb
SevenZipContentReadStream.seek.exception.invalidOrigin=\u7121\u52b9\u306a\u30b7\u30fc\u30af\u30aa\u30ea\u30b8\u30f3\uff1a {0}
SevenZipContentReadStream.read.exception.errReadStream=\u30b3\u30f3\u30c6\u30f3\u30c4\u30b9\u30c8\u30ea\u30fc\u30e0\u306e\u8aad\u307f\u53d6\u308a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002
SevenZipIngestModule.moduleName=\u30a2\u30fc\u30ab\u30a4\u30d6\u30a8\u30af\u30b9\u30c8\u30e9\u30af\u30bf\u30fc
SevenZipIngestModule.moduleDesc.text=\u30a2\u30fc\u30ab\u30a4\u30d6\u30d5\u30a1\u30a4\u30eb(zip, rar, arj, 7z, gzip, bzip2, tar)\u3092\u62bd\u51fa\u3057\u3001\u73fe\u5728\u306e\u30a4\u30f3\u30b8\u30a7\u30b9\u30c8\u306b\u30ea\u30b9\u30b1\u3057\u3001\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30c4\u30ea\u30fc\u306b\u65b0\u898f\u30d5\u30a1\u30a4\u30eb\u3092\u6295\u5165\u3057\u307e\u3059\u3002
SevenZipIngestModule.encryptionFileLevel=\u30d5\u30a1\u30a4\u30eb\u30ec\u30d9\u30eb\u6697\u53f7\u5316
SevenZipIngestModule.encryptionFull=\u5168\u4f53\u6697\u53f7\u5316
SevenZipIngestModule.init.errInitModule.msg={0}\u306e\u521d\u671f\u5316\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
SevenZipIngestModule.init.errInitModule.details=\u30a2\u30a6\u30c8\u30d7\u30c3\u30c8\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\: {0}\: {1}\u306e\u521d\u671f\u5316\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
SevenZipIngestModule.init.errCantInitLib=7-Zip\u30e9\u30a4\u30d6\u30e9\u30ea\: {0}\u3092\u521d\u671f\u5316\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
SevenZipIngestModule.isZipBombCheck.warnMsg=Zip\u7206\u5f3e\u306e\u53ef\u80fd\u6027\u304c\u3042\u308b\u3082\u306e\u304c\u30a2\u30fc\u30ab\u30a4\u30d6\: {0}, item\: {1}\u306b\u691c\u51fa\u3055\u308c\u307e\u3057\u305f
SevenZipIngestModule.isZipBombCheck.warnDetails=\u30a2\u30fc\u30ab\u30a4\u30d6\u30a2\u30a4\u30c6\u30e0\u306e\u5727\u7e2e\u7387\u306f{0}\u3001\u3053\u306e\u30a2\u30fc\u30ab\u30a4\u30d6\u30a2\u30a4\u30c6\u30e0\u306e\u51e6\u7406\u3092\u30b9\u30ad\u30c3\u30d7\u3057\u307e\u3059\u3002{1}
SevenZipIngestModule.unpack.warnMsg.zipBomb=Zip\u7206\u5f3e\u306e\u53ef\u80fd\u6027\u304c\u3042\u308b\u3082\u306e\u304c\u691c\u51fa\u3055\u308c\u307e\u3057\u305f\uff1a {0}
SevenZipIngestModule.unpack.warnDetails.zipBomb=\u30a2\u30fc\u30ab\u30a4\u30d6\u306f {0}\u30ec\u30d9\u30eb\u306e\u6df1\u3055\u3067\u3059\u3001\u3053\u306e\u30a2\u30fc\u30ab\u30a4\u30d6\u3068\u305d\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u51e6\u7406\u3092\u30b9\u30ad\u30c3\u30d7\u3057\u307e\u3059 {1}
SevenZipIngestModule.unpack.unknownPath.msg=\u30a2\u30fc\u30ab\u30a4\u30d6\: {0}\u306b\u4e0d\u660e\u306e\u30a2\u30a4\u30c6\u30e0\u30d1\u30b9\u304c\u5b58\u5728\u3057\u307e\u3059\u3001{1}\u3092\u4f7f\u7528\u3057\u307e\u3059
SevenZipIngestModule.unpack.notEnoughDiskSpace.msg=\u30a2\u30fc\u30ab\u30a4\u30d6\u30a2\u30a4\u30c6\u30e0\: {0}, {1}\u3092\u89e3\u51cd\u3059\u308b\u306e\u306b\u5341\u5206\u306a\u30c7\u30a3\u30b9\u30af\u30b9\u30da\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093
SevenZipIngestModule.unpack.notEnoughDiskSpace.details=\u30a2\u30fc\u30ab\u30a4\u30d6\u30a2\u30a4\u30c6\u30e0\u306f\u89e3\u51cd\u3059\u308b\u306e\u306b\u5927\u304d\u3059\u304e\u307e\u3059\u3001\u3053\u306e\u30a2\u30a4\u30c6\u30e0\u306e\u89e3\u51cd\u3092\u30b9\u30ad\u30c3\u30d7\u3057\u307e\u3059\u3002
SevenZipIngestModule.unpack.errUnpacking.msg={0}\u89e3\u51cd\u30a8\u30e9\u30fc
SevenZipIngestModule.unpack.errUnpacking.details={0}. {1}\u306e\u89e3\u51cd\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
SevenZipIngestModule.unpack.encrFileDetected.msg=\u30a2\u30fc\u30ab\u30a4\u30d6\u306b\u6697\u53f7\u5316\u30d5\u30a1\u30a4\u30eb\u304c\u691c\u51fa\u3055\u308c\u307e\u3057\u305f\u3002
SevenZipIngestModule.unpack.encrFileDetected.details=\u30a2\u30fc\u30ab\u30a4\u30d6\: {0}\u306e\u4e00\u90e8\u306e\u30d5\u30a1\u30a4\u30eb\u304c\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u307e\u3059\u3002{1}\u30a8\u30af\u30b9\u30c8\u30e9\u30af\u30bf\u30fc\u306f\u3053\u306e\u30a2\u30fc\u30ab\u30a4\u30d6\u304b\u3089\u5168\u3066\u306e\u30d5\u30a1\u30a4\u30eb\u306f\u62bd\u51fa\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
SevenZipIngestModule.UnpackStream.write.exception.msg=\u89e3\u51cd\u3057\u305f\u30d5\u30a1\u30a4\u30eb\u306e\u4e0b\u8a18\u3078\u306e\u66f8\u304d\u8fbc\u307f\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\: {0}
SevenZipIngestModule.UnpackedTree.exception.msg=\u6d3e\u751f\u30d5\u30a1\u30a4\u30eb\u3092\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\:{0}\u306b\u8ffd\u52a0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
@@ -45,17 +45,12 @@ public final class EmbeddedFileExtractorIngestModule implements FileIngestModule
private final IngestServices services = IngestServices.getInstance();
static final String[] SUPPORTED_EXTENSIONS = {"zip", "rar", "arj", "7z", "7zip", "gzip", "gz", "bzip2", "tar", "tgz",}; // "iso"}; NON-NLS
//buffer for checking file headers and signatures
private static final int readHeaderSize = 4;
private static final byte[] fileHeaderBuffer = new byte[readHeaderSize];
private static final int ZIP_SIGNATURE_BE = 0x504B0304;
private IngestJobContext context;
private long jobId;
private final static IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
private static final Case currentCase = Case.getCurrentCase();
protected static final String moduleDirRelative = Case.getModulesOutputDirRelPath() + File.separator + EmbeddedFileExtractorModuleFactory.getModuleName(); //relative to the case, to store in db
protected static final String moduleDirAbsolute = currentCase.getModulesOutputDirAbsPath() + File.separator + EmbeddedFileExtractorModuleFactory.getModuleName(); //absolute, to extract to
private String moduleDirRelative;
private String moduleDirAbsolute;
private boolean archivextraction;
private boolean imageExtraction;
@@ -72,14 +67,19 @@ public final class EmbeddedFileExtractorIngestModule implements FileIngestModule
this.context = context;
jobId = context.getJobId();
final Case currentCase = Case.getCurrentCase();
moduleDirRelative = Case.getModulesOutputDirRelPath() + File.separator + EmbeddedFileExtractorModuleFactory.getModuleName(); //relative to the case, to store in db
moduleDirAbsolute = currentCase.getModulesOutputDirAbsPath() + File.separator + EmbeddedFileExtractorModuleFactory.getModuleName(); //absolute, to extract to
// initialize the folder where the embedded files are extracted.
File extractionDirectory = new File(EmbeddedFileExtractorIngestModule.moduleDirAbsolute);
File extractionDirectory = new File(moduleDirAbsolute);
if (!extractionDirectory.exists()) {
try {
extractionDirectory.mkdirs();
} catch (SecurityException ex) {
logger.log(Level.SEVERE, "Error initializing output dir: " + EmbeddedFileExtractorIngestModule.moduleDirAbsolute, ex); //NON-NLS
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), "Error initializing", "Error initializing output dir: " + EmbeddedFileExtractorIngestModule.moduleDirAbsolute)); //NON-NLS
logger.log(Level.SEVERE, "Error initializing output dir: " + moduleDirAbsolute, ex); //NON-NLS
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), "Error initializing", "Error initializing output dir: " + moduleDirAbsolute)); //NON-NLS
throw new IngestModuleException(ex.getMessage());
}
}
@@ -92,8 +92,8 @@ public final class EmbeddedFileExtractorIngestModule implements FileIngestModule
}
// initialize the extraction modules.
this.archiveExtractor = new SevenZipExtractor(context, fileTypeDetector);
this.imageExtractor = new ImageExtractor(context, fileTypeDetector);
this.archiveExtractor = new SevenZipExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute);
this.imageExtractor = new ImageExtractor(context, fileTypeDetector, moduleDirRelative, moduleDirAbsolute);
}
@Override
@@ -116,24 +116,6 @@ public final class EmbeddedFileExtractorIngestModule implements FileIngestModule
return ProcessResult.OK;
}
//check if already has derived files, skip
try {
if (abstractFile.hasChildren()) {
//check if local unpacked dir exists
final String uniqueFileName = getUniqueName(abstractFile);
final String localRootAbsPath = getLocalRootAbsPath(uniqueFileName);
if (new File(localRootAbsPath).exists()) {
logger.log(Level.INFO, "File already has been processed as it has children and local unpacked file, skipping: {0}", abstractFile.getName()); //NON-NLS
return ProcessResult.OK;
}
}
} catch (TskCoreException e) {
logger.log(Level.INFO, "Error checking if file already has been processed, skipping: {0}", abstractFile.getName()); //NON-NLS
return ProcessResult.OK;
}
logger.log(Level.INFO, "Processing with embedded file extractor: {0}", abstractFile.getName()); //NON-NLS
// call the archive extractor if archiveextraction flag is set.
if (this.archivextraction) {
archiveExtractor.unpack(abstractFile);
@@ -159,17 +141,7 @@ public final class EmbeddedFileExtractorIngestModule implements FileIngestModule
* @param archiveFile
* @return
*/
protected static String getUniqueName(AbstractFile archiveFile) {
static String getUniqueName(AbstractFile archiveFile) {
return archiveFile.getName() + "_" + archiveFile.getId();
}
/**
* Get local abs path to the unpacked archive root
*
* @param localRootRelPath relative path to archive, from getUniqueName()
* @return
*/
protected static String getLocalRootAbsPath(String localRootRelPath) {
return moduleDirAbsolute + File.separator + localRootRelPath;
}
}
}
@@ -25,6 +25,7 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import org.apache.poi.OldFileFormatException;
import org.apache.poi.hslf.model.Picture;
import org.apache.poi.hslf.usermodel.PictureData;
import org.apache.poi.hslf.usermodel.SlideShow;
@@ -58,6 +59,9 @@ class ImageExtractor {
private String parentFileName;
private final String UNKNOWN_NAME_PREFIX = "image_";
private final FileTypeDetector fileTypeDetector;
private String moduleDirRelative;
private String moduleDirAbsolute;
/**
* Enum of mimetypes which support image extraction
*/
@@ -84,12 +88,14 @@ class ImageExtractor {
}
private SupportedImageExtractionFormats abstractFileExtractionFormat;
ImageExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector) {
ImageExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute) {
this.fileManager = Case.getCurrentCase().getServices().getFileManager();
this.services = IngestServices.getInstance();
this.context = context;
this.fileTypeDetector = fileTypeDetector;
this.moduleDirRelative = moduleDirRelative;
this.moduleDirAbsolute = moduleDirAbsolute;
}
/**
@@ -125,7 +131,7 @@ class ImageExtractor {
* @param format
* @param abstractFile The abstract file to be processed.
*/
protected void extractImage(AbstractFile abstractFile) {
void extractImage(AbstractFile abstractFile) {
//
// switchcase for different supported formats
// process abstractFile according to the format by calling appropriate methods.
@@ -133,7 +139,19 @@ class ImageExtractor {
List<ExtractedImage> listOfExtractedImages = null;
List<AbstractFile> listOfExtractedImageAbstractFiles = null;
this.parentFileName = EmbeddedFileExtractorIngestModule.getUniqueName(abstractFile);
//check if already has derived files, skip
try {
if (abstractFile.hasChildren()) {
//check if local unpacked dir exists
if (new File(getOutputFolderPath(parentFileName)).exists()) {
logger.log(Level.INFO, "File already has been processed as it has children and local unpacked file, skipping: {0}", abstractFile.getName()); //NON-NLS
return;
}
}
} catch (TskCoreException e) {
logger.log(Level.INFO, "Error checking if file already has been processed, skipping: {0}", parentFileName); //NON-NLS
return;
}
switch (abstractFileExtractionFormat) {
case DOC:
listOfExtractedImages = extractImagesFromDoc(abstractFile);
@@ -189,8 +207,9 @@ class ImageExtractor {
HWPFDocument doc = null;
try {
doc = new HWPFDocument(new ReadContentInputStream(af));
} catch (IOException ex) {
logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.docContainer.init.err", af.getName()), ex); //NON-NLS
} catch (Throwable ex) {
// instantiating POI containers throw RuntimeExceptions
logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.docContainer.init.err", af.getName())); //NON-NLS
return null;
}
PicturesTable pictureTable = doc.getPicturesTable();
@@ -227,8 +246,9 @@ class ImageExtractor {
XWPFDocument docx = null;
try {
docx = new XWPFDocument(new ReadContentInputStream(af));
} catch (IOException ex) {
logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.docxContainer.init.err", af.getName()), ex); //NON-NLS
} catch (Throwable ex) {
// instantiating POI containers throw RuntimeExceptions
logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.docxContainer.init.err", af.getName())); //NON-NLS
return null;
}
List<XWPFPictureData> listOfAllPictures = docx.getAllPictures();
@@ -266,8 +286,9 @@ class ImageExtractor {
SlideShow ppt = null;
try {
ppt = new SlideShow(new ReadContentInputStream(af));
} catch (IOException ex) {
logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.pptContainer.init.err", af.getName()), ex); //NON-NLS
} catch (Throwable ex) {
// instantiating POI containers throw RuntimeExceptions
logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.pptContainer.init.err", af.getName())); //NON-NLS
return null;
}
@@ -336,8 +357,9 @@ class ImageExtractor {
XMLSlideShow pptx;
try {
pptx = new XMLSlideShow(new ReadContentInputStream(af));
} catch (IOException ex) {
logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.pptxContainer.init.err", af.getName()), ex); //NON-NLS
} catch (Throwable ex) {
// instantiating POI containers throw RuntimeExceptions
logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.pptxContainer.init.err", af.getName())); //NON-NLS
return null;
}
List<XSLFPictureData> listOfAllPictures = pptx.getAllPictures();
@@ -383,8 +405,9 @@ class ImageExtractor {
Workbook xls;
try {
xls = new HSSFWorkbook(new ReadContentInputStream(af));
} catch (IOException ex) {
logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.xlsContainer.init.err", af.getName()) + af.getName(), ex); //NON-NLS
} catch (Throwable ex) {
// instantiating POI containers throw RuntimeExceptions
logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.xlsContainer.init.err", af.getName()) + af.getName()); //NON-NLS
return null;
}
@@ -426,8 +449,9 @@ class ImageExtractor {
Workbook xlsx;
try {
xlsx = new XSSFWorkbook(new ReadContentInputStream(af));
} catch (IOException ex) {
logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.xlsxContainer.init.err", af.getName()), ex); //NON-NLS
} catch (Throwable ex) {
// instantiating POI containers throw RuntimeExceptions
logger.log(Level.WARNING, NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ImageExtractor.xlsxContainer.init.err", af.getName())); //NON-NLS
return null;
}
@@ -481,7 +505,7 @@ class ImageExtractor {
* @return path to the image extraction folder for a given abstract file.
*/
private String getOutputFolderPath(String parentFileName) {
String outputFolderPath = EmbeddedFileExtractorIngestModule.moduleDirAbsolute + File.separator + parentFileName;
String outputFolderPath = moduleDirAbsolute + File.separator + parentFileName;
File outputFilePath = new File(outputFolderPath);
if (!outputFilePath.exists()) {
try {
@@ -504,7 +528,7 @@ class ImageExtractor {
*/
private String getFileRelativePath(String fileName) {
// Used explicit FWD slashes to maintain DB consistency across operating systems.
return "/" + EmbeddedFileExtractorIngestModule.moduleDirRelative + "/" + this.parentFileName + "/" + fileName; //NON-NLS
return "/" + moduleDirRelative + "/" + this.parentFileName + "/" + fileName; //NON-NLS
}
/**
@@ -572,4 +596,4 @@ class ImageExtractor {
return parentFile;
}
}
}
}
@@ -40,6 +40,7 @@ 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;
@@ -79,6 +80,13 @@ class SevenZipExtractor {
private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; //1GB
//counts archive depth
private ArchiveDepthCountTree archiveDepthCountTree;
private String moduleDirRelative;
private String moduleDirAbsolute;
private String getLocalRootAbsPath(String uniqueArchiveFileName) {
return moduleDirAbsolute + File.separator + uniqueArchiveFileName;
}
/**
* Enum of mimetypes which support archive extraction
*/
@@ -103,7 +111,7 @@ class SevenZipExtractor {
// TODO Expand to support more formats after upgrading Tika
}
SevenZipExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector) throws IngestModuleException {
SevenZipExtractor(IngestJobContext context, FileTypeDetector fileTypeDetector, String moduleDirRelative, String moduleDirAbsolute) throws IngestModuleException {
if (!SevenZip.isInitializedSuccessfully() && (SevenZip.getLastInitializationException() == null)) {
try {
SevenZip.initSevenZipFromPlatformJAR();
@@ -121,6 +129,8 @@ class SevenZipExtractor {
}
this.context = context;
this.fileTypeDetector = fileTypeDetector;
this.moduleDirRelative = moduleDirRelative;
this.moduleDirAbsolute = moduleDirAbsolute;
this.archiveDepthCountTree = new ArchiveDepthCountTree();
}
@@ -169,31 +179,37 @@ class SevenZipExtractor {
* @param archiveFileItem the archive item
* @return true if potential zip bomb, false otherwise
*/
private boolean isZipBombArchiveItemCheck(String archiveName, ISimpleInArchiveItem archiveFileItem) {
private boolean isZipBombArchiveItemCheck(AbstractFile archiveFile, ISimpleInArchiveItem archiveFileItem) {
try {
final long archiveItemSize = archiveFileItem.getSize();
final Long archiveItemSize = archiveFileItem.getSize();
//skip the check for small files
if (archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
if (archiveItemSize == null || archiveItemSize < MIN_COMPRESSION_RATIO_SIZE) {
return false;
}
final long archiveItemPackedSize = archiveFileItem.getPackedSize();
final Long archiveItemPackedSize = archiveFileItem.getPackedSize();
if (archiveItemPackedSize <= 0) {
logger.log(Level.WARNING, "Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}", new Object[]{archiveName, archiveFileItem.getPath()}); //NON-NLS
if (archiveItemPackedSize == null || archiveItemPackedSize <= 0) {
logger.log(Level.WARNING, "Cannot getting compression ratio, cannot detect if zipbomb: {0}, item: {1}", new Object[]{archiveFile.getName(), archiveFileItem.getPath()}); //NON-NLS
return false;
}
int cRatio = (int) (archiveItemSize / archiveItemPackedSize);
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(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveName, itemName);
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnMsg", archiveFile.getName(), itemName);
String path;
try {
path = archiveFile.getUniquePath();
} catch (TskCoreException ex) {
path = archiveFile.getParentPath() + archiveFile.getName();
}
String details = NbBundle.getMessage(this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio);
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.isZipBombCheck.warnDetails", cRatio, path);
//MessageNotifyUtil.Notify.error(msg, details);
services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
return true;
@@ -259,7 +275,29 @@ class SevenZipExtractor {
* @param archiveFile file to unpack
* @return list of unpacked derived files
*/
protected void unpack(AbstractFile archiveFile) {
void unpack(AbstractFile archiveFile) {
String archiveFilePath;
try {
archiveFilePath = archiveFile.getUniquePath();
} catch (TskCoreException ex) {
archiveFilePath = archiveFile.getParentPath() + archiveFile.getName();
}
//check if already has derived files, skip
try {
if (archiveFile.hasChildren()) {
//check if local unpacked dir exists
if (new File(EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile)).exists()) {
logger.log(Level.INFO, "File already has been processed as it has children and local unpacked file, skipping: {0}", archiveFilePath); //NON-NLS
return;
}
}
} catch (TskCoreException e) {
logger.log(Level.INFO, "Error checking if file already has been processed, skipping: {0}", archiveFilePath); //NON-NLS
return;
}
List<AbstractFile> unpackedFiles = Collections.<AbstractFile>emptyList();
//recursion depth check for zip bomb
@@ -272,7 +310,7 @@ class SevenZipExtractor {
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnMsg.zipBomb", archiveFile.getName());
String details = NbBundle.getMessage(this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.warnDetails.zipBomb",
parentAr.getDepth());
parentAr.getDepth(), archiveFilePath);
//MessageNotifyUtil.Notify.error(msg, details);
services.postMessage(IngestMessage.createWarningMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
return;
@@ -288,7 +326,6 @@ class SevenZipExtractor {
NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.moduleName"));
int processedItems = 0;
String compressMethod = null;
boolean progressStarted = false;
try {
stream = new SevenZipContentReadStream(new ReadContentInputStream(archiveFile));
@@ -300,7 +337,7 @@ class SevenZipExtractor {
inArchive = SevenZip.openInArchive(options, stream);
int numItems = inArchive.getNumberOfItems();
logger.log(Level.INFO, "Count of items in archive: {0}: {1}", new Object[]{archiveFile.getName(), numItems}); //NON-NLS
logger.log(Level.INFO, "Count of items in archive: {0}: {1}", new Object[]{archiveFilePath, numItems}); //NON-NLS
progress.start(numItems);
progressStarted = true;
@@ -308,7 +345,7 @@ class SevenZipExtractor {
//setup the archive local root folder
final String uniqueArchiveFileName = EmbeddedFileExtractorIngestModule.getUniqueName(archiveFile);
final String localRootAbsPath = EmbeddedFileExtractorIngestModule.getLocalRootAbsPath(uniqueArchiveFileName);
final String localRootAbsPath = getLocalRootAbsPath(uniqueArchiveFileName);
final File localRoot = new File(localRootAbsPath);
if (!localRoot.exists()) {
try {
@@ -321,7 +358,7 @@ class SevenZipExtractor {
}
//initialize tree hierarchy to keep track of unpacked file structure
SevenZipExtractor.UnpackedTree unpackedTree = new SevenZipExtractor.UnpackedTree(EmbeddedFileExtractorIngestModule.moduleDirRelative + "/" + uniqueArchiveFileName, archiveFile);
SevenZipExtractor.UnpackedTree unpackedTree = new SevenZipExtractor.UnpackedTree(moduleDirRelative + "/" + uniqueArchiveFileName, archiveFile);
long freeDiskSpace = services.getFreeDiskSpace();
@@ -359,7 +396,7 @@ class SevenZipExtractor {
}
String msg = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.unknownPath.msg",
archiveFile.getName(), pathInArchive);
archiveFilePath, pathInArchive);
logger.log(Level.WARNING, msg);
}
@@ -367,7 +404,7 @@ class SevenZipExtractor {
logger.log(Level.INFO, "Extracted item path: {0}", pathInArchive); //NON-NLS
//check if possible zip bomb
if (isZipBombArchiveItemCheck(archiveFile.getName(), item)) {
if (isZipBombArchiveItemCheck(archiveFile, item)) {
continue; //skip the item
}
@@ -379,10 +416,6 @@ class SevenZipExtractor {
//update progress bar
progress.progress(archiveFile.getName() + ": " + fileName, processedItems);
if (compressMethod == null) {
compressMethod = item.getMethod();
}
final boolean isEncrypted = item.isEncrypted();
final boolean isDir = item.isFolder();
@@ -394,7 +427,14 @@ class SevenZipExtractor {
fullEncryption = false;
}
final long size = item.getSize();
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;
}
//check if unpacking this file will result in out of disk space
//this is additional to zip bomb prevention mechanism
@@ -403,12 +443,12 @@ class SevenZipExtractor {
if (newDiskSpace < MIN_FREE_DISK_SPACE) {
String msg = NbBundle.getMessage(this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.msg",
archiveFile.getName(), fileName);
archiveFilePath, fileName);
String details = NbBundle.getMessage(this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.notEnoughDiskSpace.details");
//MessageNotifyUtil.Notify.error(msg, details);
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
logger.log(Level.INFO, "Skipping archive item due to insufficient disk space: {0}, {1}", new Object[]{archiveFile.getUniquePath(), fileName}); //NON-NLS
logger.log(Level.INFO, "Skipping archive item due to insufficient disk space: {0}, {1}", new Object[]{archiveFilePath, fileName}); //NON-NLS
logger.log(Level.INFO, "Available disk space: {0}", new Object[]{freeDiskSpace}); //NON-NLS
continue; //skip this file
} else {
@@ -420,8 +460,8 @@ class SevenZipExtractor {
final String uniqueExtractedName = uniqueArchiveFileName + File.separator + (item.getItemIndex() / 1000) + File.separator + item.getItemIndex() + new File(pathInArchive).getName();
//final String localRelPath = unpackDir + File.separator + localFileRelPath;
final String localRelPath = EmbeddedFileExtractorIngestModule.moduleDirRelative + File.separator + uniqueExtractedName;
final String localAbsPath = EmbeddedFileExtractorIngestModule.moduleDirAbsolute + File.separator + uniqueExtractedName;
final String localRelPath = moduleDirRelative + File.separator + uniqueExtractedName;
final String localAbsPath = moduleDirAbsolute + File.separator + uniqueExtractedName;
//create local dirs and empty files before extracted
File localFile = new java.io.File(localAbsPath);
@@ -498,23 +538,17 @@ class SevenZipExtractor {
//TODO decide if anything to cleanup, for now bailing
}
} catch (SevenZipException | TskCoreException ex) {
} catch (SevenZipException ex) {
logger.log(Level.SEVERE, "Error unpacking file: " + archiveFile, ex); //NON-NLS
//inbox message
String fullName;
try {
fullName = archiveFile.getUniquePath();
} catch (TskCoreException ex1) {
fullName = archiveFile.getName();
}
// 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",
archiveFile.getName());
String details = NbBundle.getMessage(this.getClass(),
"EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.errUnpacking.details",
fullName, ex.getMessage());
archiveFilePath, ex.getMessage());
services.postMessage(IngestMessage.createErrorMessage(EmbeddedFileExtractorModuleFactory.getModuleName(), msg, details));
}
} finally {
@@ -548,7 +582,7 @@ class SevenZipExtractor {
artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID(), EmbeddedFileExtractorModuleFactory.getModuleName(), encryptionType));
services.fireModuleDataEvent(new ModuleDataEvent(EmbeddedFileExtractorModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED));
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error creating blackboard artifact for encryption detected for file: " + archiveFile, ex); //NON-NLS
logger.log(Level.SEVERE, "Error creating blackboard artifact for encryption detected for file: " + archiveFilePath, ex); //NON-NLS
}
String msg = NbBundle.getMessage(this.getClass(), "EmbeddedFileExtractorIngestModule.ArchiveExtractor.unpack.encrFileDetected.msg");
@@ -204,7 +204,12 @@ public final class ExifParserFileIngestModule implements FileIngestModule {
*/
private boolean parsableFormat(AbstractFile f) {
try {
return fileTypeDetector.getFileType(f).equals("image/jpeg");
String mimeType = fileTypeDetector.getFileType(f);
if (mimeType != null) {
return fileTypeDetector.getFileType(f).equals("image/jpeg");
} else {
return false;
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Failed to detect file type", ex); //NON-NLS
return false;
@@ -3,10 +3,14 @@ OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Long-Description=PhotoRec Carver ingest module. \n\n Carves unallocated space and feeds the resulting carved files back into the system for processing.
OpenIDE-Module-Short-Description=Carves unallocated space and feeds carved files back into the system for processing.
moduleDisplayName.text=PhotoRec Carver
moduleDescription.text=Runs PhotoRec carver against unallocated space on the system.
moduleDescription.text=Runs PhotoRec carver against unallocated space in the data source.
unallocatedSpaceProcessingSettingsError.message="Process Unallocated Space" is not checked. This module is designed to carve unallocated space. Either allow processing of unallocated space, or do not use this module.
unsupportedOS.message=Module is not supported for other than Windows platforms
missingExecutable.message=Unable to locate unallocated carver executable.
cannotRunExecutable.message=Unable to execute unallocated carver
unallocatedSpaceProcessingSettingsError.message="Process Unallocated Space" is not checked. The PhotoRec module is designed to carve unallocated space. Either enable processing of unallocated space or disable this module.
unsupportedOS.message=PhotoRec Module is supported only on Windows platforms
missingExecutable.message=Unable to locate PhotoRec executable.
cannotRunExecutable.message=Unable to execute PhotoRec
cannotCreateOutputDir.message=Unable to create output directory: {0}
PhotoRecIngestModule.processTerminated=PhotoRec Carver ingest module was terminated due to exceeding max allowable run time when scanning
PhotoRecIngestModule.moduleError=PhotoRec Carver Module Error
PhotoRecIngestModule.UnableToCarve=Unable to carve file: {0}
PhotoRecIngestModule.NotEnoughDiskSpace=Not enough disk space to save unallocated file. Carving will be skipped.
@@ -26,10 +26,10 @@ import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -38,12 +38,18 @@ import org.openide.modules.InstalledFileLocator;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.ExecUtil;
import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.ingest.FileIngestModule;
import org.sleuthkit.autopsy.ingest.FileIngestModuleProcessTerminator;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestModule;
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Image;
@@ -51,10 +57,6 @@ import org.sleuthkit.datamodel.LayoutFile;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.Volume;
import org.sleuthkit.autopsy.coreutils.FileUtil;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.ingest.FileIngestModuleProcessTerminator;
import org.sleuthkit.autopsy.ingest.IngestServices;
/**
* A file ingest module that runs the Unallocated Carver executable with unallocated space files as input.
@@ -74,6 +76,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
private IngestJobContext context;
private Path rootOutputDirPath;
private File executableFile;
private IngestServices services;
/**
* @inheritDoc
@@ -81,6 +84,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
@Override
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
this.context = context;
this.services = IngestServices.getInstance();
// If the global unallocated space processing setting and the module
// process unallocated space only setting are not in sych, throw an
@@ -143,9 +147,15 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
// Check that we have roughly enough disk space left to complete the operation
long freeDiskSpace = IngestServices.getInstance().getFreeDiskSpace();
if ((file.getSize() * 2) > freeDiskSpace) {
logger.log(Level.SEVERE, "PhotoRec error processing {0} with {1} Not enough space on primary disk to carve unallocated space.", // NON-NLS
// Some network drives always return -1 for free disk space.
// In this case, expect enough space and move on.
if ((freeDiskSpace != -1) && ((file.getSize() * 1.2) > freeDiskSpace)) {
logger.log(Level.SEVERE, "PhotoRec error processing {0} with {1} Not enough space on primary disk to save unallocated space.", // NON-NLS
new Object[]{file.getName(), PhotoRecCarverIngestModuleFactory.getModuleName()}); // NON-NLS
MessageNotifyUtil.Notify.error(NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.UnableToCarve", file.getName()),
NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.NotEnoughDiskSpace"));
return IngestModule.ProcessResult.ERROR;
}
@@ -177,22 +187,12 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
if (this.context.fileIngestIsCancelled() == true) {
// if it was cancelled by the user, result is OK
// cleanup the output path
FileUtil.deleteDir(new File(outputDirPath.toString()));
if (null != tempFilePath && Files.exists(tempFilePath)) {
tempFilePath.toFile().delete();
}
cleanup(outputDirPath, tempFilePath);
logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS
return IngestModule.ProcessResult.OK;
}
else if (0 != exitValue) {
} else if (0 != exitValue) {
// if it failed or was cancelled by timeout, result is ERROR
// cleanup the output path
FileUtil.deleteDir(new File(outputDirPath.toString()));
if (null != tempFilePath && Files.exists(tempFilePath)) {
tempFilePath.toFile().delete();
}
cleanup(outputDirPath, tempFilePath);
logger.log(Level.SEVERE, "PhotoRec carver returned error exit value = {0} when scanning {1}", // NON-NLS
new Object[]{exitValue, file.getName()}); // NON-NLS
return IngestModule.ProcessResult.ERROR;
@@ -217,6 +217,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
List<LayoutFile> theList = parser.parse(newAuditFile, id, file);
if (theList != null) { // if there were any results from carving, add the unallocated carving event to the reports list.
context.addFilesToJob(new ArrayList<>(theList));
services.fireModuleContentEvent(new ModuleContentEvent(theList.get(0))); // fire an event to update the tree
}
}
catch (IOException ex) {
@@ -233,6 +234,15 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
return IngestModule.ProcessResult.OK;
}
private void cleanup(Path outputDirPath, Path tempFilePath) {
// cleanup the output path
FileUtil.deleteDir(new File(outputDirPath.toString()));
if (null != tempFilePath && Files.exists(tempFilePath)) {
tempFilePath.toFile().delete();
}
}
/**
* @inheritDoc
@@ -120,7 +120,7 @@ public final class JythonModuleLoader {
interpreter.exec("import sys"); //NON-NLS
String path = Matcher.quoteReplacement(script.getParent());
interpreter.exec("sys.path.append('" + path + "')"); //NON-NLS
String moduleName = script.getName().replaceAll(".py", ""); //NON-NLS
String moduleName = script.getName().replaceAll("\\.py$", ""); //NON-NLS
// reload the module so that the changes made to it can be loaded.
interpreter.exec("import " + moduleName); //NON-NLS
@@ -30,7 +30,9 @@ import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A Tab-delimited text report of the files in the case.
@@ -68,8 +70,13 @@ import org.sleuthkit.datamodel.AbstractFile;
if (out != null) {
try {
out.close();
Case.getCurrentCase().addReport(reportPath, NbBundle.getMessage(this.getClass(),
"FileReportText.getName.text"), "");
} catch (IOException ex) {
logger.log(Level.WARNING, "Could not close output writer when ending report.", ex); //NON-NLS
} catch (TskCoreException ex) {
String errorMessage = String.format("Error adding %s to case as a report", reportPath); //NON-NLS
logger.log(Level.SEVERE, errorMessage, ex);
}
}
}
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 Basis Technology Corp.
* Copyright 2014-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -257,7 +257,7 @@ public class TimeLineController {
LOGGER.log(Level.INFO, "Beginning generation of timeline"); // NON-NLS
try {
SwingUtilities.invokeLater(() -> {
if (mainFrame != null) {
if (isWindowOpen()) {
mainFrame.close();
}
});
@@ -311,7 +311,8 @@ public class TimeLineController {
}
}
/** show the timeline window and prompt for rebuilding database */
/** 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).
@@ -636,12 +637,21 @@ public class TimeLineController {
/**
* prompt the user to rebuild and then rebuild if the user chooses to
*/
synchronized public boolean outOfDatePromptAndRebuild() {
synchronized private boolean outOfDatePromptAndRebuild() {
return showOutOfDateConfirmation() == JOptionPane.YES_OPTION
? rebuildRepo()
: false;
}
/**
* is the timeline window open.
*
* @return true if the timeline window is open
*/
synchronized private boolean isWindowOpen() {
return mainFrame != null && mainFrame.isOpened() && mainFrame.isVisible();
}
synchronized int showLastPopulatedWhileIngestingConfirmation() {
return JOptionPane.showConfirmDialog(mainFrame,
DO_REPOPULATE_MESSAGE,
@@ -703,7 +713,9 @@ public class TimeLineController {
case CANCELLED:
case COMPLETED:
//if we are doing incremental updates, drop this
outOfDatePromptAndRebuild();
if (isWindowOpen()) {
outOfDatePromptAndRebuild();
}
break;
}
}
@@ -718,7 +730,9 @@ public class TimeLineController {
case DATA_SOURCE_ADDED:
// Content content = (Content) evt.getNewValue();
//if we are doing incremental updates, drop this
outOfDatePromptAndRebuild();
if (isWindowOpen()) {
outOfDatePromptAndRebuild();
}
break;
case CURRENT_CASE:
OpenTimelineAction.invalidateController();
+2 -2
View File
@@ -1,4 +1,5 @@
<ivy-module version="2.0">
<info organisation="org.sleuthkit.autopsy" module="corelibs"/>
<configurations >
@@ -36,8 +37,7 @@
<dependency conf="autopsy_core->*" org="org.imgscalr" name="imgscalr-lib" rev="4.2" />
<!-- timeline and image analyzer -->
<dependency conf="autopsy_core->*" org="org.controlsfx" name="controlsfx" rev="8.20.8" />
<dependency org="org.controlsfx" name="openjfx-dialogs" rev="1.0.2"/>
<dependency conf="autopsy_core->*" org="org.controlsfx" name="controlsfx" rev="8.40.9" />
<!-- timeline and -->
<dependency conf="autopsy_core->*" org="joda-time" name="joda-time" rev="2.4" />
+4 -4
View File
@@ -12,7 +12,7 @@ file.reference.commons-lang3-3.0.jar=release/modules/ext/commons-lang3-3.0.jar
file.reference.commons-logging-1.1.2-javadoc.jar=release/modules/ext/commons-logging-1.1.2-javadoc.jar
file.reference.commons-logging-1.1.2-sources.jar=release/modules/ext/commons-logging-1.1.2-sources.jar
file.reference.commons-logging-1.1.2.jar=release/modules/ext/commons-logging-1.1.2.jar
file.reference.controlsfx-8.20.8.jar=release/modules/ext/controlsfx-8.20.8.jar
file.reference.controlsfx-8.40.9.jar=release/modules/ext/controlsfx-8.40.9.jar
file.reference.dom4j-1.6.1.jar=release/modules/ext/dom4j-1.6.1.jar
file.reference.geronimo-jms_1.1_spec-1.0.jar=release/modules/ext/geronimo-jms_1.1_spec-1.0.jar
file.reference.gson-1.4.jar=release/modules/ext/gson-1.4.jar
@@ -41,7 +41,7 @@ file.reference.jsr305-1.3.9.jar=release/modules/ext/jsr305-1.3.9.jar
file.reference.log4j-1.2.17.jar=release/modules/ext/log4j-1.2.17.jar
file.reference.logkit-1.0.1.jar=release/modules/ext/logkit-1.0.1.jar
file.reference.mail-1.4.3.jar=release/modules/ext/mail-1.4.3.jar
file.reference.openjfx-dialogs-1.0.2.jar=release/modules/ext/openjfx-dialogs-1.0.2.jar
file.reference.openjfx-dialogs-1.0.2.jar=release/modules/ext/openjfx-dialogs-1.0.3.jar
file.reference.platform-3.4.0.jar=release/modules/ext/platform-3.4.0.jar
file.reference.poi-3.8.jar=release/modules/ext/poi-3.8.jar
file.reference.poi-excelant-3.8.jar=release/modules/ext/poi-excelant-3.8.jar
@@ -59,6 +59,6 @@ file.reference.xml-apis-1.0.b2.jar=release/modules/ext/xml-apis-1.0.b2.jar
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.20.8.jar=release/modules/ext/controlsfx-8.20.8-javadoc.jar
javadoc.reference.controlsfx-8.40.9.jar=release/modules/ext/controlsfx-8.40.9-javadoc.jar
nbm.needs.restart=true
source.reference.controlsfx-8.20.8.jar=release/modules/ext/controlsfx-8.20.8-sources.jar
source.reference.controlsfx-8.40.9.jar=release/modules/ext/controlsfx-8.40.9-sources.jar
+4 -9
View File
@@ -64,7 +64,6 @@
<package>com.sun.mail.smtp</package>
<package>com.sun.mail.util</package>
<package>com.sun.mail.util.logging</package>
<package>javafx.scene.control</package>
<package>javassist</package>
<package>javassist.bytecode</package>
<package>javassist.bytecode.analysis</package>
@@ -616,10 +615,6 @@
<runtime-relative-path>ext/logkit-1.0.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/logkit-1.0.1.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/openjfx-dialogs-1.0.2.jar</runtime-relative-path>
<binary-origin>release/modules/ext/openjfx-dialogs-1.0.2.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/imgscalr-lib-4.2-javadoc.jar</runtime-relative-path>
<binary-origin>release/modules/ext/imgscalr-lib-4.2-javadoc.jar</binary-origin>
@@ -748,10 +743,6 @@
<runtime-relative-path>ext/jfxtras-fxml-8.0-r1-javadoc.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jfxtras-fxml-8.0-r1-javadoc.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/controlsfx-8.20.8.jar</runtime-relative-path>
<binary-origin>release/modules/ext/controlsfx-8.20.8.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/jfxtras-fxml-8.0-r1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/jfxtras-fxml-8.0-r1.jar</binary-origin>
@@ -776,6 +767,10 @@
<runtime-relative-path>ext/commons-lang3-3.0-javadoc.jar</runtime-relative-path>
<binary-origin>release/modules/ext/commons-lang3-3.0-javadoc.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/controlsfx-8.40.9.jar</runtime-relative-path>
<binary-origin>release/modules/ext/controlsfx-8.40.9.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/platform-3.4.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/platform-3.4.0.jar</binary-origin>
+2 -2
View File
@@ -2,7 +2,7 @@
<!-- You may freely edit this file. See harness/README in the NetBeans platform -->
<!-- for some information on what you could do (e.g. targets to override). -->
<!-- If you delete this file and reopen the project it will be recreated. -->
<project name="com.basistech.imageanalysis" default="netbeans" basedir=".">
<description>Builds, tests, and runs the project com.basistech.imageanalysis.</description>
<project name="org.sleuthkit.autopsy.imagegallery" default="netbeans" basedir=".">
<description>Builds, tests, and runs the project org.sleuthkit.autopsy.imagegallery.</description>
<import file="nbproject/build-impl.xml"/>
</project>
@@ -4,9 +4,10 @@ OpenIDE-Module-Long-Description=\
It is not available on the sleuthkit.org site and has been distributed to limited users.
OpenIDE-Module-Name=ImageGallery
OpenIDE-Module-Short-Description=Advanced image and video gallery
ImageGalleryOptionsPanel.jLabel1.text=<html>To minimize its startup times, Image Gallery will constantly update its internal database. <br />This can cause ingest to be slower if you do not need the Image Gallery features. <br />Use these settings to disable Image Gallery if you do not need it.</html>
ImageGalleryOptionsPanel.enabledForCaseBox.text=Enable Image Gallery updates for the current case.
ImageGalleryOptionsPanel.enabledByDefaultBox.text=Enable Image Gallery for new cases by default.
ImageGalleryOptionsPanel.jTextArea2.text=If Image Gallery is disabled, only the fact that an update is needed is recorded. If Image Gallery is enabled after ingest, it will do one bulk update based on the results form ingest. If Image Gallery is disabled, you will be prompted to enable it when attempting to open its window.
ImageGalleryOptionsPanel.jLabel3.text=
ImageGalleryOptionsPanel.enabledForCaseBox.toolTipText=If Image Gallery is disabled, only the fact that an update is needed is recorded. If Image Gallery is enabled after ingest, it will do one bulk update based on the results form ingest. If Image Gallery is disabled, you will be prompted to enable it when attempting to open its window.
ImageGalleryOptionsPanel.descriptionLabel.text=<html>To minimize its startup times, Image Gallery will constantly update its internal database. <br />This can cause ingest to be slower if you do not need the Image Gallery features. <br />Use these settings to disable Image Gallery if you do not need it.</html>
ImageGalleryOptionsPanel.furtherDescriptionArea.text=If Image Gallery is disabled, only the fact that an update is needed is recorded. If Image Gallery is enabled after ingest, it will do one bulk update based on the results form ingest. If Image Gallery is disabled, you will be prompted to enable it when attempting to open its window.
ImageGalleryOptionsPanel.unavailableDuringInjestLabel.text=This setting is unavailable during ingest.
ImageGalleryOptionsPanel.infoIconLabel.text=
@@ -70,7 +70,11 @@ public class FileIDSelectionModel {
public void clearAndSelectAll(Long... ids) {
selected.clear();
selected.addAll(Arrays.asList(ids));
setLastSelected(ids[ids.length - 1]);
if (ids.length > 0) {
setLastSelected(ids[ids.length - 1]);
} else {
setLastSelected(null);
}
}
public void clearAndSelect(Long id) {
@@ -1,90 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.imagegallery;
import java.util.Collection;
import java.util.EventListener;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.concurrent.Immutable;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
/** represents a change in the database for one or more files. */
@Immutable
public class FileUpdateEvent {
/** the obj_ids of affected files */
private final Set<Long> fileIDs;
/** the attribute that was modified */
private final DrawableAttribute<?> changedAttribute;
/** the type of update ( updated/removed) */
private final UpdateType updateType;
public UpdateType getUpdateType() {
return updateType;
}
public Collection<Long> getFileIDs() {
return fileIDs;
}
public DrawableAttribute<?> getChangedAttribute() {
return changedAttribute;
}
public static FileUpdateEvent newRemovedEvent(Collection<? extends Long> updatedFiles) {
return new FileUpdateEvent(updatedFiles, UpdateType.REMOVE, null);
}
/**
*
* @param updatedFiles the files that have been added or changed in the
* database
* @param changedAttribute the attribute that was changed for the files, or
* null if this represents new files
*
* @return a new FileUpdateEvent
*/
public static FileUpdateEvent newUpdateEvent(Collection<? extends Long> updatedFiles, DrawableAttribute<?> changedAttribute) {
return new FileUpdateEvent(updatedFiles, UpdateType.UPDATE, changedAttribute);
}
private FileUpdateEvent(Collection<? extends Long> updatedFiles, UpdateType updateType, DrawableAttribute<?> changedAttribute) {
this.fileIDs = new HashSet<>(updatedFiles);
this.updateType = updateType;
this.changedAttribute = changedAttribute;
}
static public enum UpdateType {
/** files have been added or updated in the db */
UPDATE,
/** files have been removed
* from the db */
REMOVE;
}
/** Interface for listening to FileUpdateEvents */
public static interface FileUpdateListener extends EventListener {
public void handleFileUpdate(FileUpdateEvent evt);
}
}
@@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.imagegallery;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
@@ -56,15 +57,16 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.History;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.HashSetManager;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupManager;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog;
import org.sleuthkit.autopsy.imagegallery.gui.SummaryTablePane;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.AbstractFile;
@@ -126,13 +128,15 @@ public final class ImageGalleryController {
private final GroupManager groupManager = new GroupManager(this);
private final HashSetManager hashSetManager = new HashSetManager();
private final CategoryManager categoryManager = new CategoryManager();
private final CategoryManager categoryManager = new CategoryManager(this);
private final DrawableTagsManager tagsManager = new DrawableTagsManager(null);
private StackPane fullUIStackPane;
private StackPane centralStackPane;
private Node infoOverlay;
private SleuthkitCase sleuthKitCase;
public ReadOnlyBooleanProperty getMetaDataCollapsed() {
return metaDataCollapsed.getReadOnlyProperty();
@@ -231,7 +235,6 @@ public final class ImageGalleryController {
IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> {
Platform.runLater(this::updateRegroupDisabled);
});
// metaDataCollapsed.bind(Toolbar.getDefault().showMetaDataProperty());
}
public ReadOnlyBooleanProperty getCanAdvance() {
@@ -333,7 +336,7 @@ public final class ImageGalleryController {
Platform.runLater(this::updateRegroupDisabled);
});
Thread th = new Thread(dbWorkerThread);
Thread th = new Thread(dbWorkerThread, "DB-Worker-Thread");
th.setDaemon(false); // we want it to go away when it is done
th.start();
}
@@ -344,19 +347,27 @@ public final class ImageGalleryController {
* @param theNewCase the case to configure the controller for
*/
public synchronized void setCase(Case theNewCase) {
this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), getSleuthKitCase());
if (Objects.nonNull(theNewCase)) {
this.sleuthKitCase = theNewCase.getSleuthkitCase();
this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), this);
setListeningEnabled(ImageGalleryModule.isEnabledforCase(theNewCase));
setStale(ImageGalleryModule.isDrawableDBStale(theNewCase));
setListeningEnabled(ImageGalleryModule.isEnabledforCase(theNewCase));
setStale(ImageGalleryModule.isDrawableDBStale(theNewCase));
// if we add this line icons are made as files are analyzed rather than on demand.
// db.addUpdatedFileListener(IconCache.getDefault());
restartWorker();
historyManager.clear();
groupManager.setDB(db);
hashSetManager.setDb(db);
categoryManager.setDb(db);
SummaryTablePane.getDefault().refresh();
// if we add this line icons are made as files are analyzed rather than on demand.
// db.addUpdatedFileListener(IconCache.getDefault());
restartWorker();
historyManager.clear();
groupManager.setDB(db);
hashSetManager.setDb(db);
categoryManager.setDb(db);
tagsManager.setAutopsyTagsManager(theNewCase.getServices().getTagsManager());
tagsManager.registerListener(groupManager);
tagsManager.registerListener(categoryManager);
} else {
reset();
}
}
/**
@@ -370,9 +381,11 @@ public final class ImageGalleryController {
Platform.runLater(() -> {
historyManager.clear();
});
Category.clearTagNames();
tagsManager.clearFollowUpTagName();
tagsManager.unregisterListener(groupManager);
tagsManager.unregisterListener(categoryManager);
Toolbar.getDefault().reset();
Toolbar.getDefault(this).reset();
groupManager.clear();
if (db != null) {
db.closeDBCon();
@@ -476,6 +489,19 @@ public final class ImageGalleryController {
setStale(true);
}
break;
case CONTENT_TAG_ADDED:
final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt;
if (getDatabase().isInDB((tagAddedEvent).getTag().getContent().getId())) {
getTagsManager().fireTagAddedEvent(tagAddedEvent);
}
break;
case CONTENT_TAG_DELETED:
final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) evt;
if (getDatabase().isInDB((tagDeletedEvent).getTag().getContent().getId())) {
getTagsManager().fireTagDeletedEvent(tagDeletedEvent);
}
break;
}
});
}
@@ -488,6 +514,12 @@ public final class ImageGalleryController {
return categoryManager;
}
public DrawableTagsManager getTagsManager() {
return tagsManager;
}
// @@@ REVIEW IF THIS SHOLD BE STATIC...
//TODO: concept seems like the controller deal with how much work to do at a given time
// @@@ review this class for synchronization issues (i.e. reset and cancel being called, add, etc.)
@@ -551,12 +583,8 @@ public final class ImageGalleryController {
}
}
public SleuthkitCase getSleuthKitCase() throws IllegalStateException {
if (Case.isCaseOpen()) {
return Case.getCurrentCase().getSleuthkitCase();
} else {
throw new IllegalStateException("No Case is open!");
}
public synchronized SleuthkitCase getSleuthKitCase() {
return sleuthKitCase;
}
/**
@@ -21,18 +21,18 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="21" pref="21" max="-2" attributes="0"/>
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
<Component id="unavailableDuringInjestLabel" min="-2" max="-2" attributes="0"/>
</Group>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="21" pref="21" max="-2" attributes="0"/>
<Component id="jLabel3" min="-2" max="-2" attributes="0"/>
<Component id="infoIconLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="jTextArea2" max="32767" attributes="0"/>
<Component id="furtherDescriptionArea" max="32767" attributes="0"/>
</Group>
<Component id="enabledByDefaultBox" min="-2" max="-2" attributes="0"/>
<Component id="enabledForCaseBox" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="jLabel1" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="descriptionLabel" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace pref="46" max="32767" attributes="0"/>
@@ -43,17 +43,17 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
<Component id="descriptionLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="enabledByDefaultBox" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="enabledForCaseBox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="unavailableDuringInjestLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="separate" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jLabel3" min="-2" max="-2" attributes="0"/>
<Component id="jTextArea2" min="-2" max="-2" attributes="0"/>
<Component id="infoIconLabel" min="-2" max="-2" attributes="0"/>
<Component id="furtherDescriptionArea" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="45" max="-2" attributes="0"/>
</Group>
@@ -84,14 +84,14 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="enabledForCaseBoxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JLabel" name="jLabel1">
<Component class="javax.swing.JLabel" name="descriptionLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.descriptionLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JTextArea" name="jTextArea2">
<Component class="javax.swing.JTextArea" name="furtherDescriptionArea">
<Properties>
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
<Color blue="f0" green="f0" red="f0" type="rgb"/>
@@ -103,7 +103,7 @@
<Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="5"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.jTextArea2.text" replaceFormat="NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.furtherDescriptionArea.text" replaceFormat="NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="wrapStyleWord" type="boolean" value="true"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
@@ -111,23 +111,23 @@
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="jLabel3">
<Component class="javax.swing.JLabel" name="infoIconLabel">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/imagegallery/images/info-icon-16.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.jLabel3.text" replaceFormat="NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.infoIconLabel.text" replaceFormat="NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="jLabel2">
<Component class="javax.swing.JLabel" name="unavailableDuringInjestLabel">
<Properties>
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
<Image iconType="3" name="/org/sleuthkit/autopsy/imagegallery/images/warning16.png"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.jLabel2.text" replaceFormat="NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/imagegallery/Bundle.properties" key="ImageGalleryOptionsPanel.unavailableDuringInjestLabel.text" replaceFormat="NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
@@ -59,10 +59,10 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
enabledByDefaultBox = new javax.swing.JCheckBox();
enabledForCaseBox = new javax.swing.JCheckBox();
jLabel1 = new javax.swing.JLabel();
jTextArea2 = new javax.swing.JTextArea();
jLabel3 = new javax.swing.JLabel();
jLabel2 = new javax.swing.JLabel();
descriptionLabel = new javax.swing.JLabel();
furtherDescriptionArea = new javax.swing.JTextArea();
infoIconLabel = new javax.swing.JLabel();
unavailableDuringInjestLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(enabledByDefaultBox, org.openide.util.NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.enabledByDefaultBox.text")); // NOI18N
enabledByDefaultBox.addActionListener(new java.awt.event.ActionListener() {
@@ -79,22 +79,22 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
}
});
org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.jLabel1.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.descriptionLabel.text")); // NOI18N
jTextArea2.setBackground(new java.awt.Color(240, 240, 240));
jTextArea2.setColumns(20);
jTextArea2.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N
jTextArea2.setLineWrap(true);
jTextArea2.setRows(5);
jTextArea2.setText(NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.jTextArea2.text")); // NOI18N
jTextArea2.setWrapStyleWord(true);
jTextArea2.setPreferredSize(new java.awt.Dimension(378, 74));
furtherDescriptionArea.setBackground(new java.awt.Color(240, 240, 240));
furtherDescriptionArea.setColumns(20);
furtherDescriptionArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N
furtherDescriptionArea.setLineWrap(true);
furtherDescriptionArea.setRows(5);
furtherDescriptionArea.setText(NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.furtherDescriptionArea.text")); // NOI18N
furtherDescriptionArea.setWrapStyleWord(true);
furtherDescriptionArea.setPreferredSize(new java.awt.Dimension(378, 74));
jLabel3.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/imagegallery/images/info-icon-16.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(jLabel3, NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.jLabel3.text")); // NOI18N
infoIconLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/imagegallery/images/info-icon-16.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(infoIconLabel, NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.infoIconLabel.text")); // NOI18N
jLabel2.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/imagegallery/images/warning16.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(jLabel2, NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.jLabel2.text")); // NOI18N
unavailableDuringInjestLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/imagegallery/images/warning16.png"))); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(unavailableDuringInjestLabel, NbBundle.getMessage(ImageGalleryOptionsPanel.class, "ImageGalleryOptionsPanel.unavailableDuringInjestLabel.text")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
@@ -105,33 +105,33 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(21, 21, 21)
.addComponent(jLabel2))
.addComponent(unavailableDuringInjestLabel))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(layout.createSequentialGroup()
.addGap(21, 21, 21)
.addComponent(jLabel3)
.addComponent(infoIconLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jTextArea2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(furtherDescriptionArea, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(enabledByDefaultBox)
.addComponent(enabledForCaseBox)
.addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addContainerGap(46, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(enabledByDefaultBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(enabledForCaseBox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(jLabel2)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(unavailableDuringInjestLabel)
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jLabel3)
.addComponent(jTextArea2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(infoIconLabel)
.addComponent(furtherDescriptionArea, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(45, 45, 45))
);
}// </editor-fold>//GEN-END:initComponents
@@ -174,11 +174,11 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel descriptionLabel;
private javax.swing.JCheckBox enabledByDefaultBox;
private javax.swing.JCheckBox enabledForCaseBox;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JTextArea jTextArea2;
private javax.swing.JTextArea furtherDescriptionArea;
private javax.swing.JLabel infoIconLabel;
private javax.swing.JLabel unavailableDuringInjestLabel;
// End of variables declaration//GEN-END:variables
}
@@ -34,10 +34,9 @@ import org.openide.util.NbBundle.Messages;
import org.openide.windows.Mode;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.gui.GroupPane;
import org.sleuthkit.autopsy.imagegallery.gui.MetaDataPane;
import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.GroupPane;
import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.MetaDataPane;
import org.sleuthkit.autopsy.imagegallery.gui.StatusBar;
import org.sleuthkit.autopsy.imagegallery.gui.SummaryTablePane;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
@@ -145,13 +144,13 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
fullUIStack.getChildren().add(borderPane);
splitPane = new SplitPane();
borderPane.setCenter(splitPane);
borderPane.setTop(Toolbar.getDefault());
borderPane.setTop(Toolbar.getDefault(controller));
borderPane.setBottom(new StatusBar(controller));
metaDataTable = new MetaDataPane(controller);
navPanel = new NavPanel(controller);
leftPane = new VBox(navPanel, SummaryTablePane.getDefault());
leftPane = new VBox(navPanel, new SummaryTablePane(controller));
SplitPane.setResizableWithParent(leftPane, Boolean.FALSE);
SplitPane.setResizableWithParent(groupPane, Boolean.TRUE);
SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE);
@@ -1,137 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 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.imagegallery;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SplitMenuButton;
import javafx.scene.image.ImageView;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Contains static methods for dealing with Tags in ImageGallery
*/
public class TagUtils {
private static final String follow_Up = "Follow Up";
private static TagName followUpTagName;
private final static List<TagListener> listeners = new ArrayList<>();
synchronized public static TagName getFollowUpTagName() throws TskCoreException {
if (followUpTagName == null) {
followUpTagName = getTagName(follow_Up);
}
return followUpTagName;
}
static public Collection<TagName> getNonCategoryTagNames() {
List<TagName> nonCatTagNames = new ArrayList<>();
List<TagName> allTagNames;
try {
allTagNames = Case.getCurrentCase().getServices().getTagsManager().getAllTagNames();
for (TagName tn : allTagNames) {
if (tn.getDisplayName().startsWith(Category.CATEGORY_PREFIX) == false) {
nonCatTagNames.add(tn);
}
}
} catch (TskCoreException | IllegalStateException ex) {
Logger.getLogger(TagUtils.class.getName()).log(Level.WARNING, "couldn't access case", ex);
}
return nonCatTagNames;
}
synchronized static public TagName getTagName(String displayName) throws TskCoreException {
try {
final TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager();
for (TagName tn : tagsManager.getAllTagNames()) {
if (displayName.equals(tn.getDisplayName())) {
return tn;
}
}
try {
return tagsManager.addTagName(displayName);
} catch (TagsManager.TagNameAlreadyExistsException ex) {
throw new TskCoreException("tagame exists but wasn't found", ex);
}
} catch (IllegalStateException ex) {
Logger.getLogger(TagUtils.class.getName()).log(Level.SEVERE, "Case was closed out from underneath", ex);
throw new TskCoreException("Case was closed out from underneath", ex);
}
}
public static void fireChange(Collection<Long> ids) {
Set<TagUtils.TagListener> listenersCopy = new HashSet<TagUtils.TagListener>(listeners);
synchronized (listeners) {
listenersCopy.addAll(listeners);
}
for (TagListener list : listenersCopy) {
list.handleTagsChanged(ids);
}
}
public static void registerListener(TagListener aThis) {
synchronized (listeners) {
listeners.add(aThis);
}
}
public static void unregisterListener(TagListener aThis) {
synchronized (listeners) {
listeners.remove(aThis);
}
}
/**
* @param tn the value of tn
*/
static public MenuItem createSelTagMenuItem(final TagName tn, final SplitMenuButton tagSelectedMenuButton) {
final MenuItem menuItem = new MenuItem(tn.getDisplayName(), new ImageView(DrawableAttribute.TAGS.getIcon()));
menuItem.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent t) {
AddDrawableTagAction.getInstance().addTag(tn, "");
tagSelectedMenuButton.setText(tn.getDisplayName());
tagSelectedMenuButton.setOnAction(this);
}
});
return menuItem;
}
public static interface TagListener {
public void handleTagsChanged(Collection<Long> ids);
}
}
@@ -119,28 +119,29 @@ public enum ThumbnailCache {
/**
* load a thumbnail from the disk based cache for the given file, or
* generate and save a new thumnbail if one doesn't already exist
* generate and save a new thumbnail if one doesn't already exist
*
* @param file the file to load a thumbnail of
* @param file the DrawableFile to load a thumbnail of
*
* @return an optional containing a thumbnail, or null if a thumbnail
* couldn't be loaded or generated
* @return an (possibly empty) optional containing a thumbnail
*/
private Optional<Image> load(DrawableFile<?> file) {
Image thumbnail = null;
File cacheFile;
try {// try to load the thumbnail from disk
cacheFile = getCacheFile(file.getId());
Image thumbnail;
if (cacheFile.exists()) {
// If a thumbnail file is already saved locally, load it
try {
int dim = iconSize.get();
thumbnail = new Image(Utilities.toURI(cacheFile).toURL().toString(), dim, dim, true, false, true);
} catch (MalformedURLException ex) {
Exceptions.printStackTrace(ex);
try {
thumbnail = getCacheFile(file.getId()).map((File cachFile) -> {
if (cachFile.exists()) {
// If a thumbnail file is already saved locally, load it
try {
int dim = iconSize.get();
return new Image(Utilities.toURI(cachFile).toURL().toString(), dim, dim, true, false, true);
} catch (MalformedURLException ex) {
LOGGER.log(Level.WARNING, "Unable to parse cache file path..");
}
}
}
return null;
}).orElse(null);
} catch (IllegalStateException e) {
LOGGER.log(Level.WARNING, "can't load icon when no case is open");
return Optional.empty();
@@ -153,9 +154,23 @@ public enum ThumbnailCache {
return Optional.ofNullable(thumbnail); //return icon, or null if generation failed
}
private static File getCacheFile(long id) {
// @@@ should use ImageUtils.getFile();
return new File(Case.getCurrentCase().getCacheDirectory() + File.separator + id + ".png");
/**
* get a File to store the cached icon in.
*
* @param id the obj id of the file to get a cache file for
*
* @return a Optional containing a File to store the cahced icon in or an
* empty optional if there was a
* problem.
*/
private static Optional<File> getCacheFile(long id) {
try {
// @@@ should use ImageUtils.getFile();
return Optional.of(new File(Case.getCurrentCase().getCacheDirectory() + File.separator + id + ".png"));
} catch (IllegalStateException e) {
LOGGER.log(Level.WARNING, "Failed to create cache file.{0}", e.getLocalizedMessage());
return Optional.empty();
}
}
/**
@@ -255,15 +270,15 @@ public enum ThumbnailCache {
* @param bi the thumbnail to save for the given DrawableFile
*/
private void saveIcon(final DrawableFile<?> file, final Image bi) {
try {
if (bi != null) {
File f = getCacheFile(file.getId());
//convert back to swing to save
ImageIO.write(SwingFXUtils.fromFXImage(bi, null), FORMAT, f);
}
} catch (IllegalArgumentException | IOException ex) {
//LOGGER.log(Level.WARNING, "failed to save generated icon ", ex);
LOGGER.log(Level.WARNING, "failed to save generated icon");
if (bi != null) {
getCacheFile(file.getId()).ifPresent((File cacheFile) -> {
try {
//convert back to swing to save
ImageIO.write(SwingFXUtils.fromFXImage(bi, null), FORMAT, cacheFile);
} catch (IllegalArgumentException | IOException ex) {
LOGGER.log(Level.WARNING, "failed to save generated icon");
}
});
}
}
}
@@ -18,23 +18,25 @@
*/
package org.sleuthkit.autopsy.imagegallery.actions;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Menu;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;
import org.openide.util.Utilities;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
import org.sleuthkit.autopsy.imagegallery.FileUpdateEvent;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
@@ -45,66 +47,69 @@ import org.sleuthkit.datamodel.TskCoreException;
* diverged from autopsy action, make this extend from controlsfx Action
*/
public class AddDrawableTagAction extends AddTagAction {
private static final Logger LOGGER = Logger.getLogger(AddDrawableTagAction.class.getName());
// This class is a singleton to support multi-selection of nodes, since
// org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every
// node in the array returns a reference to the same action object from Node.getActions(boolean).
private static AddDrawableTagAction instance;
public static synchronized AddDrawableTagAction getInstance() {
if (null == instance) {
instance = new AddDrawableTagAction();
}
return instance;
private final ImageGalleryController controller;
public AddDrawableTagAction(ImageGalleryController controller) {
this.controller = controller;
}
private AddDrawableTagAction() {
}
public Menu getPopupMenu() {
return new TagMenu();
return new TagMenu(controller);
}
@Override
protected String getActionDisplayName() {
return Utilities.actionsGlobalContext().lookupAll(AbstractFile.class).size() > 1 ? "Tag Files" : "Tag File";
}
@Override
public void addTag(TagName tagName, String comment) {
Set<Long> selectedFiles = new HashSet<>(FileIDSelectionModel.getInstance().getSelected());
addTagsToFiles(tagName, comment, selectedFiles);
}
@Override
public void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles){
public void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles) {
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
for (Long fileID : selectedFiles) {
try {
DrawableFile<?> file = ImageGalleryController.getDefault().getFileFromId(fileID);
final DrawableFile<?> file = controller.getFileFromId(fileID);
LOGGER.log(Level.INFO, "tagging {0} with {1} and comment {2}", new Object[]{file.getName(), tagName.getDisplayName(), comment});
Case.getCurrentCase().getServices().getTagsManager().addContentTag(file, tagName, comment);
} catch (IllegalStateException ex) {
LOGGER.log(Level.SEVERE, "Case was closed out from underneath Updatefile task", ex);
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error tagging result", ex);
JOptionPane.showMessageDialog(null, "Unable to tag " + fileID + ".", "Tagging Error", JOptionPane.ERROR_MESSAGE);
}
//make sure rest of ui hears category change.
ImageGalleryController.getDefault().getGroupManager().handleFileUpdate(FileUpdateEvent.newUpdateEvent(Collections.singleton(fileID), DrawableAttribute.TAGS));
// check if the same tag is being added for the same abstract file.
DrawableTagsManager tagsManager = controller.getTagsManager();
List<ContentTag> contentTags = tagsManager.getContentTagsByContent(file);
Optional<TagName> duplicateTagName = contentTags.stream()
.map(ContentTag::getName)
.filter(tagName::equals)
.findAny();
if (duplicateTagName.isPresent()) {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.WARNING, "Unable to tag " + file.getName() + ". It has already been tagged as \"" + tagName.getDisplayName() + ". Cannot reapply the same tag.", ButtonType.OK);
alert.setHeaderText("Tag Error");
alert.show();
});
} else {
controller.getTagsManager().addContentTag(file, tagName, comment);
}
} catch (TskCoreException tskCoreException) {
LOGGER.log(Level.SEVERE, "Error tagging result", tskCoreException);
Platform.runLater(() -> {
new Alert(Alert.AlertType.ERROR, "Unable to file " + fileID + ".").show();
});
}
}
refreshDirectoryTree();
return null;
}
@Override
protected void done() {
super.done();
@@ -114,7 +119,6 @@ public class AddDrawableTagAction extends AddTagAction {
LOGGER.log(Level.SEVERE, "unexpected exception while tagging files", ex);
}
}
}.execute();
}
}
@@ -18,26 +18,17 @@
*/
package org.sleuthkit.autopsy.imagegallery.actions;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Collection;
import java.util.Set;
import java.util.logging.Level;
import javafx.event.ActionEvent;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javax.swing.SwingUtilities;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog;
import org.sleuthkit.autopsy.actions.GetTagNameDialog;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* An abstract base class for actions that allow users to tag SleuthKit data
@@ -45,20 +36,10 @@ import org.sleuthkit.datamodel.TskCoreException;
*
* //TODO: this class started as a cut and paste from
* org.sleuthkit.autopsy.actions.AddTagAction and needs to be
* refactor or reintegrated to the AddTagAction hierarchy of Autopysy.
* refactored or reintegrated to the AddTagAction hierarchy of Autopysy.
*/
abstract class AddTagAction {
@SuppressWarnings("deprecation")
protected void refreshDirectoryTree() {
/* Note: this is a hack. In an ideal world, TagsManager would fire
* events so that the directory tree would refresh. But, we haven't
* had a chance to add that so, we fire these events and the tree
* refreshes based on them.
*/
IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent("TagAction", BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE)); //NON-NLS
}
protected static final String NO_COMMENT = "";
/**
@@ -88,82 +69,61 @@ abstract class AddTagAction {
// to be reworked.
protected class TagMenu extends Menu {
TagMenu() {
TagMenu(ImageGalleryController controller) {
super(getActionDisplayName());
// Get the current set of tag names.
TagsManager tagsManager = Case.getCurrentCase().getServices().getTagsManager();
List<TagName> tagNames = null;
try {
tagNames = tagsManager.getAllTagNames();
} catch (TskCoreException ex) {
Logger.getLogger(TagsManager.class.getName()).log(Level.SEVERE, "Failed to get tag names", ex);
}
// Create a "Quick Tag" sub-menu.
Menu quickTagMenu = new Menu("Quick Tag");
getItems().add(quickTagMenu);
// Each tag name in the current set of tags gets its own menu item in
// the "Quick Tags" sub-menu. Selecting one of these menu items adds
// a tag with the associated tag name.
if (null != tagNames && !tagNames.isEmpty()) {
for (final TagName tagName : tagNames) {
if (tagName.getDisplayName().startsWith(Category.CATEGORY_PREFIX) == false) {
MenuItem tagNameItem = new MenuItem(tagName.getDisplayName());
tagNameItem.setOnAction((ActionEvent t) -> {
addTag(tagName, NO_COMMENT);
refreshDirectoryTree();
});
quickTagMenu.getItems().add(tagNameItem);
}
}
} else {
/* Each non-Category tag name in the current set of tags gets its
* own menu item in the "Quick Tags" sub-menu. Selecting one of
* these menu items adds a tag with the associated tag name. */
Collection<TagName> tagNames = controller.getTagsManager().getNonCategoryTagNames();
if (tagNames.isEmpty()) {
MenuItem empty = new MenuItem("No tags");
empty.setDisable(true);
quickTagMenu.getItems().add(empty);
} else {
for (final TagName tagName : tagNames) {
MenuItem tagNameItem = new MenuItem(tagName.getDisplayName());
tagNameItem.setOnAction((ActionEvent t) -> {
addTag(tagName, NO_COMMENT);
});
quickTagMenu.getItems().add(tagNameItem);
}
}
// quickTagMenu.addSeparator();
// The "Quick Tag" menu also gets an "Choose Tag..." menu item.
// Selecting this item initiates a dialog that can be used to create
// or select a tag name and adds a tag with the resulting name.
/* The "Quick Tag" menu also gets an "New Tag..." menu item.
* Selecting this item initiates a dialog that can be used to create
* or select a tag name and adds a tag with the resulting name. */
MenuItem newTagMenuItem = new MenuItem("New Tag...");
newTagMenuItem.setOnAction((ActionEvent t) -> {
try {
SwingUtilities.invokeAndWait(() -> {
TagName tagName = GetTagNameDialog.doDialog();
if (tagName != null) {
addTag(tagName, NO_COMMENT);
refreshDirectoryTree();
}
});
} catch (InterruptedException | InvocationTargetException ex) {
Exceptions.printStackTrace(ex);
}
SwingUtilities.invokeLater(() -> {
TagName tagName = GetTagNameDialog.doDialog();
if (tagName != null) {
addTag(tagName, NO_COMMENT);
}
});
});
quickTagMenu.getItems().add(newTagMenuItem);
// Create a "Choose Tag and Comment..." menu item. Selecting this item initiates
// a dialog that can be used to create or select a tag name with an
// optional comment and adds a tag with the resulting name.
/* Create a "Tag and Comment..." menu item. Selecting this item
* initiates a dialog that can be used to create or select a tag
* name with an optional comment and adds a tag with the resulting
* name. */
MenuItem tagAndCommentItem = new MenuItem("Tag and Comment...");
tagAndCommentItem.setOnAction((ActionEvent t) -> {
try {
SwingUtilities.invokeAndWait(() -> {
GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog();
if (null != tagNameAndComment) {
if (tagNameAndComment.getTagName().getDisplayName().startsWith(Category.CATEGORY_PREFIX)) {
new CategorizeAction().addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
} else {
AddDrawableTagAction.getInstance().addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
}
refreshDirectoryTree();
SwingUtilities.invokeLater(() -> {
GetTagNameAndCommentDialog.TagNameAndComment tagNameAndComment = GetTagNameAndCommentDialog.doDialog();
if (null != tagNameAndComment) {
if (CategoryManager.isCategoryTagName(tagNameAndComment.getTagName())) {
new CategorizeAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
} else {
new AddDrawableTagAction(controller).addTag(tagNameAndComment.getTagName(), tagNameAndComment.getComment());
}
});
} catch (InterruptedException | InvocationTargetException ex) {
Exceptions.printStackTrace(ex);
}
}
});
});
getItems().add(tagAndCommentItem);
}
@@ -18,27 +18,26 @@
*/
package org.sleuthkit.autopsy.imagegallery.actions;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.event.ActionEvent;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javax.swing.JOptionPane;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
import org.sleuthkit.autopsy.imagegallery.FileUpdateEvent;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupKey;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
@@ -54,13 +53,13 @@ public class CategorizeAction extends AddTagAction {
private final ImageGalleryController controller;
public CategorizeAction() {
public CategorizeAction(ImageGalleryController controller) {
super();
this.controller = ImageGalleryController.getDefault();
this.controller = controller;
}
static public Menu getPopupMenu() {
return new CategoryMenu();
public Menu getPopupMenu() {
return new CategoryMenu(controller);
}
@Override
@@ -84,13 +83,17 @@ public class CategorizeAction extends AddTagAction {
}
}
public void enforceOneCat(TagName name, String string) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
/**
* Instances of this class implement a context menu user interface for
* selecting a category
*/
static private class CategoryMenu extends Menu {
CategoryMenu() {
CategoryMenu(ImageGalleryController controller) {
super("Categorize");
// Each category get an item in the sub-menu. Selecting one of these menu items adds
@@ -99,8 +102,8 @@ public class CategorizeAction extends AddTagAction {
MenuItem categoryItem = new MenuItem(cat.getDisplayName());
categoryItem.setOnAction((ActionEvent t) -> {
final CategorizeAction categorizeAction = new CategorizeAction();
categorizeAction.addTag(cat.getTagName(), NO_COMMENT);
final CategorizeAction categorizeAction = new CategorizeAction(controller);
categorizeAction.addTag(controller.getCategoryManager().getTagName(cat), NO_COMMENT);
});
categoryItem.setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber()))));
getItems().add(categoryItem);
@@ -123,36 +126,40 @@ public class CategorizeAction extends AddTagAction {
@Override
public void run() {
final CategoryManager categoryManager = controller.getCategoryManager();
final DrawableTagsManager tagsManager = controller.getTagsManager();
try {
DrawableFile<?> file = controller.getFileFromId(fileID); //drawable db
Category oldCat = file.getCategory();
// remove file from old category group
controller.getGroupManager().removeFromGroup(new GroupKey<Category>(DrawableAttribute.CATEGORY, oldCat), fileID); //memory
//remove old category tag if necessary
List<ContentTag> allContentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByContent(file); //tsk db
for (ContentTag ct : allContentTags) {
//this is bad: treating tags as categories as long as their names start with prefix
//TODO: abandon using tags for categories and instead add a new column to DrawableDB
if (ct.getName().getDisplayName().startsWith(Category.CATEGORY_PREFIX)) {
Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(ct); //tsk db
controller.getCategoryManager().decrementCategoryCount(Category.fromDisplayName(ct.getName().getDisplayName())); //memory/drawable db
final List<ContentTag> fileTags = tagsManager.getContentTagsByContent(file);
if (tagName == categoryManager.getTagName(Category.ZERO)) {
// delete all cat tags for cat-0
fileTags.stream()
.filter(tag -> CategoryManager.isCategoryTagName(tag.getName()))
.forEach((ct) -> {
try {
tagsManager.deleteContentTag(ct);
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error removing old categories result", ex);
}
});
} else {
//add cat tag if no existing cat tag for that cat
if (fileTags.stream()
.map(Tag::getName)
.filter(tagName::equals)
.collect(Collectors.toList()).isEmpty()) {
tagsManager.addContentTag(file, tagName, comment);
}
}
controller.getCategoryManager().incrementCategoryCount(Category.fromDisplayName(tagName.getDisplayName())); //memory/drawable db
if (tagName != Category.ZERO.getTagName()) { // no tags for cat-0
Case.getCurrentCase().getServices().getTagsManager().addContentTag(file, tagName, comment); //tsk db
}
//make sure rest of ui hears category change.
controller.getGroupManager().handleFileUpdate(FileUpdateEvent.newUpdateEvent(Collections.singleton(fileID), DrawableAttribute.CATEGORY)); //memory/ui
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error categorizing result", ex);
JOptionPane.showMessageDialog(null, "Unable to categorize " + fileID + ".", "Categorizing Error", JOptionPane.ERROR_MESSAGE);
}
refreshDirectoryTree();
}
}
}
@@ -0,0 +1,67 @@
/*
* 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.imagegallery.actions;
import java.util.List;
import java.util.logging.Level;
import javafx.event.ActionEvent;
import javax.swing.SwingWorker;
import org.controlsfx.control.action.Action;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Action to delete the follow up tag a
*/
public class DeleteFollowUpTagAction extends Action {
private static final Logger LOGGER = Logger.getLogger(DeleteFollowUpTagAction.class.getName());
public DeleteFollowUpTagAction(final ImageGalleryController controller, final DrawableFile<?> file) {
super("Delete Follow Up Tag");
setEventHandler((ActionEvent t) -> {
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
final DrawableTagsManager tagsManager = controller.getTagsManager();
try {
final TagName followUpTagName = tagsManager.getFollowUpTagName();
List<ContentTag> contentTagsByContent = tagsManager.getContentTagsByContent(file);
for (ContentTag ct : contentTagsByContent) {
if (ct.getName().getDisplayName().equals(followUpTagName.getDisplayName())) {
tagsManager.deleteContentTag(ct);
}
}
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Failed to delete follow up tag.", ex);
}
return null;
}
}.execute();
});
}
}
@@ -28,7 +28,7 @@ import javafx.scene.image.ImageView;
import org.controlsfx.control.action.Action;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
/**
* Marks the currently fisplayed group as "seen" and advances to the next unseen
@@ -51,19 +51,21 @@ public class NextUnseenGroup extends Action {
this.controller = controller;
setGraphic(new ImageView(ADVANCE));
//TODO: do we need both these listeners?
controller.getGroupManager().getAnalyzedGroups().addListener((Observable observable) -> {
Platform.runLater(this::updateButton);
});
controller.getGroupManager().getUnSeenGroups().addListener((Observable observable) -> {
updateButton();
Platform.runLater(this::updateButton);
});
setEventHandler((ActionEvent t) -> {
Optional.ofNullable(controller.viewState())
.map(ObjectExpression<GroupViewState>::getValue)
.map(GroupViewState::getGroup)
.ifPresent(controller.getGroupManager()::markGroupSeen);
.ifPresent(group -> controller.getGroupManager().markGroupSeen(group, true));
if (false == controller.getGroupManager().getUnSeenGroups().isEmpty()) {
controller.advance(GroupViewState.tile(controller.getGroupManager().getUnSeenGroups().get(0)));
@@ -20,52 +20,42 @@ package org.sleuthkit.autopsy.imagegallery.datamodel;
import java.util.Map;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.scene.paint.Color;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.TagUtils;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Enum to represent the six categories in the DHs image categorization scheme.
*/
public enum Category implements Comparable<Category> {
public enum Category {
ZERO(Color.LIGHTGREY, 0, "CAT-0, Uncategorized"),
ONE(Color.RED, 1, "CAT-1, Child Exploitation (Illegal)"),
TWO(Color.ORANGE, 2, "CAT-2, Child Exploitation (Non-Illegal/Age Difficult)"),
THREE(Color.YELLOW, 3, "CAT-3, CGI/Animation (Child Exploitive)"),
FOUR(Color.BISQUE, 4, "CAT-4, Exemplar/Comparison (Internal Use Only)"),
FIVE(Color.GREEN, 5, "CAT-5, Non-pertinent");
/* This order of declaration is required so that Enum's compareTo method
* preserves the fact that lower category numbers are first/most sever,
* except 0 which is last */
ONE(Color.RED, 1, "CAT-1: Child Exploitation (Illegal)"),
TWO(Color.ORANGE, 2, "CAT-2: Child Exploitation (Non-Illegal/Age Difficult)"),
THREE(Color.YELLOW, 3, "CAT-3: CGI/Animation (Child Exploitive)"),
FOUR(Color.BISQUE, 4, "CAT-4: Exemplar/Comparison (Internal Use Only)"),
FIVE(Color.GREEN, 5, "CAT-5: Non-pertinent"),
ZERO(Color.LIGHTGREY, 0, "CAT-0: Uncategorized");
/** map from displayName to enum value */
private static final Map<String, Category> nameMap
= Stream.of(values()).collect(Collectors.toMap(Category::getDisplayName,
= Stream.of(values()).collect(Collectors.toMap(
Category::getDisplayName,
Function.identity()));
public static final String CATEGORY_PREFIX = "CAT-";
public static Category fromDisplayName(String displayName) {
return nameMap.get(displayName);
}
/**
* Use when closing a case to make sure everything is re-initialized in the
* next case.
*/
public static void clearTagNames() {
Category.ZERO.tagName = null;
Category.ONE.tagName = null;
Category.TWO.tagName = null;
Category.THREE.tagName = null;
Category.FOUR.tagName = null;
Category.FIVE.tagName = null;
public static boolean isCategoryName(String tName) {
return nameMap.containsKey(tName);
}
private TagName tagName;
public static boolean isNotCategoryName(String tName) {
return nameMap.containsKey(tName) == false;
}
private final Color color;
@@ -96,19 +86,4 @@ public enum Category implements Comparable<Category> {
return displayName;
}
/**
* get the TagName used to store this Category in the main autopsy db.
*
* @return the TagName used for this Category
*/
public TagName getTagName() {
if (tagName == null) {
try {
tagName = TagUtils.getTagName(displayName);
} catch (TskCoreException ex) {
Logger.getLogger(Category.class.getName()).log(Level.SEVERE, "failed to get TagName for " + displayName, ex);
}
}
return tagName;
}
}
@@ -20,12 +20,23 @@
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAdder;
import java.util.logging.Level;
import javax.annotation.concurrent.Immutable;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Provides a cached view of the number of files per category, and fires
@@ -43,6 +54,10 @@ public class CategoryManager {
private static final java.util.logging.Logger LOGGER = Logger.getLogger(CategoryManager.class.getName());
private final ImageGalleryController controller;
/**
* the DrawableDB that backs the category counts cache. The counts are
* initialized from this, and the counting of CAT-0 is always delegated to
@@ -53,7 +68,11 @@ public class CategoryManager {
/**
* Used to distribute {@link CategoryChangeEvent}s
*/
private final EventBus categoryEventBus = new EventBus("Category Event Bus");
private final EventBus categoryEventBus = new AsyncEventBus(Executors.newSingleThreadExecutor(
new BasicThreadFactory.Builder().namingPattern("Category Event Bus").uncaughtExceptionHandler((Thread t, Throwable e) -> {
LOGGER.log(Level.SEVERE, "uncaught exception in event bus handler", e);
}).build()
));
/**
* For performance reasons, keep current category counts in memory. All of
@@ -62,6 +81,20 @@ public class CategoryManager {
*/
private final LoadingCache<Category, LongAdder> categoryCounts
= CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper));
/**
* cached TagNames corresponding to Categories, looked up from
* autopsyTagManager at initial request or if invalidated by case change.
*/
private final LoadingCache<Category, TagName> catTagNameMap = CacheBuilder.newBuilder().build(CacheLoader.from(cat
-> getController().getTagsManager().getTagName(cat)));
public CategoryManager(ImageGalleryController controller) {
this.controller = controller;
}
private ImageGalleryController getController() {
return controller;
}
/**
* assign a new db. the counts cache is invalidated and all subsequent db
@@ -71,10 +104,17 @@ public class CategoryManager {
*
* @param db
*/
public void setDb(DrawableDB db) {
synchronized public void setDb(DrawableDB db) {
this.db = db;
categoryCounts.invalidateAll();
Category.clearTagNames();
catTagNameMap.invalidateAll();
fireChange(Collections.emptyList(), null);
}
synchronized public void invalidateCaches() {
categoryCounts.invalidateAll();
catTagNameMap.invalidateAll();
fireChange(Collections.emptyList(), null);
}
/**
@@ -84,7 +124,7 @@ public class CategoryManager {
*
* @return the long the number of files with the given Category
*/
public long getCategoryCount(Category cat) {
synchronized public long getCategoryCount(Category cat) {
if (cat == Category.ZERO) {
// Keeping track of the uncategorized files is a bit tricky while ingest
// is going on, so always use the list of file IDs we already have along with the
@@ -102,7 +142,7 @@ public class CategoryManager {
*
* @param cat the Category to increment
*/
public void incrementCategoryCount(Category cat) {
synchronized public void incrementCategoryCount(Category cat) {
if (cat != Category.ZERO) {
categoryCounts.getUnchecked(cat).increment();
}
@@ -114,7 +154,7 @@ public class CategoryManager {
*
* @param cat the Category to decrement
*/
public void decrementCategoryCount(Category cat) {
synchronized public void decrementCategoryCount(Category cat) {
if (cat != Category.ZERO) {
categoryCounts.getUnchecked(cat).decrement();
}
@@ -130,7 +170,7 @@ public class CategoryManager {
* @return a LongAdder whose value is set to the number of file with the
* given Category
*/
private LongAdder getCategoryCountHelper(Category cat) {
synchronized private LongAdder getCategoryCountHelper(Category cat) {
LongAdder longAdder = new LongAdder();
longAdder.decrement();
try {
@@ -147,8 +187,8 @@ public class CategoryManager {
*
* @param fileIDs
*/
public void fireChange(Collection<Long> fileIDs) {
categoryEventBus.post(new CategoryChangeEvent(fileIDs));
public void fireChange(Collection<Long> fileIDs, Category newCategory) {
categoryEventBus.post(new CategoryChangeEvent(fileIDs, newCategory));
}
/**
@@ -169,4 +209,97 @@ public class CategoryManager {
categoryEventBus.unregister(listener);
}
/**
* get the TagName used to store this Category in the main autopsy db.
*
* @return the TagName used for this Category
*/
synchronized public TagName getTagName(Category cat) {
return catTagNameMap.getUnchecked(cat);
}
public static Category categoryFromTagName(TagName tagName) {
return Category.fromDisplayName(tagName.getDisplayName());
}
public static boolean isCategoryTagName(TagName tName) {
return Category.isCategoryName(tName.getDisplayName());
}
public static boolean isNotCategoryTagName(TagName tName) {
return Category.isNotCategoryName(tName.getDisplayName());
}
@Subscribe
public void handleTagAdded(ContentTagAddedEvent event) {
final ContentTag addedTag = event.getTag();
if (isCategoryTagName(addedTag.getName())) {
final DrawableTagsManager tagsManager = controller.getTagsManager();
try {
//remove old category tag(s) if necessary
for (ContentTag ct : tagsManager.getContentTagsByContent(addedTag.getContent())) {
if (ct.getId() != addedTag.getId()
&& CategoryManager.isCategoryTagName(ct.getName())) {
try {
tagsManager.deleteContentTag(ct);
} catch (TskCoreException tskException) {
LOGGER.log(Level.SEVERE, "Failed to delete content tag. Unable to maintain categories in a consistent state.", tskException);
}
}
}
} catch (TskCoreException tskException) {
LOGGER.log(Level.SEVERE, "Failed to get content tags for content. Unable to maintain category in a consistent state.", tskException);
}
Category newCat = CategoryManager.categoryFromTagName(addedTag.getName());
if (newCat != Category.ZERO) {
incrementCategoryCount(newCat);
}
fireChange(Collections.singleton(addedTag.getContent().getId()), newCat);
}
}
@Subscribe
public void handleTagDeleted(ContentTagDeletedEvent event) {
ContentTag deleted = event.getTag();
if (isCategoryTagName(deleted.getName())) {
Category deletedCat = CategoryManager.categoryFromTagName(deleted.getName());
if (deletedCat != Category.ZERO) {
decrementCategoryCount(deletedCat);
}
fireChange(Collections.singleton(deleted.getContent().getId()), null);
}
}
/**
* Event broadcast to various UI componenets when one or more files'
* category
* has been changed
*/
@Immutable
public static class CategoryChangeEvent {
private final Collection<Long> fileIDs;
private final Category newCategory;
public CategoryChangeEvent(Collection<Long> fileIDs, Category newCategory) {
super();
this.fileIDs = fileIDs;
this.newCategory = newCategory;
}
public Category getNewCategory() {
return newCategory;
}
/**
* @return the fileIDs of the files whose categories have changed
*/
public Collection<Long> getFileIDs() {
return Collections.unmodifiableCollection(fileIDs);
}
}
}
@@ -40,10 +40,10 @@ import org.sleuthkit.datamodel.TagName;
public class DrawableAttribute<T extends Comparable<T>> {
public final static DrawableAttribute<String> NAME
= new DrawableAttribute<>( AttributeName.NAME, "Name", true, "folder-rename.png", f -> Collections.singleton(f.getName()));
= new DrawableAttribute<>(AttributeName.NAME, "Name", true, "folder-rename.png", f -> Collections.singleton(f.getName()));
public final static DrawableAttribute<Boolean> ANALYZED
= new DrawableAttribute<>( AttributeName.ANALYZED, "Analyzed", true, "", f -> Collections.singleton(f.isAnalyzed()));
= new DrawableAttribute<>(AttributeName.ANALYZED, "Analyzed", true, "", f -> Collections.singleton(f.isAnalyzed()));
/**
* since categories are really just tags in autopsy, they are not dealt with
@@ -54,50 +54,48 @@ public class DrawableAttribute<T extends Comparable<T>> {
* advantage. move categories into DrawableDB
*/
public final static DrawableAttribute<Category> CATEGORY
= new DrawableAttribute<>( AttributeName.CATEGORY, "Category", false, "category-icon.png", f -> Collections.singleton(f.getCategory()));
= new DrawableAttribute<>(AttributeName.CATEGORY, "Category", false, "category-icon.png", f -> Collections.singleton(f.getCategory()));
public final static DrawableAttribute<TagName> TAGS
= new DrawableAttribute<>( AttributeName.TAGS, "Tags", false, "tag_red.png", DrawableFile::getTagNames);
= new DrawableAttribute<>(AttributeName.TAGS, "Tags", false, "tag_red.png", DrawableFile::getTagNames);
public final static DrawableAttribute<String> PATH
= new DrawableAttribute<>(AttributeName.PATH, "Path", true, "folder_picture.png", f -> Collections.singleton(f.getDrawablePath()));
public final static DrawableAttribute<String> CREATED_TIME
= new DrawableAttribute<>( AttributeName.CREATED_TIME, "Created Time", true, "clock--plus.png", f -> Collections.singleton(ContentUtils.getStringTime(f.getCrtime(), f)));
= new DrawableAttribute<>(AttributeName.CREATED_TIME, "Created Time", true, "clock--plus.png", f -> Collections.singleton(ContentUtils.getStringTime(f.getCrtime(), f)));
public final static DrawableAttribute<String> MODIFIED_TIME
= new DrawableAttribute<>( AttributeName.MODIFIED_TIME, "Modified Time", true, "clock--pencil.png", f -> Collections.singleton(ContentUtils.getStringTime(f.getMtime(), f)));
= new DrawableAttribute<>(AttributeName.MODIFIED_TIME, "Modified Time", true, "clock--pencil.png", f -> Collections.singleton(ContentUtils.getStringTime(f.getMtime(), f)));
public final static DrawableAttribute<String> MAKE
= new DrawableAttribute<>( AttributeName.MAKE, "Camera Make", true, "camera.png", f -> Collections.singleton(f.getMake()));
= new DrawableAttribute<>(AttributeName.MAKE, "Camera Make", true, "camera.png", f -> Collections.singleton(f.getMake()));
public final static DrawableAttribute<String> MODEL
= new DrawableAttribute<>( AttributeName.MODEL, "Camera Model", true, "camera.png", f -> Collections.singleton(f.getModel()));
= new DrawableAttribute<>(AttributeName.MODEL, "Camera Model", true, "camera.png", f -> Collections.singleton(f.getModel()));
public final static DrawableAttribute<String> HASHSET
= new DrawableAttribute<>(AttributeName.HASHSET, "Hashset", true, "hashset_hits.png", DrawableFile::getHashHitSetNames);
public final static DrawableAttribute<Long> OBJ_ID
= new DrawableAttribute<>( AttributeName.OBJ_ID, "Internal Object ID", true, "", f -> Collections.singleton(f.getId()));
= new DrawableAttribute<>(AttributeName.OBJ_ID, "Internal Object ID", true, "", f -> Collections.singleton(f.getId()));
public final static DrawableAttribute<Double> WIDTH
= new DrawableAttribute<>(AttributeName.WIDTH, "Width", true, "arrow-resize.png", f -> Collections.singleton(f.getWidth()));
public final static DrawableAttribute<Double> HEIGHT
= new DrawableAttribute<>( AttributeName.HEIGHT, "Height", true, "arrow-resize-090.png", f -> Collections.singleton(f.getHeight()));
= new DrawableAttribute<>(AttributeName.HEIGHT, "Height", true, "arrow-resize-090.png", f -> Collections.singleton(f.getHeight()));
final private static List< DrawableAttribute<?>> groupables
= Arrays.asList(PATH, HASHSET, CATEGORY, TAGS, MAKE, MODEL);
final private static List<DrawableAttribute<?>> values
= Arrays.asList(NAME, ANALYZED, CATEGORY, TAGS, PATH, CREATED_TIME,
MODIFIED_TIME, HASHSET, CATEGORY, MAKE, MODEL, OBJ_ID,
WIDTH, HEIGHT);
MODIFIED_TIME, HASHSET, MAKE, MODEL, OBJ_ID, WIDTH, HEIGHT);
private final Function<DrawableFile<?>, Collection<T>> extractor;
private DrawableAttribute( AttributeName name, String displayName, Boolean isDBColumn, String imageName, Function<DrawableFile<?>, Collection<T>> extractor) {
private DrawableAttribute(AttributeName name, String displayName, Boolean isDBColumn, String imageName, Function<DrawableFile<?>, Collection<T>> extractor) {
this.attrName = name;
this.displayName = new ReadOnlyStringWrapper(displayName);
this.isDBColumn = isDBColumn;
@@ -142,8 +140,19 @@ public class DrawableAttribute<T extends Comparable<T>> {
public static enum AttributeName {
NAME, ANALYZED, CATEGORY, TAGS, PATH, CREATED_TIME, MODIFIED_TIME, MAKE,
MODEL, HASHSET, OBJ_ID, WIDTH, HEIGHT;
NAME,
ANALYZED,
CATEGORY,
TAGS,
PATH,
CREATED_TIME,
MODIFIED_TIME,
MAKE,
MODEL,
HASHSET,
OBJ_ID,
WIDTH,
HEIGHT;
}
public Collection<T> getValue(DrawableFile<?> f) {
@@ -34,6 +34,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
@@ -46,13 +47,12 @@ import org.apache.commons.lang3.StringUtils;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FileUpdateEvent;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupKey;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupManager;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupSortBy;
import static org.sleuthkit.autopsy.imagegallery.grouping.GroupSortBy.GROUP_BY_VALUE;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupKey;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
import static org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy.GROUP_BY_VALUE;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
@@ -125,11 +125,6 @@ public final class DrawableDB {
*/
private final Map<DrawableAttribute<?>, PreparedStatement> groupStatementMap = new HashMap<>();
/**
* list of observers to be notified if the database changes
*/
private final HashSet<FileUpdateEvent.FileUpdateListener> updateListeners = new HashSet<>();
private GroupManager groupManager;
private final Path dbPath;
@@ -148,6 +143,7 @@ public final class DrawableDB {
}
}
private final SleuthkitCase tskCase;
private final ImageGalleryController controller;
//////////////general database logic , mostly borrowed from sleuthkitcase
/**
@@ -196,9 +192,11 @@ public final class DrawableDB {
*
* @throws SQLException if there is problem creating or configuring the db
*/
private DrawableDB(Path dbPath, SleuthkitCase tskCase) throws SQLException, ExceptionInInitializerError, IOException {
private DrawableDB(Path dbPath, ImageGalleryController controller) throws SQLException, ExceptionInInitializerError, IOException {
this.dbPath = dbPath;
this.tskCase = tskCase;
this.controller = controller;
this.tskCase = controller.getSleuthKitCase();
this.groupManager = controller.getGroupManager();
Files.createDirectories(dbPath.getParent());
if (initializeDBSchema()) {
updateFileStmt = prepareStatement(
@@ -219,8 +217,8 @@ public final class DrawableDB {
analyzedGroupStmt = prepareStatement("Select obj_id , analyzed from drawable_files where analyzed = ?", DrawableAttribute.ANALYZED);
hashSetGroupStmt = prepareStatement("select drawable_files.obj_id as obj_id, analyzed from drawable_files , hash_sets , hash_set_hits where drawable_files.obj_id = hash_set_hits.obj_id and hash_sets.hash_set_id = hash_set_hits.hash_set_id and hash_sets.hash_set_name = ?", DrawableAttribute.HASHSET);
updateGroupStmt = prepareStatement("update groups set seen = 1 where value = ? and attribute = ?");
insertGroupStmt = prepareStatement("insert or replace into groups (value, attribute) values (?,?)");
updateGroupStmt = prepareStatement("insert or replace into groups (seen, value, attribute) values( ?, ? , ?)");
insertGroupStmt = prepareStatement("insert or ignore into groups (value, attribute) values (?,?)");
groupSeenQueryStmt = prepareStatement("select seen from groups where value = ? and attribute = ?");
@@ -230,6 +228,9 @@ public final class DrawableDB {
insertHashHitStmt = prepareStatement("insert or ignore into hash_set_hits (hash_set_id, obj_id) values (?,?)");
for (Category cat : Category.values()) {
insertGroup(cat.getDisplayName(), DrawableAttribute.CATEGORY);
}
initializeImageList();
} else {
throw new ExceptionInInitializerError();
@@ -284,10 +285,10 @@ public final class DrawableDB {
*
* @return
*/
public static DrawableDB getDrawableDB(Path dbPath, SleuthkitCase tskCase) {
public static DrawableDB getDrawableDB(Path dbPath, ImageGalleryController controller) {
try {
return new DrawableDB(dbPath.resolve("drawable.db"), tskCase);
return new DrawableDB(dbPath.resolve("drawable.db"), controller);
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "sql error creating database connection", ex);
return null;
@@ -516,13 +517,14 @@ public final class DrawableDB {
return false;
}
public void markGroupSeen(GroupKey<?> gk) {
public void markGroupSeen(GroupKey<?> gk, boolean seen) {
dbWriteLock();
try {
//PreparedStatement updateGroup = con.prepareStatement("update groups set seen = 1 where value = ? and attribute = ?");
//PreparedStatement updateGroup = con.prepareStatement("update groups set seen = ? where value = ? and attribute = ?");
updateGroupStmt.clearParameters();
updateGroupStmt.setString(1, gk.getValueDisplayName());
updateGroupStmt.setString(2, gk.getAttribute().attrName.toString());
updateGroupStmt.setBoolean(1, seen);
updateGroupStmt.setString(2, gk.getValueDisplayName());
updateGroupStmt.setString(3, gk.getAttribute().attrName.toString());
updateGroupStmt.execute();
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "Error marking group as seen", ex);
@@ -581,27 +583,25 @@ public final class DrawableDB {
stmt.setBoolean(8, f.isAnalyzed());
stmt.executeUpdate();
final Collection<String> hashSetNames = DrawableAttribute.HASHSET.getValue(f);
final Collection<String> hashSetNames = getHashSetsForFileFromAutopsy(f.getId());
if (hashSetNames.isEmpty() == false) {
for (String name : hashSetNames) {
for (String name : hashSetNames) {
// "insert or ignore into hash_sets (hash_set_name) values (?)"
insertHashSetStmt.setString(1, name);
insertHashSetStmt.executeUpdate();
// "insert or ignore into hash_sets (hash_set_name) values (?)"
insertHashSetStmt.setString(1, name);
insertHashSetStmt.executeUpdate();
//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()) {
while (rs.next()) {
int hashsetID = rs.getInt("hash_set_id");
//"insert or ignore into hash_set_hits (hash_set_id, obj_id) values (?,?)";
insertHashHitStmt.setInt(1, hashsetID);
insertHashHitStmt.setLong(2, f.getId());
insertHashHitStmt.executeUpdate();
break;
}
//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()) {
while (rs.next()) {
int hashsetID = rs.getInt("hash_set_id");
//"insert or ignore into hash_set_hits (hash_set_id, obj_id) values (?,?)";
insertHashHitStmt.setInt(1, hashsetID);
insertHashHitStmt.setLong(2, f.getId());
insertHashHitStmt.executeUpdate();
break;
}
}
}
@@ -638,22 +638,6 @@ public final class DrawableDB {
tr.commit(notify);
}
public void addUpdatedFileListener(FileUpdateEvent.FileUpdateListener l) {
updateListeners.add(l);
}
private void fireUpdatedFiles(Collection<Long> fileIDs) {
for (FileUpdateEvent.FileUpdateListener listener : updateListeners) {
listener.handleFileUpdate(FileUpdateEvent.newUpdateEvent(fileIDs, null));
}
}
private void fireRemovedFiles(Collection<Long> fileIDs) {
for (FileUpdateEvent.FileUpdateListener listener : updateListeners) {
listener.handleFileUpdate(FileUpdateEvent.newRemovedEvent(fileIDs));
}
}
public Boolean isFileAnalyzed(DrawableFile<?> f) {
return isFileAnalyzed(f.getId());
}
@@ -695,7 +679,7 @@ public final class DrawableDB {
public Boolean isGroupAnalyzed(GroupKey<?> gk) {
dbReadLock();
try {
List<Long> fileIDsInGroup = getFileIDsInGroup(gk);
Set<Long> fileIDsInGroup = getFileIDsInGroup(gk);
try {
// In testing, this method appears to be a lot faster than doing one large select statement
@@ -744,10 +728,10 @@ public final class DrawableDB {
*
* @throws TskCoreException
*/
public List<Long> findAllFileIdsWhere(String sqlWhereClause) throws TskCoreException {
public Set<Long> findAllFileIdsWhere(String sqlWhereClause) throws TskCoreException {
Statement statement = null;
ResultSet rs = null;
List<Long> ret = new ArrayList<>();
Set<Long> ret = new HashSet<>();
dbReadLock();
try {
statement = con.createStatement();
@@ -981,7 +965,7 @@ public final class DrawableDB {
}
}
public List<Long> getFileIDsInGroup(GroupKey<?> groupKey) throws TskCoreException {
public Set<Long> getFileIDsInGroup(GroupKey<?> groupKey) throws TskCoreException {
if (groupKey.getAttribute().isDBColumn) {
switch (groupKey.getAttribute().attrName) {
@@ -991,7 +975,7 @@ public final class DrawableDB {
return groupManager.getFileIDsWithTag((TagName) groupKey.getValue());
}
}
List<Long> files = new ArrayList<>();
Set<Long> files = new HashSet<>();
dbReadLock();
try {
PreparedStatement statement = getGroupStatment(groupKey.getAttribute());
@@ -1052,7 +1036,7 @@ public final class DrawableDB {
public List<DrawableFile<?>> getFilesWithCategory(Category cat) throws TskCoreException, IllegalArgumentException {
try {
List<DrawableFile<?>> files = new ArrayList<>();
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(cat.getTagName());
List<ContentTag> contentTags = controller.getTagsManager().getContentTagsByTagName(controller.getTagsManager().getTagName(cat));
for (ContentTag ct : contentTags) {
if (ct.getContent() instanceof AbstractFile) {
files.add(DrawableFile.create((AbstractFile) ct.getContent(), isFileAnalyzed(ct.getContent().getId()),
@@ -1110,6 +1094,8 @@ public final class DrawableDB {
removeFileStmt.setLong(1, id);
removeFileStmt.executeUpdate();
tr.addRemovedFile(id);
//TODO: delete from hash_set_hits table also...
} catch (SQLException ex) {
LOGGER.log(Level.WARNING, "failed to delete row for obj_id = " + id, ex);
} finally {
@@ -1137,16 +1123,13 @@ public final class DrawableDB {
*
* @return a set of names, each of which is a hashset that the given file is
* in.
*
*
* //TODO: why does this go to the SKC? don't we already have this in =fo
* in the drawable db?
*/
@Nonnull
Set<String> getHashSetsForFile(long fileID) {
public Set<String> getHashSetsForFileFromAutopsy(long fileID) {
try {
Set<String> hashNames = new HashSet<>();
List<BlackboardArtifact> arts = ImageGalleryController.getDefault().getSleuthKitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, fileID);
List<BlackboardArtifact> arts = tskCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, fileID);
for (BlackboardArtifact a : arts) {
List<BlackboardAttribute> attrs = a.getAttributes();
for (BlackboardAttribute attr : attrs) {
@@ -1154,10 +1137,10 @@ public final class DrawableDB {
hashNames.add(attr.getValueString());
}
}
return hashNames;
}
} catch (TskCoreException tskCoreException) {
throw new IllegalStateException(tskCoreException);
return hashNames;
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "failed to get hash sets for file", ex);
}
return Collections.emptySet();
}
@@ -1214,10 +1197,24 @@ public final class DrawableDB {
/**
* For performance reasons, keep the file type in memory
*/
private final Map<AbstractFile, Boolean> videoFileMap = new ConcurrentHashMap<>();
private final Map<Long, Boolean> videoFileMap = new ConcurrentHashMap<>();
/**
* is this File a video file?
*
* @param f check if this file is a video. will return false for null file.
*
* @return returns true if this file is a video as determined by {@link ImageGalleryModule#isVideoFile(org.sleuthkit.datamodel.AbstractFile)
* } but caches the result.
* returns false if passed a null AbstractFile
*/
public boolean isVideoFile(AbstractFile f) {
return videoFileMap.computeIfAbsent(f, ImageGalleryModule::isVideoFile);
if (Objects.isNull(f)) {
return false;
} else {
return videoFileMap.computeIfAbsent(f.getId(), (id) -> ImageGalleryModule.isVideoFile(f));
}
}
/**
@@ -1237,7 +1234,7 @@ public final class DrawableDB {
*/
public long getCategoryCount(Category cat) {
try {
return Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(cat.getTagName()).stream()
return tskCase.getContentTagsByTagName(controller.getTagsManager().getTagName(cat)).stream()
.map(ContentTag::getContent)
.map(Content::getId)
.filter(this::isInDB)
@@ -1306,8 +1303,10 @@ public final class DrawableDB {
close();
if (notify) {
fireUpdatedFiles(updatedFiles);
fireRemovedFiles(removedFiles);
if (groupManager != null) {
groupManager.handleFileUpdate(updatedFiles);
groupManager.handleFileRemoved(removedFiles);
}
}
} catch (SQLException ex) {
if (Case.isCaseOpen()) {
@@ -46,7 +46,6 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBU
import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG;
import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.ContentVisitor;
import org.sleuthkit.datamodel.SleuthkitItemVisitor;
import org.sleuthkit.datamodel.Tag;
@@ -111,7 +110,6 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
super(file.getSleuthkitCase(), file.getId(), file.getAttrType(), file.getAttrId(), file.getName(), file.getType(), file.getMetaAddr(), (int) file.getMetaSeq(), file.getDirType(), file.getMetaType(), null, new Integer(0).shortValue(), file.getSize(), file.getCtime(), file.getCrtime(), file.getAtime(), file.getMtime(), new Integer(0).shortValue(), file.getUid(), file.getGid(), file.getMd5Hash(), file.getKnown(), file.getParentPath());
this.analyzed = new SimpleBooleanProperty(analyzed);
this.file = file;
}
public abstract boolean isVideo();
@@ -170,19 +168,16 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
public Set<TagName> getTagNames() {
try {
List<ContentTag> contentTagsByContent = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByContent(this);
return contentTagsByContent.stream()
return getSleuthkitCase().getContentTagsByContent(this).stream()
.map(Tag::getName)
.collect(Collectors.toSet());
} catch (TskCoreException ex) {
Logger.getAnonymousLogger().log(Level.WARNING, "problem looking up " + DrawableAttribute.TAGS.getDisplayName() + " for " + file.getName(), ex);
return Collections.emptySet();
} catch (IllegalStateException ex) {
Logger.getAnonymousLogger().log(Level.WARNING, "there is no case open; failed to look up " + DrawableAttribute.TAGS.getDisplayName() + " for " + file.getName());
return Collections.emptySet();
}
return Collections.emptySet();
}
@Deprecated
@@ -273,27 +268,21 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
return category;
}
public void updateCategory() {
/** set the category property to the most severe one found */
private void updateCategory() {
try {
List<ContentTag> contentTagsByContent = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByContent(this);
Category cat = null;
for (ContentTag ct : contentTagsByContent) {
if (ct.getName().getDisplayName().startsWith(Category.CATEGORY_PREFIX)) {
cat = Category.fromDisplayName(ct.getName().getDisplayName());
break;
}
}
if (cat == null) {
category.set(Category.ZERO);
} else {
category.set(cat);
}
category.set(getSleuthkitCase().getContentTagsByContent(this).stream()
.map(Tag::getName).filter(CategoryManager::isCategoryTagName)
.map(TagName::getDisplayName)
.map(Category::fromDisplayName)
.sorted().findFirst() //sort by severity and take the first
.orElse(Category.ZERO)
);
} catch (TskCoreException ex) {
Logger.getLogger(DrawableFile.class.getName()).log(Level.WARNING, "problem looking up category for file " + this.getName(), ex);
} catch (IllegalStateException ex) {
// We get here many times if the case is closed during ingest, so don't print out a ton of warnings.
}
}
public abstract Image getThumbnail();
@@ -0,0 +1,230 @@
/*
* 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.imagegallery.datamodel;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.events.ContentTagDeletedEvent;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Manages Tags, Tagging, and the relationship between Categories and Tags in
* the autopsy Db. delegates some, work to the backing {@link TagsManager}.
*/
public class DrawableTagsManager {
private static final Logger LOGGER = Logger.getLogger(DrawableTagsManager.class.getName());
private static final String FOLLOW_UP = "Follow Up";
final private Object autopsyTagsManagerLock = new Object();
private TagsManager autopsyTagsManager;
/** Used to distribute {@link TagsChangeEvent}s */
private final EventBus tagsEventBus = new AsyncEventBus(
Executors.newSingleThreadExecutor(
new BasicThreadFactory.Builder().namingPattern("Tags Event Bus").uncaughtExceptionHandler((Thread t, Throwable e) -> {
LOGGER.log(Level.SEVERE, "uncaught exception in event bus handler", e);
}).build()
));
/** The tag name corresponding to the "built-in" tag "Follow Up" */
private TagName followUpTagName;
public DrawableTagsManager(TagsManager autopsyTagsManager) {
this.autopsyTagsManager = autopsyTagsManager;
}
/**
* register an object to receive CategoryChangeEvents
*
* @param listner
*/
public void registerListener(Object listner) {
tagsEventBus.register(listner);
}
/**
* unregister an object from receiving CategoryChangeEvents
*
* @param listener
*/
public void unregisterListener(Object listener) {
tagsEventBus.unregister(listener);
}
public void fireTagAddedEvent(ContentTagAddedEvent event) {
tagsEventBus.post(event);
}
public void fireTagDeletedEvent(ContentTagDeletedEvent event) {
tagsEventBus.post(event);
}
/**
* assign a new TagsManager to back this one, ie when the current case
* changes
*
* @param autopsyTagsManager
*/
public void setAutopsyTagsManager(TagsManager autopsyTagsManager) {
synchronized (autopsyTagsManagerLock) {
this.autopsyTagsManager = autopsyTagsManager;
clearFollowUpTagName();
}
}
/**
* Use when closing a case to make sure everything is re-initialized in the
* next case.
*/
public void clearFollowUpTagName() {
synchronized (autopsyTagsManagerLock) {
followUpTagName = null;
}
}
/**
* get the (cached) follow up TagName
*
* @return
*
* @throws TskCoreException
*/
public TagName getFollowUpTagName() throws TskCoreException {
synchronized (autopsyTagsManagerLock) {
if (Objects.isNull(followUpTagName)) {
followUpTagName = getTagName(FOLLOW_UP);
}
return followUpTagName;
}
}
/**
* get all the TagNames that are not categories
*
* @return all the TagNames that are not categories, in alphabetical order
* by displayName, or, an empty set if there was an exception looking them
* up from the db.
*/
@Nonnull
public Collection<TagName> getNonCategoryTagNames() {
synchronized (autopsyTagsManagerLock) {
try {
return autopsyTagsManager.getAllTagNames().stream()
.filter(CategoryManager::isNotCategoryTagName)
.distinct().sorted()
.collect(Collectors.toList());
} catch (TskCoreException | IllegalStateException ex) {
LOGGER.log(Level.WARNING, "couldn't access case", ex);
}
return Collections.emptySet();
}
}
/**
* Gets content tags count by content.
*
* @param The content of interest.
*
* @return A list, possibly empty, of the tags that have been applied to the
* artifact.
*
* @throws TskCoreException
*/
public List<ContentTag> getContentTagsByContent(Content content) throws TskCoreException {
synchronized (autopsyTagsManagerLock) {
return autopsyTagsManager.getContentTagsByContent(content);
}
}
public TagName getTagName(String displayName) throws TskCoreException {
synchronized (autopsyTagsManagerLock) {
try {
for (TagName tn : autopsyTagsManager.getAllTagNames()) {
if (displayName.equals(tn.getDisplayName())) {
return tn;
}
}
try {
return autopsyTagsManager.addTagName(displayName);
} catch (TagsManager.TagNameAlreadyExistsException ex) {
throw new TskCoreException("tagame exists but wasn't found", ex);
}
} catch (IllegalStateException ex) {
LOGGER.log(Level.SEVERE, "Case was closed out from underneath", ex);
throw new TskCoreException("Case was closed out from underneath", ex);
}
}
}
public TagName getTagName(Category cat) {
try {
return getTagName(cat.getDisplayName());
} catch (TskCoreException ex) {
return null;
}
}
public ContentTag addContentTag(DrawableFile<?> file, TagName tagName, String comment) throws TskCoreException {
synchronized (autopsyTagsManagerLock) {
return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment);
}
}
public List<ContentTag> getContentTagsByTagName(TagName t) throws TskCoreException {
synchronized (autopsyTagsManagerLock) {
return autopsyTagsManager.getContentTagsByTagName(t);
}
}
public List<TagName> getAllTagNames() throws TskCoreException {
synchronized (autopsyTagsManagerLock) {
return autopsyTagsManager.getAllTagNames();
}
}
public List<TagName> getTagNamesInUse() throws TskCoreException {
synchronized (autopsyTagsManagerLock) {
return autopsyTagsManager.getTagNamesInUse();
}
}
public void deleteContentTag(ContentTag ct) throws TskCoreException {
synchronized (autopsyTagsManagerLock) {
autopsyTagsManager.deleteContentTag(ct);
}
}
}
@@ -4,6 +4,7 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.Set;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
/**
* Manages a cache of hashset hits as a map from fileID to hashset names.
@@ -35,7 +36,7 @@ public class HashSetManager {
* @return the names of the hashsets the given fileID is in
*/
private Set<String> getHashSetsForFileHelper(long fileID) {
return db.getHashSetsForFile(fileID);
return db.getHashSetsForFileFromAutopsy(fileID);
}
/**
@@ -16,10 +16,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.grouping;
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.collections.FXCollections;
@@ -41,13 +41,15 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
}
private final ObservableList<Long> fileIDs = FXCollections.observableArrayList();
private final ObservableList<Long> unmodifiableFileIDS = FXCollections.unmodifiableObservableList(fileIDs);
//cache the number of files in this groups with hashset hits
private long hashSetHitsCount = -1;
private final ReadOnlyBooleanWrapper seen = new ReadOnlyBooleanWrapper(false);
@SuppressWarnings("ReturnOfCollectionOrArrayField")
synchronized public ObservableList<Long> fileIds() {
return FXCollections.unmodifiableObservableList(fileIDs);
return unmodifiableFileIDS;
}
final public GroupKey<?> groupKey;
@@ -68,9 +70,10 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
return groupKey.getValueDisplayName();
}
DrawableGroup(GroupKey<?> groupKey, List<Long> filesInGroup) {
DrawableGroup(GroupKey<?> groupKey, Set<Long> filesInGroup, boolean seen) {
this.groupKey = groupKey;
fileIDs.setAll(filesInGroup);
this.fileIDs.setAll(filesInGroup);
this.seen.set(seen);
}
synchronized public int getSize() {
@@ -131,18 +134,33 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
((DrawableGroup) obj).groupKey);
}
synchronized public void addFile(Long f) {
synchronized void addFile(Long f) {
invalidateHashSetHitsCount();
seen.set(false);
if (fileIDs.contains(f) == false) {
fileIDs.add(f);
seen.set(false);
}
}
synchronized public void removeFile(Long f) {
synchronized void setFiles(Set<? extends Long> newFileIds) {
invalidateHashSetHitsCount();
seen.set(false);
fileIDs.removeAll(f);
boolean filesRemoved = fileIDs.removeIf((Long t) -> newFileIds.contains(t) == false);
if (filesRemoved) {
seen.set(false);
}
for (Long f : newFileIds) {
if (fileIDs.contains(f) == false) {
fileIDs.add(f);
seen.set(false);
}
}
}
synchronized void removeFile(Long f) {
invalidateHashSetHitsCount();
if (fileIDs.removeAll(f)) {
seen.set(false);
}
}
// By default, sort by group key name
@@ -151,8 +169,8 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
return this.groupKey.getValueDisplayName().compareTo(other.groupKey.getValueDisplayName());
}
void setSeen() {
this.seen.set(true);
void setSeen(boolean isSeen) {
this.seen.set(isSeen);
}
public ReadOnlyBooleanWrapper seenProperty() {
@@ -162,4 +180,5 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
public boolean isSeen() {
return seen.get();
}
}
@@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.grouping;
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
import java.util.Map;
import java.util.Objects;
@@ -56,7 +56,7 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
@Override
public String toString() {
return "GroupKey: " + getAttribute() + " = " + getValue();
return "GroupKey: " + getAttribute().attrName + " = " + getValue();
}
@Override
@@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-4 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");
@@ -16,8 +16,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.grouping;
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
import com.google.common.eventbus.Subscribe;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -28,18 +29,31 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import static javafx.concurrent.Worker.State.CANCELLED;
import static javafx.concurrent.Worker.State.FAILED;
import static javafx.concurrent.Worker.State.READY;
import static javafx.concurrent.Worker.State.RUNNING;
import static javafx.concurrent.Worker.State.SCHEDULED;
import static javafx.concurrent.Worker.State.SUCCEEDED;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.swing.SortOrder;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.netbeans.api.progress.ProgressHandle;
@@ -47,17 +61,18 @@ import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.LoggedTask;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
import org.sleuthkit.autopsy.imagegallery.FileUpdateEvent;
import org.sleuthkit.autopsy.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
import org.sleuthkit.autopsy.imagegallery.TagUtils;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
@@ -71,18 +86,19 @@ import org.sleuthkit.datamodel.TskCoreException;
* extent {@link SleuthkitCase} ) to facilitate creation, retrieval, updating,
* and sorting of {@link DrawableGroup}s.
*/
public class GroupManager implements FileUpdateEvent.FileUpdateListener {
public class GroupManager {
private static final Logger LOGGER = Logger.getLogger(GroupManager.class.getName());
private DrawableDB db;
private final ImageGalleryController controller;
/**
* map from {@link GroupKey}s to {@link DrawableGroup}s. All groups (even
* not
* fully analyzed or not visible groups could be in this map
* not fully analyzed or not visible groups could be in this map
*/
@GuardedBy("this")
private final Map<GroupKey<?>, DrawableGroup> groupMap = new HashMap<>();
/**
@@ -90,16 +106,12 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
*/
@ThreadConfined(type = ThreadType.JFX)
private final ObservableList<DrawableGroup> analyzedGroups = FXCollections.observableArrayList();
private final ObservableList<DrawableGroup> unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups);
private final ObservableList<DrawableGroup> publicAnalyzedGroupsWrapper = FXCollections.unmodifiableObservableList(analyzedGroups);
/**
* list of unseen groups
*/
/** list of unseen groups */
@ThreadConfined(type = ThreadType.JFX)
private final ObservableList<DrawableGroup> unSeenGroups = FXCollections.observableArrayList();
// private final SortedList<Grouping> sortedUnSeenGroups = new SortedList<>(unSeenGroups);
private final ObservableList<DrawableGroup> publicSortedUnseenGroupsWrapper = FXCollections.unmodifiableObservableList(unSeenGroups);
private final ObservableList<DrawableGroup> unmodifiableUnSeenGroups = FXCollections.unmodifiableObservableList(unSeenGroups);
private ReGroupTask<?> groupByTask;
@@ -109,21 +121,22 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
private volatile DrawableAttribute<?> groupBy = DrawableAttribute.PATH;
private volatile SortOrder sortOrder = SortOrder.ASCENDING;
private ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper();
private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper();
public void setDB(DrawableDB db) {
this.db = db;
db.addUpdatedFileListener(this);
regroup(groupBy, sortBy, sortOrder, Boolean.TRUE);
}
@SuppressWarnings("ReturnOfCollectionOrArrayField")
public ObservableList<DrawableGroup> getAnalyzedGroups() {
return publicAnalyzedGroupsWrapper;
return unmodifiableAnalyzedGroups;
}
@ThreadConfined(type = ThreadType.JFX)
@SuppressWarnings("ReturnOfCollectionOrArrayField")
public ObservableList<DrawableGroup> getUnSeenGroups() {
return publicSortedUnseenGroupsWrapper;
return unmodifiableUnSeenGroups;
}
/**
@@ -151,7 +164,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
Set<GroupKey<?>> resultSet = new HashSet<>();
for (Comparable<?> val : groupBy.getValue(file)) {
if (groupBy == DrawableAttribute.TAGS) {
if (((TagName) val).getDisplayName().startsWith(Category.CATEGORY_PREFIX) == false) {
if (CategoryManager.isNotCategoryTagName((TagName) val)) {
resultSet.add(new GroupKey(groupBy, val));
}
} else {
@@ -187,7 +200,8 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
* or
* null if no group exists for that key.
*/
public DrawableGroup getGroupForKey(GroupKey<?> groupKey) {
@Nullable
public DrawableGroup getGroupForKey(@Nonnull GroupKey<?> groupKey) {
synchronized (groupMap) {
return groupMap.get(groupKey);
}
@@ -231,38 +245,22 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
}
/**
* make and return a new group with the given key and files. If a group
* already existed for that key, it will be replaced.
*
* NOTE: this is the only API for making a new group.
*
* @param groupKey the groupKey that uniquely identifies this group
* @param files a list of fileids that are members of this group
*
* @return the new DrawableGroup for the given key
*/
public DrawableGroup makeGroup(GroupKey<?> groupKey, List<Long> files) {
List<Long> newFiles = files == null ? new ArrayList<>() : files;
DrawableGroup g = new DrawableGroup(groupKey, newFiles);
synchronized (groupMap) {
groupMap.put(groupKey, g);
}
return g;
}
/**
* 'mark' the given group as seen. This removes it from the queue of groups
* to review, and is persisted in the drawable db.
* 'mark' the given group as seen. This removes it from the queue of
* groups to review, and is persisted in the drawable db.
*
* @param group the {@link DrawableGroup} to mark as seen
*/
@ThreadConfined(type = ThreadType.JFX)
public void markGroupSeen(DrawableGroup group) {
db.markGroupSeen(group.getGroupKey());
group.setSeen();
unSeenGroups.removeAll(group);
public void markGroupSeen(DrawableGroup group, boolean seen) {
db.markGroupSeen(group.getGroupKey(), seen);
group.setSeen(seen);
if (seen) {
unSeenGroups.removeAll(group);
} else if (unSeenGroups.contains(group) == false) {
unSeenGroups.add(group);
}
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder));
}
/**
@@ -273,97 +271,35 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
* @param groupKey the value of groupKey
* @param fileID the value of file
*/
public synchronized void removeFromGroup(GroupKey<?> groupKey, final Long fileID) {
public synchronized DrawableGroup removeFromGroup(GroupKey<?> groupKey, final Long fileID) {
//get grouping this file would be in
final DrawableGroup group = getGroupForKey(groupKey);
if (group != null) {
group.removeFile(fileID);
Platform.runLater(() -> {
group.removeFile(fileID);
});
// If we're grouping by category, we don't want to remove empty groups.
if (groupKey.getAttribute() != DrawableAttribute.CATEGORY) {
if (group.fileIds().isEmpty()) {
synchronized (groupMap) {
groupMap.remove(groupKey, group);
}
Platform.runLater(() -> {
analyzedGroups.remove(group);
unSeenGroups.remove(group);
if (analyzedGroups.contains(group)) {
analyzedGroups.remove(group);
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder));
}
if (unSeenGroups.contains(group)) {
unSeenGroups.remove(group);
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder));
}
});
}
} else {
} else { //group == null
// It may be that this was the last unanalyzed file in the group, so test
// whether the group is now fully analyzed.
popuplateIfAnalyzed(groupKey, null);
}
}
}
public synchronized void populateAnalyzedGroup(final GroupKey<?> groupKey, List<Long> filesInGroup) {
populateAnalyzedGroup(groupKey, filesInGroup, null);
}
/**
* create a group with the given GroupKey and file ids and add it to the
* analyzed group list.
*
* @param groupKey
* @param filesInGroup
*/
private synchronized <A extends Comparable<A>> void populateAnalyzedGroup(final GroupKey<A> groupKey, List<Long> filesInGroup, ReGroupTask<A> task) {
/* if this is not part of a regroup task or it is but the task is not
* cancelled...
*
* this allows us to stop if a regroup task has been cancelled (e.g. the
* user picked a different group by attribute, while the current task
* was still running) */
if (task == null || (task.isCancelled() == false)) {
DrawableGroup g = makeGroup(groupKey, filesInGroup);
populateAnalyzedGroup(g, task);
}
}
private synchronized void populateAnalyzedGroup(final DrawableGroup g, ReGroupTask<?> task) {
if (task == null || (task.isCancelled() == false)) {
final boolean groupSeen = db.isGroupSeen(g.getGroupKey());
if (groupSeen) {
g.setSeen();
}
Platform.runLater(() -> {
if (analyzedGroups.contains(g) == false) {
analyzedGroups.add(g);
}
if (groupSeen) {
unSeenGroups.removeAll(g);
} else if (unSeenGroups.contains(g) == false) {
unSeenGroups.add(g);
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder));
}
});
}
}
/**
* check if the group for the given groupkey is analyzed
*
* @param groupKey
*
* @return null if this group is not analyzed or a list of file ids in
* this
* group if they are all analyzed
*/
public List<Long> checkAnalyzed(final GroupKey<?> groupKey) {
try {
/* for attributes other than path we can't be sure a group is fully
* analyzed because we don't know all the files that will be a part
* of that group */
if ((groupKey.getAttribute() != DrawableAttribute.PATH) || db.isGroupAnalyzed(groupKey)) {
return getFileIDsInGroup(groupKey);
} else {
return null;
}
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex);
return null;
}
return group;
}
/**
@@ -438,8 +374,8 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
values = (List<A>) Arrays.asList(Category.values());
break;
case TAGS:
values = (List<A>) Case.getCurrentCase().getServices().getTagsManager().getTagNamesInUse().stream()
.filter(t -> t.getDisplayName().startsWith(Category.CATEGORY_PREFIX) == false)
values = (List<A>) controller.getTagsManager().getTagNamesInUse().stream()
.filter(CategoryManager::isNotCategoryTagName)
.collect(Collectors.toList());
break;
case ANALYZED:
@@ -462,7 +398,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
}
public List<Long> getFileIDsInGroup(GroupKey<?> groupKey) throws TskCoreException {
public Set<Long> getFileIDsInGroup(GroupKey<?> groupKey) throws TskCoreException {
switch (groupKey.getAttribute().attrName) {
//these cases get special treatment
case CATEGORY:
@@ -479,34 +415,37 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
// @@@ This was kind of slow in the profiler. Maybe we should cache it.
// Unless the list of file IDs is necessary, use countFilesWithCategory() to get the counts.
public List<Long> getFileIDsWithCategory(Category category) throws TskCoreException {
public Set<Long> getFileIDsWithCategory(Category category) throws TskCoreException {
try {
final DrawableTagsManager tagsManager = controller.getTagsManager();
if (category == Category.ZERO) {
List< TagName> tns = Stream.of(Category.ONE, Category.TWO, Category.THREE, Category.FOUR, Category.FIVE)
.map(tagsManager::getTagName)
.collect(Collectors.toList());
List<Long> files = new ArrayList<>();
TagName[] tns = {Category.FOUR.getTagName(), Category.THREE.getTagName(), Category.TWO.getTagName(), Category.ONE.getTagName(), Category.FIVE.getTagName()};
Set<Long> files = new HashSet<>();
for (TagName tn : tns) {
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(tn);
for (ContentTag ct : contentTags) {
if (ct.getContent() instanceof AbstractFile && db.isInDB(ct.getContent().getId())) {
files.add(ct.getContent().getId());
}
if (tn != null) {
List<ContentTag> contentTags = tagsManager.getContentTagsByTagName(tn);
files.addAll(contentTags.stream()
.filter(ct -> ct.getContent() instanceof AbstractFile)
.filter(ct -> db.isInDB(ct.getContent().getId()))
.map(ct -> ct.getContent().getId())
.collect(Collectors.toSet()));
}
}
return db.findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(files, ',') + ")");
} else {
List<Long> files = new ArrayList<>();
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(category.getTagName());
for (ContentTag ct : contentTags) {
if (ct.getContent() instanceof AbstractFile && db.isInDB(ct.getContent().getId())) {
files.add(ct.getContent().getId());
}
}
List<ContentTag> contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category));
return contentTags.stream()
.filter(ct -> ct.getContent() instanceof AbstractFile)
.filter(ct -> db.isInDB(ct.getContent().getId()))
.map(ct -> ct.getContent().getId())
.collect(Collectors.toSet());
return files;
}
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex);
@@ -514,17 +453,15 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
}
}
public List<Long> getFileIDsWithTag(TagName tagName) throws TskCoreException {
public Set<Long> getFileIDsWithTag(TagName tagName) throws TskCoreException {
try {
List<Long> files = new ArrayList<>();
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(tagName);
Set<Long> files = new HashSet<>();
List<ContentTag> contentTags = controller.getTagsManager().getContentTagsByTagName(tagName);
for (ContentTag ct : contentTags) {
if (ct.getContent() instanceof AbstractFile && db.isInDB(ct.getContent().getId())) {
files.add(ct.getContent().getId());
}
}
return files;
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "TSK error getting files with Tag:" + tagName.getDisplayName(), ex);
@@ -565,7 +502,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
* @param sortOrder
* @param force true to force a full db query regroup
*/
public <A extends Comparable<A>> void regroup(final DrawableAttribute<A> groupBy, final GroupSortBy sortBy, final SortOrder sortOrder, Boolean force) {
public synchronized <A extends Comparable<A>> void regroup(final DrawableAttribute<A> groupBy, final GroupSortBy sortBy, final SortOrder sortOrder, Boolean force) {
if (!Case.isCaseOpen()) {
return;
@@ -579,9 +516,6 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
if (groupByTask != null) {
groupByTask.cancel(true);
}
Platform.runLater(() -> {
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder));
});
groupByTask = new ReGroupTask<A>(groupBy, sortBy, sortOrder);
Platform.runLater(() -> {
@@ -589,12 +523,12 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
});
regroupExecutor.submit(groupByTask);
} else {
// just resort the list of groups
// resort the list of groups
setSortBy(sortBy);
setSortOrder(sortOrder);
Platform.runLater(() -> {
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder));
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder));
FXCollections.sort(unSeenGroups, sortBy.getGrpComparator(sortOrder));
});
}
}
@@ -608,86 +542,149 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
return regroupProgress.getReadOnlyProperty();
}
@Subscribe
public void handleTagAdded(ContentTagAddedEvent evt) {
GroupKey<?> newGroupKey = null;
final long fileID = evt.getTag().getContent().getId();
if (groupBy == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(evt.getTag().getName())) {
newGroupKey = new GroupKey<Category>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(evt.getTag().getName()));
for (GroupKey<?> oldGroupKey : groupMap.keySet()) {
if (oldGroupKey.equals(newGroupKey) == false) {
removeFromGroup(oldGroupKey, fileID);
}
}
} else if (groupBy == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(evt.getTag().getName())) {
newGroupKey = new GroupKey<TagName>(DrawableAttribute.TAGS, evt.getTag().getName());
}
if (newGroupKey != null) {
DrawableGroup g = getGroupForKey(newGroupKey);
addFileToGroup(g, newGroupKey, fileID);
}
}
@SuppressWarnings("AssignmentToMethodParameter")
private void addFileToGroup(DrawableGroup g, final GroupKey<?> groupKey, final long fileID) {
if (g == null) {
//if there wasn't already a group check if there should be one now
g = popuplateIfAnalyzed(groupKey, null);
}
DrawableGroup group = g;
if (group != null) {
//if there is aleady a group that was previously deemed fully analyzed, then add this newly analyzed file to it.
Platform.runLater(() -> {
group.addFile(fileID);
});
}
}
@Subscribe
public void handleTagDeleted(ContentTagDeletedEvent evt) {
GroupKey<?> groupKey = null;
if (groupBy == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(evt.getTag().getName())) {
groupKey = new GroupKey<Category>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(evt.getTag().getName()));
} else if (groupBy == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(evt.getTag().getName())) {
groupKey = new GroupKey<TagName>(DrawableAttribute.TAGS, evt.getTag().getName());
}
if (groupKey != null) {
final long fileID = evt.getTag().getContent().getId();
DrawableGroup g = removeFromGroup(groupKey, fileID);
}
}
@Subscribe
synchronized public void handleFileRemoved(Collection<Long> removedFileIDs) {
for (final long fileId : removedFileIDs) {
//get grouping(s) this file would be in
Set<GroupKey<?>> groupsForFile = getGroupKeysForFileID(fileId);
for (GroupKey<?> gk : groupsForFile) {
removeFromGroup(gk, fileId);
}
}
}
/**
* handle {@link FileUpdateEvent} sent from Db when files are
* inserted/updated
*
* TODO: why isn't this just two methods!
*
* @param evt
*/
@Override
synchronized public void handleFileUpdate(FileUpdateEvent evt) {
final Collection<Long> fileIDs = evt.getFileIDs();
switch (evt.getUpdateType()) {
case REMOVE:
for (final long fileId : fileIDs) {
//get grouping(s) this file would be in
Set<GroupKey<?>> groupsForFile = getGroupKeysForFileID(fileId);
@Subscribe
synchronized public void handleFileUpdate(Collection<Long> updatedFileIDs) {
/**
* TODO: is there a way to optimize this to avoid quering to db
* so much. the problem is that as a new files are analyzed they
* might be in new groups( if we are grouping by say make or
* model) -jm
*/
for (long fileId : updatedFileIDs) {
for (GroupKey<?> gk : groupsForFile) {
removeFromGroup(gk, fileId);
controller.getHashSetManager().invalidateHashSetsForFile(fileId);
DrawableGroup g = getGroupForKey(gk);
if (g == null) {
// It may be that this was the last unanalyzed file in the group, so test
// whether the group is now fully analyzed.
//TODO: use method in groupmanager ?
List<Long> checkAnalyzed = checkAnalyzed(gk);
if (checkAnalyzed != null) { // => the group is analyzed, so add it to the ui
populateAnalyzedGroup(gk, checkAnalyzed);
}
}
}
}
break;
case UPDATE:
/**
* TODO: is there a way to optimize this to avoid quering to db
* so much. the problem is that as a new files are analyzed they
* might be in new groups( if we are grouping by say make or
* model)
*
* TODO: Should this be a InnerTask so it can be done by the
* WorkerThread? Is it already done by worker thread because
* handlefileUpdate is invoked through call on db in UpdateTask
* innertask? -jm
*/
for (final long fileId : fileIDs) {
controller.getHashSetManager().invalidateHashSetsForFile(fileId);
//get grouping(s) this file would be in
Set<GroupKey<?>> groupsForFile = getGroupKeysForFileID(fileId);
for (GroupKey<?> gk : groupsForFile) {
DrawableGroup g = getGroupForKey(gk);
if (g != null) {
//if there is aleady a group that was previously deemed fully analyzed, then add this newly analyzed file to it.
g.addFile(fileId);
} else {
//if there wasn't already a group check if there should be one now
//TODO: use method in groupmanager ?
List<Long> checkAnalyzed = checkAnalyzed(gk);
if (checkAnalyzed != null) { // => the group is analyzed, so add it to the ui
populateAnalyzedGroup(gk, checkAnalyzed);
}
}
}
}
//we fire this event for all files so that the category counts get updated during initial db population
controller.getCategoryManager().fireChange(fileIDs);
if (evt.getChangedAttribute() == DrawableAttribute.TAGS) {
TagUtils.fireChange(fileIDs);
}
break;
//get grouping(s) this file would be in
Set<GroupKey<?>> groupsForFile = getGroupKeysForFileID(fileId);
for (GroupKey<?> gk : groupsForFile) {
DrawableGroup g = getGroupForKey(gk);
addFileToGroup(g, gk, fileId);
}
}
//we fire this event for all files so that the category counts get updated during initial db population
controller.getCategoryManager().fireChange(updatedFileIDs, null);
}
private DrawableGroup popuplateIfAnalyzed(GroupKey<?> groupKey, ReGroupTask<?> task) {
if (Objects.nonNull(task) && (task.isCancelled())) {
/* if this method call is part of a ReGroupTask and that task is
* cancelled, no-op
*
* this allows us to stop if a regroup task has been cancelled (e.g.
* the user picked a different group by attribute, while the
* current task was still running) */
} else { // no task or un-cancelled task
if ((groupKey.getAttribute() != DrawableAttribute.PATH) || db.isGroupAnalyzed(groupKey)) {
/* for attributes other than path we can't be sure a group is
* fully analyzed because we don't know all the files that
* will be a part of that group,. just show them no matter what. */
try {
Set<Long> fileIDs = getFileIDsInGroup(groupKey);
if (Objects.nonNull(fileIDs)) {
DrawableGroup group;
final boolean groupSeen = db.isGroupSeen(groupKey);
synchronized (groupMap) {
if (groupMap.containsKey(groupKey)) {
group = groupMap.get(groupKey);
group.setFiles(ObjectUtils.defaultIfNull(fileIDs, Collections.emptySet()));
} else {
group = new DrawableGroup(groupKey, fileIDs, groupSeen);
group.seenProperty().addListener((o, oldSeen, newSeen) -> {
markGroupSeen(group, newSeen);
});
groupMap.put(groupKey, group);
}
}
Platform.runLater(() -> {
if (analyzedGroups.contains(group) == false) {
analyzedGroups.add(group);
if (Objects.isNull(task)) {
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder));
}
}
markGroupSeen(group, groupSeen);
});
return group;
}
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex);
}
}
}
return null;
}
/**
@@ -728,20 +725,12 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
groupProgress = ProgressHandleFactory.createHandle("regrouping files by " + groupBy.attrName.toString() + " sorted by " + sortBy.name() + " in " + sortOrder.toString() + " order", this);
Platform.runLater(() -> {
analyzedGroups.clear();
synchronized (unSeenGroups) {
unSeenGroups.clear();
}
unSeenGroups.clear();
});
synchronized (groupMap) {
groupMap.clear();
}
// Get the list of group keys
final List<A> vals = findValuesForAttribute(groupBy);
// Make a list of each group
final List<DrawableGroup> groups = new ArrayList<>();
groupProgress.start(vals.size());
int p = 0;
@@ -754,27 +743,9 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
updateMessage("regrouping files by " + groupBy.attrName.toString() + " : " + val);
updateProgress(p, vals.size());
groupProgress.progress("regrouping files by " + groupBy.attrName.toString() + " : " + val, p);
//check if this group is analyzed
final GroupKey<A> groupKey = new GroupKey<>(groupBy, val);
List<Long> checkAnalyzed = checkAnalyzed(groupKey);
if (checkAnalyzed != null) { // != null => the group is analyzed, so add it to the ui
// makeGroup will create the group and add it to the map groupMap, but does not
// update anything else
DrawableGroup g = makeGroup(groupKey, checkAnalyzed);
groups.add(g);
}
popuplateIfAnalyzed(new GroupKey<A>(groupBy, val), this);
}
// Sort the group list
Collections.sort(groups, sortBy.getGrpComparator(sortOrder));
// Officially add all groups in order
for (DrawableGroup g : groups) {
populateAnalyzedGroup(g, ReGroupTask.this);
}
FXCollections.sort(analyzedGroups, sortBy.getGrpComparator(sortOrder));
updateProgress(1, 1);
return null;
}
@@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.grouping;
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
import java.util.Arrays;
import java.util.Comparator;
@@ -0,0 +1,7 @@
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
public enum GroupViewMode {
TILE, SLIDE_SHOW
}
@@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.grouping;
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
import java.util.Objects;
import java.util.Optional;
@@ -1,7 +0,0 @@
package org.sleuthkit.autopsy.imagegallery.grouping;
public enum GroupViewMode {
TILE, SLIDE_SHOW
}
@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<fx:root maxHeight="-1.0" maxWidth="-1.0" minHeight="-Infinity" minWidth="-Infinity" opacity="1.0" prefHeight="-1.0" prefWidth="-1.0" style="-fx-background-color: linear-gradient(to bottom, derive(-fx-base,-30%), derive(-fx-base,-60%)), linear-gradient(to bottom, derive(-fx-base,65%) 2%, derive(-fx-base,-20%) 95%); -fx-background-radius: 2;" type="javafx.scene.layout.AnchorPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<BorderPane maxHeight="-1.0" maxWidth="-1.0" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" snapToPixel="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<bottom>
<BorderPane maxHeight="-Infinity" maxWidth="-1.0" minHeight="-Infinity" minWidth="-1.0" prefHeight="-1.0" prefWidth="-1.0" BorderPane.alignment="CENTER">
<center>
<Label id="pathLabel" fx:id="nameLabel" alignment="CENTER" contentDisplay="TEXT_ONLY" maxHeight="16.0" minHeight="16.0" minWidth="-1.0" prefHeight="-1.0" prefWidth="-1.0" text="file name" textAlignment="CENTER">
<labelFor>
<ImageView fx:id="imageView" fitHeight="200.0" fitWidth="200.0" opacity="1.0" pickOnBounds="true" preserveRatio="true" style="-fx-border-radius : 5;&#10;-fx-border-width : 5;&#10;-fx-border-color : blue;" BorderPane.alignment="CENTER" />
</labelFor>
</Label>
</center>
<left>
<HBox maxHeight="-Infinity" prefHeight="-1.0" prefWidth="-1.0" spacing="2.0" BorderPane.alignment="CENTER_LEFT">
<children>
<ImageView fx:id="fileTypeImageView" fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true" scaleX="1.0" scaleY="1.0">
<image>
<Image url="@../images/video-file.png" />
</image>
</ImageView>
<ImageView fx:id="hashHitImageView" fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true" style="">
<image>
<Image url="@../images/hashset_hits.png" />
</image>
<HBox.margin>
<Insets bottom="1.0" left="1.0" right="1.0" top="1.0" fx:id="x1" />
</HBox.margin>
</ImageView>
</children>
<padding>
<Insets bottom="2.0" right="2.0" top="2.0" />
</padding>
</HBox>
</left>
<right>
<ToggleButton fx:id="followUpToggle" minWidth="24.0" mnemonicParsing="false" prefWidth="24.0" selected="false" text="">
<graphic>
<ImageView id="followUpImageview" fx:id="followUpImageView" fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../images/flag_gray.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
</right>
</BorderPane>
</bottom>
<center>
<BorderPane fx:id="imageBorder" center="$imageView" maxHeight="-1.0" maxWidth="-1.0" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" BorderPane.alignment="CENTER">
<center><ImageView fx:id="imageView" fitHeight="100.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER" />
</center></BorderPane>
</center>
</BorderPane>
</children>
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
</fx:root>
@@ -1,8 +0,0 @@
package org.sleuthkit.autopsy.imagegallery.gui;
public interface GroupView {
}
@@ -0,0 +1,62 @@
/*
* 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.imagegallery.gui;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SplitMenuButton;
import javafx.scene.image.ImageView;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.datamodel.TagName;
/**
* Static utility methods for working with GUI components
*/
public class GuiUtils {
/**
* make a new menu item that when clicked, tags the selected files with the
* given tagname
*
* @param tagName
* @param tagSelectedMenuButton
* @param controller
*
* @return
*/
public static MenuItem createSelTagMenuItem(final TagName tagName, final SplitMenuButton tagSelectedMenuButton, ImageGalleryController controller) {
final MenuItem menuItem = new MenuItem(tagName.getDisplayName(), new ImageView(DrawableAttribute.TAGS.getIcon()));
menuItem.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent t) {
new AddDrawableTagAction(controller).addTag(tagName, "");
tagSelectedMenuButton.setText(tagName.getDisplayName());
tagSelectedMenuButton.setOnAction(this);
}
});
return menuItem;
}
private GuiUtils() {
}
}
@@ -20,7 +20,7 @@ package org.sleuthkit.autopsy.imagegallery.gui;
import javafx.scene.control.ListCell;
import javafx.scene.image.ImageView;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupSortBy;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
public class SortByListCell extends ListCell<GroupSortBy> {
@@ -36,15 +36,12 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent;
/**
* Displays summary statistics (counts) for each group
*/
public class SummaryTablePane extends AnchorPane {
private static SummaryTablePane instance;
@FXML
private TableColumn<Pair<Category, Long>, String> catColumn;
@@ -53,6 +50,7 @@ public class SummaryTablePane extends AnchorPane {
@FXML
private TableView<Pair<Category, Long>> tableView;
private final ImageGalleryController controller;
@FXML
void initialize() {
@@ -74,34 +72,26 @@ public class SummaryTablePane extends AnchorPane {
tableView.getColumns().setAll(Arrays.asList(catColumn, countColumn));
// //register for category events
ImageGalleryController.getDefault().getCategoryManager().registerListener(this);
//register for category events
controller.getCategoryManager().registerListener(this);
handleCategoryChanged(null);
}
private SummaryTablePane() {
public SummaryTablePane(ImageGalleryController controller) {
this.controller = controller;
FXMLConstructor.construct(this, "SummaryTablePane.fxml");
}
public static synchronized SummaryTablePane getDefault() {
if (instance == null) {
instance = new SummaryTablePane();
}
return instance;
}
/**
* listen to Category updates and rebuild the table
*/
@Subscribe
public void handleCategoryChanged(CategoryChangeEvent evt) {
refresh();
}
public void refresh() {
public void handleCategoryChanged(org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager.CategoryChangeEvent evt) {
final ObservableList<Pair<Category, Long>> data = FXCollections.observableArrayList();
if (Case.isCaseOpen()) {
for (Category cat : Category.values()) {
data.add(new Pair<>(cat, ImageGalleryController.getDefault().getCategoryManager().getCategoryCount(cat)));
data.add(new Pair<>(cat, controller.getCategoryManager().getCategoryCount(cat)));
}
}
Platform.runLater(() -> {
@@ -44,12 +44,11 @@ import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.TagUtils;
import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupSortBy;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
@@ -105,6 +104,7 @@ public class Toolbar extends ToolBar {
ImageGalleryController.getDefault().getGroupManager().regroup(groupByBox.getSelectionModel().getSelectedItem(), sortByBox.getSelectionModel().getSelectedItem(), getSortOrder(), false);
};
private ImageGalleryController controller;
synchronized public SortOrder getSortOrder() {
return orderProperty.get();
@@ -117,9 +117,9 @@ public class Toolbar extends ToolBar {
return sizeSlider.valueProperty();
}
static synchronized public Toolbar getDefault() {
static synchronized public Toolbar getDefault(ImageGalleryController controller) {
if (instance == null) {
instance = new Toolbar();
instance = new Toolbar(controller);
}
return instance;
}
@@ -151,7 +151,7 @@ public class Toolbar extends ToolBar {
tagSelectedMenuButton.setOnAction((ActionEvent t) -> {
try {
TagUtils.createSelTagMenuItem(TagUtils.getFollowUpTagName(), tagSelectedMenuButton).getOnAction().handle(t);
GuiUtils.createSelTagMenuItem(getController().getTagsManager().getFollowUpTagName(), tagSelectedMenuButton, getController()).getOnAction().handle(t);
} catch (TskCoreException ex) {
Exceptions.printStackTrace(ex);
}
@@ -161,22 +161,22 @@ public class Toolbar extends ToolBar {
tagSelectedMenuButton.showingProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
if (t1) {
ArrayList<MenuItem> selTagMenues = new ArrayList<>();
for (final TagName tn : TagUtils.getNonCategoryTagNames()) {
MenuItem menuItem = TagUtils.createSelTagMenuItem(tn, tagSelectedMenuButton);
for (final TagName tn : getController().getTagsManager().getNonCategoryTagNames()) {
MenuItem menuItem = GuiUtils.createSelTagMenuItem(tn, tagSelectedMenuButton, getController());
selTagMenues.add(menuItem);
}
tagSelectedMenuButton.getItems().setAll(selTagMenues);
}
});
catSelectedMenuButton.setOnAction(createSelCatMenuItem(Category.FIVE, catSelectedMenuButton).getOnAction());
catSelectedMenuButton.setOnAction(createSelCatMenuItem(Category.FIVE, catSelectedMenuButton, getController()).getOnAction());
catSelectedMenuButton.setText(Category.FIVE.getDisplayName());
catSelectedMenuButton.setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon()));
catSelectedMenuButton.showingProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
if (t1) {
ArrayList<MenuItem> categoryMenues = new ArrayList<>();
for (final Category cat : Category.values()) {
MenuItem menuItem = createSelCatMenuItem(cat, catSelectedMenuButton);
MenuItem menuItem = createSelCatMenuItem(cat, catSelectedMenuButton, getController());
categoryMenues.add(menuItem);
}
catSelectedMenuButton.getItems().setAll(categoryMenues);
@@ -221,20 +221,25 @@ public class Toolbar extends ToolBar {
});
}
private Toolbar() {
private Toolbar(ImageGalleryController controller) {
this.controller = controller;
FXMLConstructor.construct(this, "Toolbar.fxml");
}
private static MenuItem createSelCatMenuItem(Category cat, final SplitMenuButton catSelectedMenuButton) {
private static MenuItem createSelCatMenuItem(Category cat, final SplitMenuButton catSelectedMenuButton, ImageGalleryController controller) {
final MenuItem menuItem = new MenuItem(cat.getDisplayName(), new ImageView(DrawableAttribute.CATEGORY.getIcon()));
menuItem.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent t) {
new CategorizeAction().addTag(cat.getTagName(), "");
new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), "");
catSelectedMenuButton.setText(cat.getDisplayName());
catSelectedMenuButton.setOnAction(this);
}
});
return menuItem;
}
private ImageGalleryController getController() {
return controller;
}
}
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<fx:root maxHeight="-1.0" maxWidth="-1.0" minHeight="-Infinity" minWidth="-Infinity" opacity="1.0" prefHeight="-1.0" prefWidth="-1.0" style="-fx-background-color: linear-gradient(to bottom, derive(-fx-base,-30%), derive(-fx-base,-60%)), linear-gradient(to bottom, derive(-fx-base,65%) 2%, derive(-fx-base,-20%) 95%); -fx-background-radius: 2;" type="javafx.scene.layout.AnchorPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
<children>
<BorderPane maxHeight="-1.0" maxWidth="-1.0" minHeight="-Infinity" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" snapToPixel="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<bottom>
<BorderPane maxHeight="-Infinity" maxWidth="-1.0" minHeight="-Infinity" minWidth="-1.0" prefHeight="-1.0" prefWidth="-1.0" BorderPane.alignment="CENTER">
<center>
<Label id="pathLabel" fx:id="nameLabel" alignment="CENTER" contentDisplay="TEXT_ONLY" maxHeight="16.0" minHeight="16.0" minWidth="-1.0" prefHeight="-1.0" prefWidth="-1.0" text="file name" textAlignment="CENTER">
<labelFor>
<ImageView fx:id="imageView" fitHeight="200.0" fitWidth="200.0" opacity="1.0" pickOnBounds="true" preserveRatio="true" style="-fx-border-radius : 5;&#10;-fx-border-width : 5;&#10;-fx-border-color : blue;" BorderPane.alignment="CENTER" />
</labelFor>
</Label>
</center>
<left>
<HBox maxHeight="-Infinity" prefHeight="-1.0" prefWidth="-1.0" spacing="2.0" BorderPane.alignment="CENTER_LEFT">
<children>
<ImageView fx:id="fileTypeImageView" fitHeight="16.0" fitWidth="16.0" mouseTransparent="true" pickOnBounds="true" preserveRatio="true" scaleX="1.0" scaleY="1.0">
<image>
<Image url="@../../images/video-file.png" />
</image>
</ImageView>
<ImageView fx:id="hashHitImageView" fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true" style="">
<image>
<Image url="@../../images/hashset_hits.png" />
</image>
<HBox.margin>
<Insets bottom="1.0" left="1.0" right="1.0" top="1.0" fx:id="x1" />
</HBox.margin>
</ImageView>
</children>
<padding>
<Insets bottom="2.0" right="2.0" top="2.0" />
</padding>
</HBox>
</left>
<right>
<ToggleButton fx:id="followUpToggle" minWidth="24.0" mnemonicParsing="false" prefWidth="24.0" selected="false" text="">
<graphic>
<ImageView id="followUpImageview" fx:id="followUpImageView" fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../images/flag_gray.png" />
</image>
</ImageView>
</graphic>
</ToggleButton>
</right>
</BorderPane>
</bottom>
<center>
<BorderPane fx:id="imageBorder" center="$imageView" maxHeight="-1.0" maxWidth="-1.0" minWidth="-Infinity" prefHeight="-1.0" prefWidth="-1.0" BorderPane.alignment="CENTER">
<center>
<ImageView fx:id="imageView" fitHeight="100.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
</center>
</BorderPane>
</children>
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
</fx:root>
@@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.gui;
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import java.util.Objects;
import java.util.logging.Level;
@@ -32,8 +32,9 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.TagUtils;
import static org.sleuthkit.autopsy.imagegallery.gui.DrawableViewBase.globalSelectionModel;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableTileBase.globalSelectionModel;
import org.sleuthkit.datamodel.AbstractContent;
/**
* GUI component that represents a single image as a tile with an icon, a label,
@@ -43,7 +44,7 @@ import static org.sleuthkit.autopsy.imagegallery.gui.DrawableViewBase.globalSele
*
* TODO: refactor this to extend from {@link Control}? -jm
*/
public class DrawableTile extends DrawableViewBase implements TagUtils.TagListener {
public class DrawableTile extends DrawableTileBase {
private static final DropShadow LAST_SELECTED_EFFECT = new DropShadow(10, Color.BLUE);
@@ -67,18 +68,17 @@ public class DrawableTile extends DrawableViewBase implements TagUtils.TagListen
assert imageBorder != null : "fx:id=\"imageAnchor\" was not injected: check your FXML file 'DrawableTile.fxml'.";
assert imageView != null : "fx:id=\"imageView\" was not injected: check your FXML file 'DrawableTile.fxml'.";
assert nameLabel != null : "fx:id=\"nameLabel\" was not injected: check your FXML file 'DrawableTile.fxml'.";
//set up properties and binding
setCache(true);
setCacheHint(CacheHint.SPEED);
nameLabel.prefWidthProperty().bind(imageView.fitWidthProperty());
imageView.fitHeightProperty().bind(Toolbar.getDefault().sizeSliderValue());
imageView.fitWidthProperty().bind(Toolbar.getDefault().sizeSliderValue());
imageView.fitHeightProperty().bind(Toolbar.getDefault(getController()).sizeSliderValue());
imageView.fitWidthProperty().bind(Toolbar.getDefault(getController()).sizeSliderValue());
globalSelectionModel.lastSelectedProperty().addListener((observable, oldValue, newValue) -> {
try {
setEffect(Objects.equals(newValue, fileID) ? LAST_SELECTED_EFFECT : null);
setEffect(Objects.equals(newValue, getFileID()) ? LAST_SELECTED_EFFECT : null);
} catch (java.lang.IllegalStateException ex) {
Logger.getLogger(DrawableTile.class.getName()).log(Level.WARNING, "Error displaying tile");
}
@@ -86,11 +86,12 @@ public class DrawableTile extends DrawableViewBase implements TagUtils.TagListen
}
public DrawableTile(GroupPane gp) {
super();
super(gp);
FXMLConstructor.construct(this, "DrawableTile.fxml");
groupPane = gp;
}
@Override
@ThreadConfined(type = ThreadType.JFX)
protected void clearContent() {
@@ -103,7 +104,7 @@ public class DrawableTile extends DrawableViewBase implements TagUtils.TagListen
@Override
protected void updateSelectionState() {
super.updateSelectionState();
final boolean lastSelected = Objects.equals(globalSelectionModel.lastSelectedProperty().get(), fileID);
final boolean lastSelected = Objects.equals(globalSelectionModel.lastSelectedProperty().get(), getFileID());
Platform.runLater(() -> {
setEffect(lastSelected ? LAST_SELECTED_EFFECT : null);
});
@@ -111,16 +112,20 @@ public class DrawableTile extends DrawableViewBase implements TagUtils.TagListen
@Override
protected Runnable getContentUpdateRunnable() {
Image image = file.getThumbnail();
if (getFile().isPresent()) {
Image image = getFile().get().getThumbnail();
return () -> {
imageView.setImage(image);
};
return () -> {
imageView.setImage(image);
};
} else {
return () -> { //no-op
};
}
}
@Override
@ThreadConfined(type = ThreadType.UI)
protected String getLabelText() {
return file.getName();
protected String getTextForLabel() {
return getFile().map(AbstractContent::getName).orElse("");
}
}
@@ -2,7 +2,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-14 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");
@@ -17,14 +17,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.imagegallery.gui;
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.Observable;
@@ -39,7 +37,6 @@ import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.BorderStroke;
@@ -50,7 +47,6 @@ import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.actions.Presenter;
import org.openide.windows.TopComponent;
@@ -64,21 +60,17 @@ import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.autopsy.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.events.TagEvent;
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
import org.sleuthkit.autopsy.imagegallery.FileUpdateEvent;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
import org.sleuthkit.autopsy.imagegallery.TagUtils;
import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction;
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
import org.sleuthkit.autopsy.imagegallery.actions.DeleteFollowUpTagAction;
import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupKey;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
@@ -89,9 +81,9 @@ import org.sleuthkit.datamodel.TskCoreException;
* of {@link DrawableView}s should implement the interface directly
*
*/
public abstract class DrawableViewBase extends AnchorPane implements DrawableView {
public abstract class DrawableTileBase extends DrawableUIBase {
private static final Logger LOGGER = Logger.getLogger(DrawableViewBase.class.getName());
private static final Logger LOGGER = Logger.getLogger(DrawableTileBase.class.getName());
private static final Border UNSELECTED_BORDER = new Border(new BorderStroke(Color.GRAY, BorderStrokeStyle.SOLID, new CornerRadii(2), new BorderWidths(3)));
@@ -104,6 +96,7 @@ public abstract class DrawableViewBase extends AnchorPane implements DrawableVie
protected static final Image followUpGray = new Image("org/sleuthkit/autopsy/imagegallery/images/flag_gray.png");
protected static final FileIDSelectionModel globalSelectionModel = FileIDSelectionModel.getInstance();
private static ContextMenu contextMenu;
/**
* displays the icon representing video files
@@ -135,19 +128,16 @@ public abstract class DrawableViewBase extends AnchorPane implements DrawableVie
@FXML
protected BorderPane imageBorder;
static private ContextMenu contextMenu;
protected DrawableFile<?> file;
protected Long fileID;
/**
* the groupPane this {@link DrawableViewBase} is embedded in
* the groupPane this {@link DrawableTileBase} is embedded in
*/
protected GroupPane groupPane;
final private GroupPane groupPane;
volatile private boolean registered = false;
protected DrawableViewBase() {
protected DrawableTileBase(GroupPane groupPane) {
super(groupPane.getController());
this.groupPane = groupPane;
globalSelectionModel.getSelected().addListener((Observable observable) -> {
updateSelectionState();
});
@@ -158,48 +148,49 @@ public abstract class DrawableViewBase extends AnchorPane implements DrawableVie
@Override
public void handle(MouseEvent t) {
getFile().ifPresent(file -> {
final long fileID = file.getId();
switch (t.getButton()) {
case PRIMARY:
if (t.getClickCount() == 1) {
if (t.isControlDown()) {
switch (t.getButton()) {
case PRIMARY:
if (t.getClickCount() == 1) {
if (t.isControlDown()) {
globalSelectionModel.toggleSelection(fileID);
} else {
groupPane.makeSelection(t.isShiftDown(), fileID);
globalSelectionModel.toggleSelection(fileID);
} else {
groupPane.makeSelection(t.isShiftDown(), fileID);
}
} else if (t.getClickCount() > 1) {
groupPane.activateSlideShowViewer(fileID);
}
} else if (t.getClickCount() > 1) {
groupPane.activateSlideShowViewer(fileID);
}
break;
case SECONDARY:
if (t.getClickCount() == 1) {
if (globalSelectionModel.isSelected(fileID) == false) {
groupPane.makeSelection(false, fileID);
break;
case SECONDARY:
if (t.getClickCount() == 1) {
if (globalSelectionModel.isSelected(fileID) == false) {
groupPane.makeSelection(false, fileID);
}
}
}
if (contextMenu != null) {
contextMenu.hide();
}
final ContextMenu groupContextMenu = groupPane.getContextMenu();
if (groupContextMenu != null) {
groupContextMenu.hide();
}
contextMenu = buildContextMenu(file);
contextMenu.show(DrawableTileBase.this, t.getScreenX(), t.getScreenY());
break;
}
});
if (contextMenu != null) {
contextMenu.hide();
}
final ContextMenu groupContextMenu = groupPane.getContextMenu();
if (groupContextMenu != null) {
groupContextMenu.hide();
}
contextMenu = buildContextMenu();
contextMenu.show(DrawableViewBase.this, t.getScreenX(), t.getScreenY());
break;
}
t.consume();
}
private ContextMenu buildContextMenu() {
private ContextMenu buildContextMenu(DrawableFile<?> file) {
final ArrayList<MenuItem> menuItems = new ArrayList<>();
menuItems.add(CategorizeAction.getPopupMenu());
menuItems.add(new CategorizeAction(getController()).getPopupMenu());
menuItems.add(AddDrawableTagAction.getInstance().getPopupMenu());
menuItems.add(new AddDrawableTagAction(getController()).getPopupMenu());
final MenuItem extractMenuItem = new MenuItem("Extract File(s)");
extractMenuItem.setOnAction((ActionEvent t) -> {
@@ -213,13 +204,13 @@ public abstract class DrawableViewBase extends AnchorPane implements DrawableVie
MenuItem contentViewer = new MenuItem("Show Content Viewer");
contentViewer.setOnAction((ActionEvent t) -> {
SwingUtilities.invokeLater(() -> {
new NewWindowViewAction("Show Content Viewer", new FileNode(getFile().getAbstractFile())).actionPerformed(null);
new NewWindowViewAction("Show Content Viewer", new FileNode(file.getAbstractFile())).actionPerformed(null);
});
});
menuItems.add(contentViewer);
MenuItem externalViewer = new MenuItem("Open in External Viewer");
final ExternalViewerAction externalViewerAction = new ExternalViewerAction("Open in External Viewer", new FileNode(getFile().getAbstractFile()));
final ExternalViewerAction externalViewerAction = new ExternalViewerAction("Open in External Viewer", new FileNode(file.getAbstractFile()));
externalViewer.setDisable(externalViewerAction.isEnabled() == false);
externalViewer.setOnAction((ActionEvent t) -> {
@@ -247,6 +238,10 @@ public abstract class DrawableViewBase extends AnchorPane implements DrawableVie
});
}
GroupPane getGroupPane() {
return groupPane;
}
@ThreadConfined(type = ThreadType.UI)
protected abstract void clearContent();
@@ -254,159 +249,144 @@ public abstract class DrawableViewBase extends AnchorPane implements DrawableVie
protected abstract Runnable getContentUpdateRunnable();
protected abstract String getLabelText();
protected abstract String getTextForLabel();
@SuppressWarnings("deprecation")
protected void initialize() {
followUpToggle.setOnAction((ActionEvent t) -> {
if (followUpToggle.isSelected() == true) {
globalSelectionModel.clearAndSelect(fileID);
try {
AddDrawableTagAction.getInstance().addTag(TagUtils.getFollowUpTagName(), "");
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Failed to add follow up tag. Could not load TagName.", ex);
}
} else {
//TODO: convert this to an action!
final ImageGalleryController controller = ImageGalleryController.getDefault();
try {
// remove file from old category group
controller.getGroupManager().removeFromGroup(new GroupKey<TagName>(DrawableAttribute.TAGS, TagUtils.getFollowUpTagName()), fileID);
List<ContentTag> contentTagsByContent = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByContent(getFile());
for (ContentTag ct : contentTagsByContent) {
if (ct.getName().getDisplayName().equals(TagUtils.getFollowUpTagName().getDisplayName())) {
Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(ct);
}
followUpToggle.setOnAction((ActionEvent event) -> {
getFile().ifPresent(file -> {
if (followUpToggle.isSelected() == true) {
try {
globalSelectionModel.clearAndSelect(file.getId());
new AddDrawableTagAction(getController()).addTag(getController().getTagsManager().getFollowUpTagName(), "");
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex);
}
IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent("TagAction", BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE)); //NON-NLS
controller.getGroupManager().handleFileUpdate(FileUpdateEvent.newUpdateEvent(Collections.singleton(fileID), DrawableAttribute.TAGS));
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Failed to delete follow up tag.", ex);
} else {
new DeleteFollowUpTagAction(getController(), file).handle(event);
}
}
});
});
}
@Override
public DrawableFile<?> getFile() {
if (fileID != null) {
if (file == null || file.getId() != fileID) {
try {
file = ImageGalleryController.getDefault().getFileFromId(fileID);
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileID, ex);
file = null;
}
}
return file;
} else {
return null;
}
}
protected boolean hasFollowUp() throws TskCoreException {
String followUpTagName = TagUtils.getFollowUpTagName().getDisplayName();
Collection<TagName> tagNames = DrawableAttribute.TAGS.getValue(getFile());
return tagNames.stream().anyMatch((tn) -> tn.getDisplayName().equals(followUpTagName));
}
protected void updateUI(final boolean isVideo, final boolean hasHashSetHits, final String text) {
if (isVideo) {
fileTypeImageView.setImage(videoIcon);
} else {
fileTypeImageView.setImage(null);
}
if (hasHashSetHits) {
hashHitImageView.setImage(hashHitIcon);
} else {
hashHitImageView.setImage(null);
}
nameLabel.setText(text);
nameLabel.setTooltip(new Tooltip(text));
}
@Override
public Long getFileID() {
return fileID;
}
@Override
public void handleTagsChanged(Collection<Long> ids) {
if (fileID != null && ids.contains(fileID)) {
updateFollowUpIcon();
}
}
protected void updateFollowUpIcon() {
if (file != null) {
protected boolean hasFollowUp() {
if (getFileID().isPresent()) {
try {
boolean hasFollowUp = hasFollowUp();
Platform.runLater(() -> {
followUpImageView.setImage(hasFollowUp ? followUpIcon : followUpGray);
followUpToggle.setSelected(hasFollowUp);
});
TagName followUpTagName = getController().getTagsManager().getFollowUpTagName();
return DrawableAttribute.TAGS.getValue(getFile().get()).stream()
.anyMatch(followUpTagName::equals);
} catch (TskCoreException ex) {
Exceptions.printStackTrace(ex);
LOGGER.log(Level.WARNING, "failed to get follow up tag name ", ex);
return true;
}
} else {
return false;
}
}
@Override
public void setFile(final Long fileID) {
if (Objects.equals(fileID, this.fileID) == false) {
this.fileID = fileID;
disposeContent();
synchronized protected void setFileHelper(final Long newFileID) {
setFileIDOpt(Optional.ofNullable(newFileID));
disposeContent();
if (this.fileID == null || Case.isCaseOpen() == false) {
ImageGalleryController.getDefault().getCategoryManager().unregisterListener(this);
TagUtils.unregisterListener(this);
file = null;
Platform.runLater(() -> {
clearContent();
});
} else {
ImageGalleryController.getDefault().getCategoryManager().registerListener(this);
TagUtils.registerListener(this);
getFile();
updateSelectionState();
updateCategoryBorder();
updateFollowUpIcon();
final String text = getLabelText();
final boolean isVideo = file.isVideo();
final boolean hasHashSetHits = hasHashHit();
Platform.runLater(() -> {
updateUI(isVideo, hasHashSetHits, text);
});
Platform.runLater(getContentUpdateRunnable());
if (getFileID().isPresent() == false || Case.isCaseOpen() == false) {
if (registered == true) {
getController().getCategoryManager().unregisterListener(this);
getController().getTagsManager().unregisterListener(this);
registered = false;
}
setFileOpt(Optional.empty());
Platform.runLater(() -> {
clearContent();
});
} else {
if (registered == false) {
getController().getCategoryManager().registerListener(this);
getController().getTagsManager().registerListener(this);
registered = true;
}
setFileOpt(Optional.empty());
updateSelectionState();
updateCategory();
updateFollowUpIcon();
updateUI();
Platform.runLater(getContentUpdateRunnable());
}
}
private void updateUI() {
getFile().ifPresent(file -> {
final boolean isVideo = file.isVideo();
final boolean hasHashSetHits = hasHashHit();
final String text = getTextForLabel();
Platform.runLater(() -> {
fileTypeImageView.setImage(isVideo ? videoIcon : null);
hashHitImageView.setImage(hasHashSetHits ? hashHitIcon : null);
nameLabel.setText(text);
nameLabel.setTooltip(new Tooltip(text));
});
});
}
/**
* update the visual representation of the selection state of this
* DrawableView
*/
protected void updateSelectionState() {
final boolean selected = globalSelectionModel.isSelected(fileID);
Platform.runLater(() -> {
setBorder(selected ? SELECTED_BORDER : UNSELECTED_BORDER);
getFile().ifPresent(file -> {
final boolean selected = globalSelectionModel.isSelected(file.getId());
Platform.runLater(() -> {
setBorder(selected ? SELECTED_BORDER : UNSELECTED_BORDER);
});
});
}
@Override
public Region getBorderable() {
public Region getCategoryBorderRegion() {
return imageBorder;
}
@Subscribe
@Override
public void handleCategoryChanged(CategoryChangeEvent evt) {
if (evt.getIds().contains(fileID)) {
updateCategoryBorder();
}
public void handleTagAdded(ContentTagAddedEvent evt) {
handleTagEvent(evt, () -> {
Platform.runLater(() -> {
followUpImageView.setImage(followUpIcon);
followUpToggle.setSelected(true);
});
});
}
@Subscribe
@Override
public void handleTagDeleted(ContentTagDeletedEvent evt) {
handleTagEvent(evt, this::updateFollowUpIcon);
}
void handleTagEvent(TagEvent<ContentTag> evt, Runnable runnable) {
getFileID().ifPresent(fileID -> {
try {
final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName();
final ContentTag deletedTag = evt.getTag();
if (fileID == deletedTag.getContent().getId()
&& deletedTag.getName().equals(followUpTagName)) {
runnable.run();
}
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Failed to get followup tag name. Unable to update follow up status for file. ", ex);
}
});
}
private void updateFollowUpIcon() {
boolean hasFollowUp = hasFollowUp();
Platform.runLater(() -> {
followUpImageView.setImage(hasFollowUp ? followUpIcon : followUpGray);
followUpToggle.setSelected(hasFollowUp);
});
}
}
@@ -0,0 +1,95 @@
/*
* 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.imagegallery.gui.drawableviews;
import java.util.Objects;
import static java.util.Objects.nonNull;
import java.util.Optional;
import java.util.logging.Level;
import javafx.scene.layout.AnchorPane;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.datamodel.TskCoreException;
/**
*
*/
abstract public class DrawableUIBase extends AnchorPane implements DrawableView {
private final ImageGalleryController controller;
private Optional<DrawableFile<?>> fileOpt = Optional.empty();
private Optional<Long> fileIDOpt = Optional.empty();
public DrawableUIBase(ImageGalleryController controller) {
this.controller = controller;
}
@Override
public ImageGalleryController getController() {
return controller;
}
@Override
public Optional<Long> getFileID() {
return fileIDOpt;
}
synchronized void setFileIDOpt(Optional<Long> fileIDOpt) {
this.fileIDOpt = fileIDOpt;
}
synchronized void setFileOpt(Optional<DrawableFile<?>> fileOpt) {
this.fileOpt = fileOpt;
}
@Override
synchronized public Optional<DrawableFile<?>> getFile() {
if (fileIDOpt.isPresent()) {
if (fileOpt.isPresent() && fileOpt.get().getId() == fileIDOpt.get()) {
return fileOpt;
} else {
try {
fileOpt = Optional.of(getController().getFileFromId(fileIDOpt.get()));
} catch (TskCoreException ex) {
Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex);
fileOpt = Optional.empty();
}
return fileOpt;
}
} else {
return Optional.empty();
}
}
protected abstract void setFileHelper(Long newFileID);
@Override
synchronized public void setFile(Long newFileID) {
if (getFileID().isPresent()) {
if (Objects.equals(newFileID, getFileID().get()) == false) {
setFileHelper(newFileID);
}
} else if (nonNull(newFileID)) {
setFileHelper(newFileID);
}
}
}
@@ -1,7 +1,8 @@
package org.sleuthkit.autopsy.imagegallery.gui;
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.eventbus.Subscribe;
import java.util.Collection;
import java.util.Optional;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.scene.layout.Border;
@@ -13,9 +14,10 @@ import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.TagUtils;
import org.sleuthkit.autopsy.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
@@ -25,7 +27,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
* } to have there {@link DrawableView#handleCategoryChanged(org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent)
* } method invoked
*/
public interface DrawableView extends TagUtils.TagListener {
public interface DrawableView {
//TODO: do this all in css? -jm
static final int CAT_BORDER_WIDTH = 10;
@@ -48,13 +50,13 @@ public interface DrawableView extends TagUtils.TagListener {
static final Border CAT0_BORDER = new Border(new BorderStroke(Category.ZERO.getColor(), BorderStrokeStyle.SOLID, CAT_CORNER_RADII, CAT_BORDER_WIDTHS));
Region getBorderable();
Region getCategoryBorderRegion();
DrawableFile<?> getFile();
Optional<DrawableFile<?>> getFile();
void setFile(final Long fileID);
Long getFileID();
Optional<Long> getFileID();
/**
* update the visual representation of the category of the assigned file.
@@ -65,14 +67,28 @@ public interface DrawableView extends TagUtils.TagListener {
* @param evt the CategoryChangeEvent to handle
*/
@Subscribe
void handleCategoryChanged(CategoryChangeEvent evt);
default void handleCategoryChanged(org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager.CategoryChangeEvent evt) {
getFileID().ifPresent(fileID -> {
if (evt.getFileIDs().contains(fileID)) {
updateCategory();
}
});
}
@Override
void handleTagsChanged(Collection<Long> ids);
@Subscribe
void handleTagAdded(ContentTagAddedEvent evt);
@Subscribe
void handleTagDeleted(ContentTagDeletedEvent evt);
ImageGalleryController getController();
default boolean hasHashHit() {
try {
return getFile().getHashHitSetNames().isEmpty() == false;
return getFile().map(DrawableFile::getHashHitSetNames)
.map((Collection<String> t) -> t.isEmpty() == false)
.orElse(false);
} catch (NullPointerException ex) {
// I think this happens when we're in the process of removing images from the view while
// also trying to update it?
@@ -82,33 +98,39 @@ public interface DrawableView extends TagUtils.TagListener {
}
static Border getCategoryBorder(Category category) {
switch (category) {
case ZERO:
return CAT0_BORDER;
case ONE:
return CAT1_BORDER;
case TWO:
return CAT2_BORDER;
case THREE:
return CAT3_BORDER;
case FOUR:
return CAT4_BORDER;
case FIVE:
default:
return CAT5_BORDER;
if (category != null) {
switch (category) {
case ONE:
return CAT1_BORDER;
case TWO:
return CAT2_BORDER;
case THREE:
return CAT3_BORDER;
case FOUR:
return CAT4_BORDER;
case FIVE:
return CAT5_BORDER;
case ZERO:
default:
return CAT0_BORDER;
}
} else {
return CAT0_BORDER;
}
}
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
default Category updateCategoryBorder() {
final Category category = getFile().getCategory();
final Border border = hasHashHit() && (category == Category.ZERO)
? HASH_BORDER
: DrawableView.getCategoryBorder(category);
Platform.runLater(() -> {
getBorderable().setBorder(border);
});
return category;
default Category updateCategory() {
if (getFile().isPresent()) {
final Category category = getFile().map(DrawableFile::getCategory).orElse(Category.ZERO);
final Border border = hasHashHit() && (category == Category.ZERO) ? HASH_BORDER : getCategoryBorder(category);
Platform.runLater(() -> {
getCategoryBorderRegion().setBorder(border);
});
return category;
} else {
return Category.ZERO;
}
}
}

Some files were not shown because too many files have changed in this diff Show More