From a5a69b803604a5c7959daddb00f40b6b2297b70c Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 16 Jul 2013 18:07:37 -0400 Subject: [PATCH 01/20] First draft of implementation of multi-select tagging and export --- .../corecomponents/DataResultViewerTable.java | 2 +- .../datamodel/AbstractAbstractFileNode.java | 26 ++++- .../datamodel/AbstractContentChildren.java | 1 - .../autopsy/datamodel/DirectoryNode.java | 9 +- .../sleuthkit/autopsy/datamodel/FileNode.java | 14 ++- .../autopsy/datamodel/LayoutFileNode.java | 10 +- .../autopsy/datamodel/LocalFileNode.java | 12 +-- .../org/sleuthkit/autopsy/datamodel/Tags.java | 33 ------ .../datamodel/VirtualDirectoryNode.java | 11 +- .../directorytree/DataResultFilterNode.java | 45 ++++---- .../DirectoryTreeFilterNode.java | 4 +- .../ExplorerNodeActionVisitor.java | 25 +++-- .../autopsy/directorytree/ExtractAction.java | 96 ++++++----------- .../directorytree/TagAbstractFileAction.java | 51 +++++++++ .../autopsy/directorytree/TagAction.java | 102 ++++-------------- .../directorytree/TagAndCommentDialog.java | 75 +++++++------ .../TagBlackboardArtifactAction.java | 51 +++++++++ .../autopsy/directorytree/TagMenu.java | 73 ++++++------- 18 files changed, 318 insertions(+), 322 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java create mode 100755 Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 9ab0d40dea..6f5368578b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -62,7 +62,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { ov.setAllowedDropActions(DnDConstants.ACTION_NONE); // only allow one item to be selected at a time - ov.getOutline().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + ov.getOutline().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // don't show the root node ov.getOutline().setRootVisible(false); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index adb6cb6249..51b2781c60 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -21,6 +21,8 @@ package org.sleuthkit.autopsy.datamodel; import java.util.Map; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.directorytree.TagAbstractFileAction; +import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; @@ -33,6 +35,14 @@ public abstract class AbstractAbstractFileNode extends A private static Logger logger = Logger.getLogger(AbstractAbstractFileNode.class.getName()); + /** + * These Actions are class instances to support multi-selection of nodes corresponding to AbstractFiles. + * They must be a class instances because org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick + * up an Action if every selected node returns a reference to it from Node.getActions(boolean). + */ + private static TagAbstractFileAction tagAction = new TagAbstractFileAction(); + private static ExtractAction extractAction = new ExtractAction(); + /** * @param type of the AbstractFile data to encapsulate * @param abstractFile file to encapsulate @@ -153,8 +163,7 @@ public abstract class AbstractAbstractFileNode extends A } } } - - + /** * Fill map with AbstractFile properties * @@ -191,8 +200,15 @@ public abstract class AbstractAbstractFileNode extends A map.put(AbstractFilePropertyType.MD5HASH.toString(), content.getMd5Hash() == null ? "" : content.getMd5Hash()); } - - static String getContentDisplayName(AbstractFile file) { + protected static TagAbstractFileAction getTagAbstractFileActionInstance() { + return tagAction; + } + + protected static ExtractAction getExtractActionInstance() { + return extractAction; + } + + protected static String getContentDisplayName(AbstractFile file) { String name = file.getName(); if (name.equals("..")) { name = DirectoryNode.DOTDOTDIR; @@ -201,4 +217,4 @@ public abstract class AbstractAbstractFileNode extends A } return name; } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index 76ad367ace..3e10b86c93 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datamodel; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children.Keys; import org.openide.nodes.Node; -import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; import org.sleuthkit.autopsy.datamodel.KeywordHits.KeywordHitsRootNode; import org.sleuthkit.datamodel.DerivedFile; import org.sleuthkit.datamodel.Directory; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java index a364071ac7..827552cfa2 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java @@ -23,7 +23,6 @@ import java.util.List; import javax.swing.Action; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Directory; @@ -34,7 +33,7 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; * are more directories. */ public class DirectoryNode extends AbstractFsContentNode { - + public static final String DOTDOTDIR = "[parent folder]"; public static final String DOTDIR = "[current folder]"; @@ -67,16 +66,16 @@ public class DirectoryNode extends AbstractFsContentNode { */ @Override public Action[] getActions(boolean popup) { - List actions = new ArrayList(); + List actions = new ArrayList<>(); if (!getDirectoryBrowseMode()) { actions.add(new ViewContextAction("View File in Directory", this)); actions.add(null); // creates a menu separator } actions.add(new NewWindowViewAction("View in New Window", this)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract Directory", this)); + actions.add(getExtractActionInstance()); actions.add(null); // creates a menu separator - actions.add(new TagAction(this)); + actions.add(getTagAbstractFileActionInstance()); return actions.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index 6c3f739624..efbaf4e517 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -21,14 +21,13 @@ package org.sleuthkit.autopsy.datamodel; import java.util.ArrayList; import java.util.List; import javax.swing.Action; +import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getExtractActionInstance; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.FsContent; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; @@ -37,7 +36,7 @@ import org.sleuthkit.datamodel.TskData.TSK_FS_NAME_FLAG_ENUM; * files children. */ public class FileNode extends AbstractFsContentNode { - + /** * @param file underlying Content */ @@ -74,7 +73,7 @@ public class FileNode extends AbstractFsContentNode { */ @Override public Action[] getActions(boolean popup) { - List actionsList = new ArrayList(); + List actionsList = new ArrayList<>(); if (!this.getDirectoryBrowseMode()) { actionsList.add(new ViewContextAction("View File in Directory", this)); actionsList.add(null); // creates a menu separator @@ -82,10 +81,10 @@ public class FileNode extends AbstractFsContentNode { actionsList.add(new NewWindowViewAction("View in New Window", this)); actionsList.add(new ExternalViewerAction("Open in External Viewer", this)); actionsList.add(null); // creates a menu separator - actionsList.add(new ExtractAction("Extract File", this)); + actionsList.add(getExtractActionInstance()); actionsList.add(new HashSearchAction("Search for files with the same MD5 hash", this)); - actionsList.add(null); // creates a menu separator - actionsList.add(new TagAction(this)); + actionsList.add(null); // creates a menu separator + actionsList.add(getTagAbstractFileActionInstance()); return actionsList.toArray(new Action[0]); } @@ -166,7 +165,6 @@ public class FileNode extends AbstractFsContentNode { } // Else return the default return "org/sleuthkit/autopsy/images/file-icon.png"; - } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index 36f2fc8c02..899c8b045b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -24,11 +24,11 @@ import java.util.List; import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; -import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; +import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getExtractActionInstance; +import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getTagAbstractFileActionInstance; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAction; import org.sleuthkit.datamodel.LayoutFile; import org.sleuthkit.datamodel.TskData; @@ -104,14 +104,12 @@ public class LayoutFileNode extends AbstractAbstractFileNode { @Override public Action[] getActions(boolean context) { List actionsList = new ArrayList(); - actionsList.add(new NewWindowViewAction("View in New Window", this)); actionsList.add(new ExternalViewerAction("Open in External Viewer", this)); actionsList.add(null); // creates a menu separator - actionsList.add(new ExtractAction("Extract File", content)); + actionsList.add(getExtractActionInstance()); actionsList.add(null); // creates a menu separator - actionsList.add(new TagAction(content)); - + actionsList.add(getTagAbstractFileActionInstance()); return actionsList.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index f2dc17d9a0..60d535e4ce 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -25,14 +25,14 @@ import java.util.List; import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; +import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getExtractActionInstance; +import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getTagAbstractFileActionInstance; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode.TYPE; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAction; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.LocalFile; /** * A Node for a LocalFile or DerivedFile content object. @@ -86,16 +86,14 @@ public class LocalFileNode extends AbstractAbstractFileNode { @Override public Action[] getActions(boolean context) { - List actionsList = new ArrayList(); - + List actionsList = new ArrayList<>(); actionsList.add(new NewWindowViewAction("View in New Window", this)); actionsList.add(new ExternalViewerAction("Open in External Viewer", this)); actionsList.add(null); // creates a menu separator - actionsList.add(new ExtractAction("Extract", content)); //might not need this actions - already local file + actionsList.add(getExtractActionInstance()); actionsList.add(new HashSearchAction("Search for files with the same MD5 hash", this)); actionsList.add(null); // creates a menu separator - actionsList.add(new TagAction(content)); - + actionsList.add(getTagAbstractFileActionInstance()); return actionsList.toArray(new Action[0]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index 319d36e737..064f953660 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -22,7 +22,6 @@ import java.awt.event.ActionEvent; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; @@ -656,36 +655,4 @@ public class Tags implements AutopsyVisitableItem { return tagNames; } - - public interface Taggable { - void createTag(String name, String comment); - } - - public static class TaggableFile implements Taggable { - - private AbstractFile file; - - public TaggableFile(AbstractFile file) { - this.file = file; - } - - @Override - public void createTag(String name, String comment) { - Tags.createTag(file, name, comment); - } - } - - public static class TaggableBlackboardArtifact implements Taggable { - - private BlackboardArtifact bba; - - public TaggableBlackboardArtifact(BlackboardArtifact bba) { - this.bba = bba; - } - - @Override - public void createTag(String name, String comment) { - Tags.createTag(bba, name, comment); - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java index 59e523ff81..ac612e225f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java @@ -25,9 +25,9 @@ import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; import org.sleuthkit.autopsy.coreutils.Logger; +import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getExtractActionInstance; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; -import org.sleuthkit.autopsy.directorytree.TagAction; import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.TskData; @@ -76,16 +76,15 @@ public class VirtualDirectoryNode extends AbstractAbstractFileNode actions = new ArrayList(); - + List actions = new ArrayList<>(); actions.add(new NewWindowViewAction("View in New Window", this)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract Directory", this)); + actions.add(getExtractActionInstance()); actions.add(null); // creates a menu separator - actions.add(new TagAction(this)); + actions.add(getTagAbstractFileActionInstance()); return actions.toArray(new Action[0]); } - + @Override protected Sheet createSheet() { Sheet s = super.createSheet(); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index a439a066bf..6ea9191bf5 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -81,6 +81,15 @@ import org.sleuthkit.datamodel.VirtualDirectory; */ public class DataResultFilterNode extends FilterNode { + /** + * These are class instances to support multi-selection of nodes corresponding to AbstractFiles and BlackboardArtifacts. + * They are required because org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every selected + * node returns a reference to it from Node.getActions(boolean). + */ + private final static Action extractAction = new ExtractAction(); + private final static Action fileTagAction = new TagAbstractFileAction(); + private final static Action resultTagAction = new TagBlackboardArtifactAction(); + private ExplorerManager sourceEm; private final DisplayableItemNodeVisitor> getActionsDIV; private final DisplayableItemNodeVisitor getPreferredActionsDIV; @@ -105,7 +114,7 @@ public class DataResultFilterNode extends FilterNode { @Override public Action[] getActions(boolean popup) { - List actions = new ArrayList(); + List actions = new ArrayList<>(); final DisplayableItemNode originalNode = (DisplayableItemNode) this.getOriginal(); actions.addAll(originalNode.accept(getActionsDIV)); @@ -167,7 +176,7 @@ public class DataResultFilterNode extends FilterNode { //TODO all actions need to be consolidated in single place! //they should be set in individual Node subclass and using a utility to get Actions per Content sub-type - List actions = new ArrayList(); + List actions = new ArrayList<>(); //merge predefined specific node actions if bban subclasses have their own for (Action a : ban.getActions(true)) { @@ -197,15 +206,15 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", fn)); actions.add(new ExternalViewerAction("Open in External Viewer", fn)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract File", new FileNode(f))); + actions.add(extractAction); actions.add(new HashSearchAction("Search for files with the same MD5 hash", fn)); //add file/result tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(new TagAction(f)); - actions.add(new TagAction(ba)); + actions.add(fileTagAction); + actions.add(resultTagAction); } } if ((d = ban.getLookup().lookup(Directory.class)) != null) { @@ -214,14 +223,14 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", dn)); actions.add(new ExternalViewerAction("Open in External Viewer", dn)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract Directory", dn)); + actions.add(extractAction); //add file/result tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(new TagAction(d)); - actions.add(new TagAction(ba)); + actions.add(fileTagAction); + actions.add(resultTagAction); } } if ((vd = ban.getLookup().lookup(VirtualDirectory.class)) != null) { @@ -230,14 +239,14 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", dn)); actions.add(new ExternalViewerAction("Open in External Viewer", dn)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract Directory", dn)); + actions.add(extractAction); //add file/result tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(new TagAction(d)); - actions.add(new TagAction(ba)); + actions.add(fileTagAction); + actions.add(resultTagAction); } } else if ((lf = ban.getLookup().lookup(LayoutFile.class)) != null) { LayoutFileNode lfn = new LayoutFileNode(lf); @@ -245,14 +254,14 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", lfn)); actions.add(new ExternalViewerAction("Open in External Viewer", lfn)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract File", lfn)); + actions.add(extractAction); //add tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(new TagAction(lf)); - actions.add(new TagAction(ba)); + actions.add(fileTagAction); + actions.add(resultTagAction); } } else if ((locF = ban.getLookup().lookup(LocalFile.class)) != null || (locF = ban.getLookup().lookup(DerivedFile.class)) != null) { @@ -261,14 +270,14 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", locfn)); actions.add(new ExternalViewerAction("Open in External Viewer", locfn)); actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract File", locfn)); + actions.add(extractAction); //add tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { actions.add(null); // creates a menu separator - actions.add(new TagAction(lf)); - actions.add(new TagAction(ba)); + actions.add(fileTagAction); + actions.add(resultTagAction); } } @@ -278,7 +287,7 @@ public class DataResultFilterNode extends FilterNode { @Override protected List defaultVisit(DisplayableItemNode ditem) { //preserve the default node's actions - List actions = new ArrayList(); + List actions = new ArrayList<>(); for (Action action : ditem.getActions(true)) { actions.add(action); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java index 018679ae2c..034e392e80 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java @@ -47,6 +47,7 @@ import org.sleuthkit.datamodel.TskCoreException; class DirectoryTreeFilterNode extends FilterNode { private static final Action collapseAll = new CollapseAction("Collapse All"); + private static final Action extractAction = new ExtractAction(); private static final Logger logger = Logger.getLogger(DirectoryTreeFilterNode.class.getName()); /** @@ -99,8 +100,7 @@ class DirectoryTreeFilterNode extends FilterNode { //extract dir action Directory dir = this.getLookup().lookup(Directory.class); if (dir != null) { - actions.add(new ExtractAction("Extract Directory", - getOriginal())); + actions.add(extractAction); } // file search action diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java index 28479e83c3..96af70d439 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java @@ -47,6 +47,13 @@ import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.Volume; public class ExplorerNodeActionVisitor extends ContentVisitor.Default> { + /** + * These are class instances to support multi-selection of nodes corresponding to AbstractFiles and BlackboardArtifacts. + * They are required because org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every selected + * node returns a reference to it from Node.getActions(boolean). + */ + private static Action extractAction = new ExtractAction(); + private static Action tagAction = new TagAbstractFileAction(); private static ExplorerNodeActionVisitor instance = new ExplorerNodeActionVisitor(); @@ -101,39 +108,39 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final Directory d) { List actions = new ArrayList(); - actions.add(new TagAction(d)); + actions.add(tagAction); return actions; } @Override public List visit(final VirtualDirectory d) { List actions = new ArrayList(); - actions.add(new TagAction(d)); - actions.add(new ExtractAction("Extract Directory", d)); + actions.add(extractAction); + actions.add(tagAction); return actions; } @Override public List visit(final DerivedFile d) { List actions = new ArrayList(); - actions.add(new ExtractAction("Extract File", d)); - actions.add(new TagAction(d)); + actions.add(extractAction); + actions.add(tagAction); return actions; } @Override public List visit(final LocalFile d) { List actions = new ArrayList(); - actions.add(new ExtractAction("Extract File", d)); - actions.add(new TagAction(d)); + actions.add(extractAction); + actions.add(tagAction); return actions; } @Override public List visit(final org.sleuthkit.datamodel.File d) { List actions = new ArrayList(); - actions.add(new ExtractAction("Extract File", d)); - actions.add(new TagAction(d)); + actions.add(extractAction); + actions.add(tagAction); return actions; } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java index 6b183644f1..630b5d1a02 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java @@ -32,11 +32,15 @@ import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.nodes.Node; import org.openide.util.Cancellable; +import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; +import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; @@ -47,66 +51,12 @@ import org.sleuthkit.datamodel.Directory; */ public final class ExtractAction extends AbstractAction { - private static final InitializeContentVisitor initializeCV = new InitializeContentVisitor(); - private AbstractFile content; private Logger logger = Logger.getLogger(ExtractAction.class.getName()); - public ExtractAction(String title, Node contentNode) { - super(title); - Content tempContent = contentNode.getLookup().lookup(Content.class); - - this.content = tempContent.accept(initializeCV); - this.setEnabled(content != null); + public ExtractAction() { + super("Export"); } - public ExtractAction(String title, Content content) { - super(title); - - this.content = content.accept(initializeCV); - this.setEnabled(this.content != null); - } - - /** - * Returns the FsContent if it is supported, otherwise null - */ - private static class InitializeContentVisitor extends ContentVisitor.Default { - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.File f) { - return f; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.LayoutFile lf) { - return lf; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.DerivedFile df) { - return df; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.LocalFile lf) { - return lf; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.VirtualDirectory vd) { - return vd; - } - - @Override - public AbstractFile visit(Directory dir) { - return ContentUtils.isDotDirectory(dir) ? null : dir; - } - - @Override - protected AbstractFile defaultVisit(Content cntnt) { - return null; - } - } - /** * Asks user to choose destination, then extracts content/directory to * destination (recursing on directories) @@ -114,10 +64,35 @@ public final class ExtractAction extends AbstractAction { */ @Override public void actionPerformed(ActionEvent e) { + DataResultViewerTable resultViewer = (DataResultViewerTable)Lookup.getDefault().lookup(DataResultViewer.class); + if (null == resultViewer) { + Logger.getLogger(TagAction.class.getName()).log(Level.SEVERE, "Could not get DataResultViewerTable from Lookup"); + return; + } + + Node[] selectedNodes = resultViewer.getExplorerManager().getSelectedNodes(); + if (selectedNodes.length <= 0) { + Logger.getLogger(TagAction.class.getName()).log(Level.SEVERE, "Tried to perform tagging of Nodes with no Nodes selected"); + return; + } + + for (Node node : selectedNodes) { + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + if (null != file) { + extractFile(e, file); + } + else { + // RJCTODO +// Logger.getLogger(org.sleuthkit.autopsy.directorytree.TagAbstractFileAction.TagAbstractFileMenu.class.getName()).log(Level.SEVERE, "Node not associated with an AbstractFile object"); + } + } + } + + private void extractFile(ActionEvent e, AbstractFile file) { // Get content and check that it's okay to overwrite existing content JFileChooser fc = new JFileChooser(); fc.setCurrentDirectory(new File(Case.getCurrentCase().getCaseDirectory())); - fc.setSelectedFile(new File(this.content.getName())); + fc.setSelectedFile(new File(file.getName())); int returnValue = fc.showSaveDialog((Component) e.getSource()); if (returnValue == JFileChooser.APPROVE_OPTION) { @@ -144,12 +119,12 @@ public final class ExtractAction extends AbstractAction { try { ExtractFileThread extract = new ExtractFileThread(); - extract.init(this.content, e, destination); + extract.init(file, e, destination); extract.execute(); } catch (Exception ex) { logger.log(Level.WARNING, "Unable to start background thread.", ex); } - } + } } private class ExtractFileThread extends SwingWorker { @@ -230,6 +205,5 @@ public final class ExtractAction extends AbstractAction { } } } - } - + } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java new file mode 100755 index 0000000000..6ba7a518ed --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java @@ -0,0 +1,51 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit 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.directorytree; + +import java.util.logging.Level; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.Tags; +import org.sleuthkit.datamodel.AbstractFile; + +public class TagAbstractFileAction extends TagAction { + @Override + protected TagMenu getTagMenu(Node[] selectedNodes) { + return new TagAbstractFileMenu(selectedNodes); + } + + private static class TagAbstractFileMenu extends TagMenu { + public TagAbstractFileMenu(Node[] nodes) { + super((nodes.length > 1 ? "Tag Files" : "Tag File"), nodes); + } + + @Override + protected void tagNodes(String tagName, String comment) { + for (Node node : getNodes()) { + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + if (null != file) { + Tags.createTag(file, tagName, comment); + } + else { + Logger.getLogger(org.sleuthkit.autopsy.directorytree.TagAbstractFileAction.TagAbstractFileMenu.class.getName()).log(Level.SEVERE, "Node not associated with an AbstractFile object"); + } + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagAction.java index 35c755c0e2..436e3e4541 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/TagAction.java @@ -21,102 +21,38 @@ package org.sleuthkit.autopsy.directorytree; import java.awt.event.ActionEvent; import java.util.logging.Level; import javax.swing.AbstractAction; -import javax.swing.JMenu; import javax.swing.JMenuItem; import org.openide.nodes.Node; import org.openide.util.actions.Presenter; +import org.openide.util.Lookup; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.Content; -import org.sleuthkit.datamodel.ContentVisitor; -import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; +import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; /** - * Action on a file or artifact that adds a tag and - * reloads the directory tree. Supports tagging of AbstractFiles and - * BlackboardArtifacts. - * - * TODO add use enters description and hierarchy (TSK_TAG_NAME with slashes) + * Action on a file or artifact that adds a tag and reloads the directory tree. + * Supports tagging of AbstractFiles and BlackboardArtifacts. */ -public class TagAction extends AbstractAction implements Presenter.Popup { - - private static final Logger logger = Logger.getLogger(TagAction.class.getName()); - private JMenu tagMenu; - private final InitializeBookmarkFileV initializer = new InitializeBookmarkFileV(); - - public TagAction(Node contentNode) { - AbstractFile file = contentNode.getLookup().lookup(AbstractFile.class); - if (file != null) { - tagMenu = new TagMenu(file); - return; - } - - BlackboardArtifact bba = contentNode.getLookup().lookup(BlackboardArtifact.class); - if (bba != null) { - tagMenu = new TagMenu(bba); - return; - } - - logger.log(Level.SEVERE, "Tried to create a " + TagAction.class.getName() - + " using a Node whose lookup did not contain an AbstractFile or a BlackboardArtifact."); - } - - public TagAction(AbstractFile file) { - tagMenu = new TagMenu(file); - } - - public TagAction(BlackboardArtifact bba) { - tagMenu = new TagMenu(bba); - } - +public abstract class TagAction extends AbstractAction implements Presenter.Popup { @Override public JMenuItem getPopupPresenter() { - return tagMenu; - } - - /** - * Returns the FsContent if it is supported, otherwise null - */ - private static class InitializeBookmarkFileV extends ContentVisitor.Default { - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.File f) { - return f; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.LayoutFile lf) { - return lf; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.DerivedFile lf) { - return lf; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.LocalFile lf) { - return lf; - } - - @Override - public AbstractFile visit(org.sleuthkit.datamodel.VirtualDirectory ld) { - return ld; - } - - @Override - public AbstractFile visit(Directory dir) { - return ContentUtils.isDotDirectory(dir) ? null : dir; - } - - @Override - protected AbstractFile defaultVisit(Content cntnt) { + DataResultViewerTable resultViewer = (DataResultViewerTable)Lookup.getDefault().lookup(DataResultViewer.class); + if (null == resultViewer) { + Logger.getLogger(TagAction.class.getName()).log(Level.SEVERE, "Could not get DataResultViewerTable from Lookup"); return null; } + + Node[] selectedNodes = resultViewer.getExplorerManager().getSelectedNodes(); + if (selectedNodes.length <= 0) { + Logger.getLogger(TagAction.class.getName()).log(Level.SEVERE, "Tried to perform tagging of Nodes with no Nodes selected"); + return null; + } + + return getTagMenu(selectedNodes); } + protected abstract TagMenu getTagMenu(Node[] selectedNodes); + @Override public void actionPerformed(ActionEvent e) { // Do nothing - this action should never be performed diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.java index a36a78809b..6a66e55689 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/TagAndCommentDialog.java @@ -30,28 +30,50 @@ import javax.swing.JFrame; import javax.swing.KeyStroke; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.autopsy.datamodel.Tags.Taggable; -import org.sleuthkit.datamodel.BlackboardArtifact; /** * Tag dialog for tagging files and results. User enters an optional comment. */ public class TagAndCommentDialog extends JDialog { - private static final String TAG_ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; - private static final String BOOKMARK_ICON_PATH = "org/sleuthkit/autopsy/images/star-bookmark-icon-16.png"; - private static final String NO_TAG_MESSAGE = "No Tags"; - - private Taggable taggable; + private static final String NO_TAG_MESSAGE = "No Tags"; + private String tagName = ""; + private String comment = ""; + public static class CommentedTag { + private String name; + private String comment; + + CommentedTag(String name, String comment) { + this.name = name; + this.comment = comment; + } + + public String getName() { + return name; + } + + public String getComment() { + return comment; + } + } + + public static CommentedTag doDialog() { + TagAndCommentDialog dialog = new TagAndCommentDialog(); + if (!dialog.tagName.isEmpty()) { + return new CommentedTag(dialog.tagName, dialog.comment); + } + else { + return null; + } + } + /** * Creates new form TagDialog */ - public TagAndCommentDialog(Taggable taggable) { + private TagAndCommentDialog() { super((JFrame)WindowManager.getDefault().getMainWindow(), "Tag and Comment", true); - this.taggable = taggable; - initComponents(); // Close the dialog when Esc is pressed @@ -60,8 +82,8 @@ public class TagAndCommentDialog extends JDialog { inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), cancelName); ActionMap actionMap = getRootPane().getActionMap(); actionMap.put(cancelName, new AbstractAction() { + @Override public void actionPerformed(ActionEvent e) { - //doClose(RET_CANCEL); dispose(); } }); @@ -81,15 +103,10 @@ public class TagAndCommentDialog extends JDialog { //center it this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); - - customizeComponent(); setVisible(true); // blocks } - - private void customizeComponent() { - } - + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -195,22 +212,12 @@ public class TagAndCommentDialog extends JDialog { }// //GEN-END:initComponents private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - //doClose(RET_OK); - - // get the selected tag and comment - String selectedTag = (String)tagCombo.getSelectedItem(); - String comment = commentText.getText(); - - // create the tag - taggable.createTag(selectedTag, comment); - - refreshDirectoryTree(); - + tagName = (String)tagCombo.getSelectedItem(); + comment = commentText.getText(); dispose(); }//GEN-LAST:event_okButtonActionPerformed private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed - //doClose(RET_CANCEL); dispose(); }//GEN-LAST:event_cancelButtonActionPerformed @@ -218,14 +225,12 @@ public class TagAndCommentDialog extends JDialog { * Closes the dialog */ private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog - //doClose(RET_CANCEL); dispose(); }//GEN-LAST:event_closeDialog private void newTagButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTagButtonActionPerformed String newTagName = CreateTagDialog.getNewTagNameDialog(null); if (newTagName != null) { - //tagsModel.addElement(newTagName); tagCombo.addItem(newTagName); tagCombo.setSelectedItem(newTagName); } @@ -240,12 +245,4 @@ public class TagAndCommentDialog extends JDialog { private javax.swing.JComboBox tagCombo; private javax.swing.JLabel tagLabel; // End of variables declaration//GEN-END:variables - //private int returnStatus = RET_CANCEL; - - private void refreshDirectoryTree() { - //TODO instead should send event to node children, which will call its refresh() / refreshKeys() - DirectoryTreeTopComponent viewer = DirectoryTreeTopComponent.findInstance(); - viewer.refreshTree(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE); - viewer.refreshTree(BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT); - } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java new file mode 100755 index 0000000000..b8f7fefb96 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java @@ -0,0 +1,51 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2013 Basis Technology Corp. + * Contact: carrier sleuthkit 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.directorytree; + +import java.util.logging.Level; +import org.openide.nodes.Node; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.Tags; +import org.sleuthkit.datamodel.BlackboardArtifact; + +public class TagBlackboardArtifactAction extends TagAction { + @Override + protected TagMenu getTagMenu(Node[] selectedNodes) { + return new TagBlackboardArtifactMenu(selectedNodes); + } + + private static class TagBlackboardArtifactMenu extends TagMenu { + public TagBlackboardArtifactMenu(Node[] nodes) { + super((nodes.length > 1 ? "Tag Results" : "Tag Result"), nodes); + } + + @Override + protected void tagNodes(String tagName, String comment) { + for (Node node : getNodes()) { + BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class); + if (null != artifact) { + Tags.createTag(artifact, tagName, comment); + } + else { + Logger.getLogger(org.sleuthkit.autopsy.directorytree.TagBlackboardArtifactAction.TagBlackboardArtifactMenu.class.getName()).log(Level.SEVERE, "Node not associated with a BlackboardArtifact object"); + } + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagMenu.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagMenu.java index af432e199a..9d6709f39b 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/TagMenu.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/TagMenu.java @@ -23,39 +23,26 @@ import java.awt.event.ActionListener; import java.util.List; import javax.swing.JMenu; import javax.swing.JMenuItem; +import org.openide.nodes.Node; import org.sleuthkit.autopsy.datamodel.Tags; -import org.sleuthkit.autopsy.datamodel.Tags.Taggable; -import org.sleuthkit.autopsy.datamodel.Tags.TaggableBlackboardArtifact; -import org.sleuthkit.autopsy.datamodel.Tags.TaggableFile; -import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; /** * The menu that results when one right-clicks on a file or artifact. */ -public class TagMenu extends JMenu { +public abstract class TagMenu extends JMenu { - private Taggable tagCreator; - - public TagMenu(AbstractFile file) { - super("Tag File"); - tagCreator = new TaggableFile(file); - init(); - } - - public TagMenu(BlackboardArtifact bba) { - super("Tag Result"); - tagCreator = new TaggableBlackboardArtifact(bba); - init(); - } + private Node[] nodes; - private void init() { - - // create the 'Quick Tag' menu and add it to the 'Tag File' menu + public TagMenu(String menuItemText, Node[] selectedNodes) { + super(menuItemText); + this.nodes = selectedNodes; + + // Create the 'Quick Tag' sub-menu and add it to the tag menu. JMenu quickTagMenu = new JMenu("Quick Tag"); - add(quickTagMenu); - - // create the 'Quick Tag' sub-menu items and add them to the 'Quick Tag' menu + add(quickTagMenu); + + // Get the existing tag names. List tagNames = Tags.getTagNames(); if (tagNames.isEmpty()) { JMenuItem empty = new JMenuItem("No tags"); @@ -63,46 +50,56 @@ public class TagMenu extends JMenu { quickTagMenu.add(empty); } + // Add a menu item for each existing tag name to the 'Quick Tag' menu. for (final String tagName : tagNames) { - JMenuItem tagItem = new JMenuItem(tagName); - tagItem.addActionListener(new ActionListener() { + JMenuItem tagNameItem = new JMenuItem(tagName); + tagNameItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - tagCreator.createTag(tagName, ""); + tagNodes(tagName, ""); refreshDirectoryTree(); } }); - quickTagMenu.add(tagItem); + quickTagMenu.add(tagNameItem); } quickTagMenu.addSeparator(); - // create the 'New Tag' menu item + // Create the 'New Tag' menu item and add it to the 'Quick Tag' menu. JMenuItem newTagMenuItem = new JMenuItem("New Tag"); newTagMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - String newTagName = CreateTagDialog.getNewTagNameDialog(null); - if (newTagName != null) { - tagCreator.createTag(newTagName, ""); + String tagName = CreateTagDialog.getNewTagNameDialog(null); + if (tagName != null) { + tagNodes(tagName, ""); refreshDirectoryTree(); } } }); - - // add the 'New Tag' menu item to the 'Quick Tag' menu quickTagMenu.add(newTagMenuItem); - JMenuItem newTagItem = new JMenuItem("Tag and Comment"); - newTagItem.addActionListener(new ActionListener() { + // Create the 'Tag and Comment' menu item and add it to the tag menu. + JMenuItem tagAndCommentItem = new JMenuItem("Tag and Comment"); + tagAndCommentItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - new TagAndCommentDialog(tagCreator); + TagAndCommentDialog.CommentedTag commentedTag = TagAndCommentDialog.doDialog(); + if (null != commentedTag) { + tagNodes(commentedTag.getName(), commentedTag.getComment()); + refreshDirectoryTree(); + } } }); - add(newTagItem); + add(tagAndCommentItem); } + protected Node[] getNodes() { + return nodes; + } + + protected abstract void tagNodes(String tagName, String comment); + private void refreshDirectoryTree() { //TODO instead should send event to node children, which will call its refresh() / refreshKeys() DirectoryTreeTopComponent viewer = DirectoryTreeTopComponent.findInstance(); From d6e82a872181aec8f3983f008c6cb336ea4909f6 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 18 Jul 2013 15:08:10 -0400 Subject: [PATCH 02/20] Backed out addition of multi-selection feature for file extraction. --- .../datamodel/AbstractAbstractFileNode.java | 12 +-- .../autopsy/datamodel/DirectoryNode.java | 2 +- .../sleuthkit/autopsy/datamodel/FileNode.java | 3 +- .../autopsy/datamodel/LayoutFileNode.java | 3 +- .../autopsy/datamodel/LocalFileNode.java | 3 +- .../datamodel/VirtualDirectoryNode.java | 3 +- .../directorytree/DataResultFilterNode.java | 13 ++- .../DirectoryTreeFilterNode.java | 4 +- .../ExplorerNodeActionVisitor.java | 23 +++-- .../autopsy/directorytree/ExtractAction.java | 96 ++++++++++++------- .../directorytree/TagAbstractFileAction.java | 0 .../TagBlackboardArtifactAction.java | 0 .../KeywordSearchFilterNode.java | 21 ++-- .../recentactivity/RAImageIngestModule.java | 0 14 files changed, 102 insertions(+), 81 deletions(-) mode change 100755 => 100644 Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java mode change 100755 => 100644 Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java mode change 100755 => 100644 RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index 51b2781c60..ce0a57d13e 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -22,7 +22,6 @@ import java.util.Map; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.TagAbstractFileAction; -import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; @@ -36,12 +35,11 @@ public abstract class AbstractAbstractFileNode extends A private static Logger logger = Logger.getLogger(AbstractAbstractFileNode.class.getName()); /** - * These Actions are class instances to support multi-selection of nodes corresponding to AbstractFiles. - * They must be a class instances because org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick + * This Action is a class instance to support multi-selection of nodes corresponding to AbstractFiles. + * It must be a class instances because org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick * up an Action if every selected node returns a reference to it from Node.getActions(boolean). */ private static TagAbstractFileAction tagAction = new TagAbstractFileAction(); - private static ExtractAction extractAction = new ExtractAction(); /** * @param type of the AbstractFile data to encapsulate @@ -203,11 +201,7 @@ public abstract class AbstractAbstractFileNode extends A protected static TagAbstractFileAction getTagAbstractFileActionInstance() { return tagAction; } - - protected static ExtractAction getExtractActionInstance() { - return extractAction; - } - + protected static String getContentDisplayName(AbstractFile file) { String name = file.getName(); if (name.equals("..")) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java index 827552cfa2..6b3a474535 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java @@ -73,7 +73,7 @@ public class DirectoryNode extends AbstractFsContentNode { } actions.add(new NewWindowViewAction("View in New Window", this)); actions.add(null); // creates a menu separator - actions.add(getExtractActionInstance()); + actions.add(new ExtractAction("Extract Directory", this)); actions.add(null); // creates a menu separator actions.add(getTagAbstractFileActionInstance()); return actions.toArray(new Action[0]); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java index efbaf4e517..b4b8930d97 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileNode.java @@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datamodel; import java.util.ArrayList; import java.util.List; import javax.swing.Action; -import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getExtractActionInstance; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.HashSearchAction; @@ -81,7 +80,7 @@ public class FileNode extends AbstractFsContentNode { actionsList.add(new NewWindowViewAction("View in New Window", this)); actionsList.add(new ExternalViewerAction("Open in External Viewer", this)); actionsList.add(null); // creates a menu separator - actionsList.add(getExtractActionInstance()); + actionsList.add(new ExtractAction("Extract File", this)); actionsList.add(new HashSearchAction("Search for files with the same MD5 hash", this)); actionsList.add(null); // creates a menu separator actionsList.add(getTagAbstractFileActionInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index 899c8b045b..09d3b41f15 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; -import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getExtractActionInstance; import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getTagAbstractFileActionInstance; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; @@ -107,7 +106,7 @@ public class LayoutFileNode extends AbstractAbstractFileNode { actionsList.add(new NewWindowViewAction("View in New Window", this)); actionsList.add(new ExternalViewerAction("Open in External Viewer", this)); actionsList.add(null); // creates a menu separator - actionsList.add(getExtractActionInstance()); + actionsList.add(new ExtractAction("Extract", content)); //might not need this actions - already local file actionsList.add(null); // creates a menu separator actionsList.add(getTagAbstractFileActionInstance()); return actionsList.toArray(new Action[0]); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index 60d535e4ce..b9eee78aba 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; -import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getExtractActionInstance; import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getTagAbstractFileActionInstance; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode.TYPE; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; @@ -90,7 +89,7 @@ public class LocalFileNode extends AbstractAbstractFileNode { actionsList.add(new NewWindowViewAction("View in New Window", this)); actionsList.add(new ExternalViewerAction("Open in External Viewer", this)); actionsList.add(null); // creates a menu separator - actionsList.add(getExtractActionInstance()); + actionsList.add(new ExtractAction("Extract", content)); //might not need this actions - already local file actionsList.add(new HashSearchAction("Search for files with the same MD5 hash", this)); actionsList.add(null); // creates a menu separator actionsList.add(getTagAbstractFileActionInstance()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java index ac612e225f..39be8bfedb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java @@ -25,7 +25,6 @@ import java.util.Map; import javax.swing.Action; import org.openide.nodes.Sheet; import org.sleuthkit.autopsy.coreutils.Logger; -import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.getExtractActionInstance; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.datamodel.VirtualDirectory; @@ -79,7 +78,7 @@ public class VirtualDirectoryNode extends AbstractAbstractFileNode actions = new ArrayList<>(); actions.add(new NewWindowViewAction("View in New Window", this)); actions.add(null); // creates a menu separator - actions.add(getExtractActionInstance()); + actions.add(new ExtractAction("Extract Directory", this)); actions.add(null); // creates a menu separator actions.add(getTagAbstractFileActionInstance()); return actions.toArray(new Action[0]); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 6ea9191bf5..ab5fe89c20 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -86,7 +86,6 @@ public class DataResultFilterNode extends FilterNode { * They are required because org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every selected * node returns a reference to it from Node.getActions(boolean). */ - private final static Action extractAction = new ExtractAction(); private final static Action fileTagAction = new TagAbstractFileAction(); private final static Action resultTagAction = new TagBlackboardArtifactAction(); @@ -206,7 +205,7 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", fn)); actions.add(new ExternalViewerAction("Open in External Viewer", fn)); actions.add(null); // creates a menu separator - actions.add(extractAction); + actions.add(new ExtractAction("Extract File", new FileNode(f))); actions.add(new HashSearchAction("Search for files with the same MD5 hash", fn)); //add file/result tag if itself is not a tag @@ -223,7 +222,7 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", dn)); actions.add(new ExternalViewerAction("Open in External Viewer", dn)); actions.add(null); // creates a menu separator - actions.add(extractAction); + actions.add(new ExtractAction("Extract Directory", dn)); //add file/result tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() @@ -239,7 +238,7 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", dn)); actions.add(new ExternalViewerAction("Open in External Viewer", dn)); actions.add(null); // creates a menu separator - actions.add(extractAction); + actions.add(new ExtractAction("Extract Directory", dn)); //add file/result tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() @@ -254,7 +253,7 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", lfn)); actions.add(new ExternalViewerAction("Open in External Viewer", lfn)); actions.add(null); // creates a menu separator - actions.add(extractAction); + actions.add(new ExtractAction("Extract File", lfn)); //add tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() @@ -270,8 +269,8 @@ public class DataResultFilterNode extends FilterNode { actions.add(new NewWindowViewAction("View in New Window", locfn)); actions.add(new ExternalViewerAction("Open in External Viewer", locfn)); actions.add(null); // creates a menu separator - actions.add(extractAction); - + actions.add(new ExtractAction("Extract File", locfn)); + //add tag if itself is not a tag if (artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_FILE.getTypeID() && artifactTypeID != BlackboardArtifact.ARTIFACT_TYPE.TSK_TAG_ARTIFACT.getTypeID()) { diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java index 034e392e80..018679ae2c 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java @@ -47,7 +47,6 @@ import org.sleuthkit.datamodel.TskCoreException; class DirectoryTreeFilterNode extends FilterNode { private static final Action collapseAll = new CollapseAction("Collapse All"); - private static final Action extractAction = new ExtractAction(); private static final Logger logger = Logger.getLogger(DirectoryTreeFilterNode.class.getName()); /** @@ -100,7 +99,8 @@ class DirectoryTreeFilterNode extends FilterNode { //extract dir action Directory dir = this.getLookup().lookup(Directory.class); if (dir != null) { - actions.add(extractAction); + actions.add(new ExtractAction("Extract Directory", + getOriginal())); } // file search action diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java index 96af70d439..551718b5f3 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java @@ -48,11 +48,10 @@ import org.sleuthkit.datamodel.Volume; public class ExplorerNodeActionVisitor extends ContentVisitor.Default> { /** - * These are class instances to support multi-selection of nodes corresponding to AbstractFiles and BlackboardArtifacts. - * They are required because org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every selected + * This is a class instance to support multi-selection of nodes corresponding to AbstractFiles and BlackboardArtifacts. + * It is required because org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every selected * node returns a reference to it from Node.getActions(boolean). */ - private static Action extractAction = new ExtractAction(); private static Action tagAction = new TagAbstractFileAction(); private static ExplorerNodeActionVisitor instance = new ExplorerNodeActionVisitor(); @@ -85,7 +84,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final Image img) { - List lst = new ArrayList(); + List lst = new ArrayList<>(); lst.add(new ImageDetails("Image Details", img)); //TODO lst.add(new ExtractAction("Extract Image", img)); lst.add(new ExtractUnallocAction("Extract Unallocated Space to Single Files", img)); @@ -99,7 +98,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final Volume vol) { - List lst = new ArrayList(); + List lst = new ArrayList<>(); lst.add(new VolumeDetails("Volume Details", vol)); lst.add(new ExtractUnallocAction("Extract Unallocated Space to Single File", vol)); return lst; @@ -107,23 +106,23 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final Directory d) { - List actions = new ArrayList(); + List actions = new ArrayList<>(); actions.add(tagAction); return actions; } @Override public List visit(final VirtualDirectory d) { - List actions = new ArrayList(); - actions.add(extractAction); + List actions = new ArrayList<>(); + actions.add(new ExtractAction("Extract Directory", d)); actions.add(tagAction); return actions; } @Override public List visit(final DerivedFile d) { - List actions = new ArrayList(); - actions.add(extractAction); + List actions = new ArrayList<>(); + actions.add(new ExtractAction("Extract Directory", d)); actions.add(tagAction); return actions; } @@ -131,7 +130,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final LocalFile d) { List actions = new ArrayList(); - actions.add(extractAction); + actions.add(new ExtractAction("Extract File", d)); actions.add(tagAction); return actions; } @@ -139,7 +138,7 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final org.sleuthkit.datamodel.File d) { List actions = new ArrayList(); - actions.add(extractAction); + actions.add(new ExtractAction("Extract File", d)); actions.add(tagAction); return actions; } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java index 630b5d1a02..6b183644f1 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java @@ -32,15 +32,11 @@ import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.openide.nodes.Node; import org.openide.util.Cancellable; -import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; -import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable; import org.sleuthkit.autopsy.coreutils.FileUtil; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor; -import org.sleuthkit.autopsy.datamodel.Tags; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; @@ -51,12 +47,66 @@ import org.sleuthkit.datamodel.Directory; */ public final class ExtractAction extends AbstractAction { + private static final InitializeContentVisitor initializeCV = new InitializeContentVisitor(); + private AbstractFile content; private Logger logger = Logger.getLogger(ExtractAction.class.getName()); - public ExtractAction() { - super("Export"); + public ExtractAction(String title, Node contentNode) { + super(title); + Content tempContent = contentNode.getLookup().lookup(Content.class); + + this.content = tempContent.accept(initializeCV); + this.setEnabled(content != null); } + public ExtractAction(String title, Content content) { + super(title); + + this.content = content.accept(initializeCV); + this.setEnabled(this.content != null); + } + + /** + * Returns the FsContent if it is supported, otherwise null + */ + private static class InitializeContentVisitor extends ContentVisitor.Default { + + @Override + public AbstractFile visit(org.sleuthkit.datamodel.File f) { + return f; + } + + @Override + public AbstractFile visit(org.sleuthkit.datamodel.LayoutFile lf) { + return lf; + } + + @Override + public AbstractFile visit(org.sleuthkit.datamodel.DerivedFile df) { + return df; + } + + @Override + public AbstractFile visit(org.sleuthkit.datamodel.LocalFile lf) { + return lf; + } + + @Override + public AbstractFile visit(org.sleuthkit.datamodel.VirtualDirectory vd) { + return vd; + } + + @Override + public AbstractFile visit(Directory dir) { + return ContentUtils.isDotDirectory(dir) ? null : dir; + } + + @Override + protected AbstractFile defaultVisit(Content cntnt) { + return null; + } + } + /** * Asks user to choose destination, then extracts content/directory to * destination (recursing on directories) @@ -64,35 +114,10 @@ public final class ExtractAction extends AbstractAction { */ @Override public void actionPerformed(ActionEvent e) { - DataResultViewerTable resultViewer = (DataResultViewerTable)Lookup.getDefault().lookup(DataResultViewer.class); - if (null == resultViewer) { - Logger.getLogger(TagAction.class.getName()).log(Level.SEVERE, "Could not get DataResultViewerTable from Lookup"); - return; - } - - Node[] selectedNodes = resultViewer.getExplorerManager().getSelectedNodes(); - if (selectedNodes.length <= 0) { - Logger.getLogger(TagAction.class.getName()).log(Level.SEVERE, "Tried to perform tagging of Nodes with no Nodes selected"); - return; - } - - for (Node node : selectedNodes) { - AbstractFile file = node.getLookup().lookup(AbstractFile.class); - if (null != file) { - extractFile(e, file); - } - else { - // RJCTODO -// Logger.getLogger(org.sleuthkit.autopsy.directorytree.TagAbstractFileAction.TagAbstractFileMenu.class.getName()).log(Level.SEVERE, "Node not associated with an AbstractFile object"); - } - } - } - - private void extractFile(ActionEvent e, AbstractFile file) { // Get content and check that it's okay to overwrite existing content JFileChooser fc = new JFileChooser(); fc.setCurrentDirectory(new File(Case.getCurrentCase().getCaseDirectory())); - fc.setSelectedFile(new File(file.getName())); + fc.setSelectedFile(new File(this.content.getName())); int returnValue = fc.showSaveDialog((Component) e.getSource()); if (returnValue == JFileChooser.APPROVE_OPTION) { @@ -119,12 +144,12 @@ public final class ExtractAction extends AbstractAction { try { ExtractFileThread extract = new ExtractFileThread(); - extract.init(file, e, destination); + extract.init(this.content, e, destination); extract.execute(); } catch (Exception ex) { logger.log(Level.WARNING, "Unable to start background thread.", ex); } - } + } } private class ExtractFileThread extends SwingWorker { @@ -205,5 +230,6 @@ public final class ExtractAction extends AbstractAction { } } } - } + } + } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java old mode 100755 new mode 100644 diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java old mode 100755 new mode 100644 diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java index bb9945dfa4..a1c6da0284 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java @@ -33,6 +33,7 @@ import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.autopsy.directorytree.TagAbstractFileAction; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; import org.sleuthkit.datamodel.DerivedFile; @@ -44,7 +45,13 @@ import org.sleuthkit.datamodel.File; * the full highlighted content as a MarkupSource */ class KeywordSearchFilterNode extends FilterNode { - + /** + * This Action is a class instance to support multi-selection of nodes corresponding to AbstractFiles. + * It must be a class instances because org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick + * up an Action if every selected node returns a reference to it from Node.getActions(boolean). + */ + private static TagAbstractFileAction tagAction = new TagAbstractFileAction(); + String solrQuery; int previewChunk; @@ -123,7 +130,7 @@ class KeywordSearchFilterNode extends FilterNode { @Override public Action[] getActions(boolean popup) { - List actions = new ArrayList(); + List actions = new ArrayList<>(); Content content = this.getOriginal().getLookup().lookup(Content.class); actions.addAll(content.accept(new GetPopupActionsContentVisitor())); @@ -137,33 +144,33 @@ class KeywordSearchFilterNode extends FilterNode { @Override public List visit(File f) { - List actions = new ArrayList(); + List actions = new ArrayList<>(); actions.add(new NewWindowViewAction("View in New Window", KeywordSearchFilterNode.this)); actions.add(new ExternalViewerAction("Open in External Viewer", getOriginal())); actions.add(null); actions.add(new ExtractAction("Extract File", getOriginal())); actions.add(new HashSearchAction("Search for files with the same MD5 hash", getOriginal())); actions.add(null); // creates a menu separator - actions.add(new TagAction(getOriginal())); + actions.add(tagAction); return actions; } @Override public List visit(DerivedFile f) { - List actions = new ArrayList(); + List actions = new ArrayList<>(); actions.add(new NewWindowViewAction("View in New Window", KeywordSearchFilterNode.this)); actions.add(new ExternalViewerAction("Open in External Viewer", getOriginal())); actions.add(null); actions.add(new ExtractAction("Extract File", getOriginal())); actions.add(new HashSearchAction("Search for files with the same MD5 hash", getOriginal())); actions.add(null); // creates a menu separator - actions.add(new TagAction(getOriginal())); + actions.add(tagAction); return actions; } @Override protected List defaultVisit(Content c) { - return new ArrayList(); + return new ArrayList<>(); } } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/RAImageIngestModule.java old mode 100755 new mode 100644 From a431a747be88010acdd629a9e44697135d1e83fd Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 18 Jul 2013 15:10:40 -0400 Subject: [PATCH 03/20] Additional files for back out of mult-select feature for file extraction --- Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java | 0 .../sleuthkit/autopsy/directorytree/TagAbstractFileAction.java | 0 .../autopsy/directorytree/TagBlackboardArtifactAction.java | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java mode change 100644 => 100755 Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java mode change 100644 => 100755 Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractAction.java old mode 100644 new mode 100755 diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagAbstractFileAction.java old mode 100644 new mode 100755 diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/TagBlackboardArtifactAction.java old mode 100644 new mode 100755 From 317a31b47fdfe10f5e2bddc1ad18a491ab73f29c Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Fri, 19 Jul 2013 08:15:25 -0400 Subject: [PATCH 04/20] Updated function calls in srcupdater --- test/script/srcupdater.py | 373 +++++++++++++++++++------------------- 1 file changed, 187 insertions(+), 186 deletions(-) diff --git a/test/script/srcupdater.py b/test/script/srcupdater.py index b498e1edb7..99a393d9eb 100644 --- a/test/script/srcupdater.py +++ b/test/script/srcupdater.py @@ -1,186 +1,187 @@ -import codecs -import datetime -import logging -import os -import re -import shutil -import socket -import sqlite3 -import subprocess -import sys -from sys import platform as _platform -import time -import traceback -import xml -from xml.dom.minidom import parse, parseString -import Emailer - -def compile(errore, attachli, parsedin): - global redo - global tryredo - global failedbool - global errorem - errorem = errore - global attachl - attachl = attachli - global passed - global parsed - parsed = parsedin - passed = True - tryredo = False - redo = True - while(redo): - passed = True - if(passed): - gitPull("sleuthkit") - if(passed): - vsBuild() - if(passed): - gitPull("autopsy") - if(passed): - antBuild("datamodel", False) - if(passed): - antBuild("autopsy", True) - if(passed): - redo = False - else: - print("Compile Failed") - time.sleep(3600) - attachl = [] - errorem = "The test standard didn't match the gold standard.\n" - failedbool = False - if(tryredo): - errorem = "" - errorem += "Rebuilt properly.\n" - Emailer.send_email(parsed, errorem, attachl, True) - attachl = [] - passed = True - -#Pulls from git -def gitPull(TskOrAutopsy): - global SYS - global errorem - global attachl - ccwd = "" - gppth = Emailer.make_local_path("..", "GitPullOutput" + TskOrAutopsy + ".txt") - attachl.append(gppth) - gpout = open(gppth, 'a') - toPull = "https://www.github.com/sleuthkit/" + TskOrAutopsy - call = ["git", "pull", toPull] - if TskOrAutopsy == "sleuthkit": - ccwd = os.path.join("..", "..", "..", "sleuthkit") - else: - ccwd = os.path.join("..", "..") - subprocess.call(call, stdout=sys.stdout, cwd=ccwd) - gpout.close() - - -#Builds TSK as a win32 applicatiion -def vsBuild(): - global redo - global tryredo - global passed - global parsed - #Please ensure that the current working directory is $autopsy/testing/script - oldpath = os.getcwd() - os.chdir(os.path.join("..", "..", "..","sleuthkit", "win32")) - vs = [] - vs.append("/cygdrive/c/windows/microsoft.NET/framework/v4.0.30319/MSBuild.exe") - vs.append(os.path.join("Tsk-win.sln")) - vs.append("/p:configuration=release") - vs.append("/p:platform=win32") - vs.append("/t:clean") - vs.append("/t:rebuild") - print(vs) - VSpth = Emailer.make_local_path("..", "VSOutput.txt") - VSout = open(VSpth, 'a') - subprocess.call(vs, stdout=VSout) - VSout.close() - os.chdir(oldpath) - chk = os.path.join("..", "..", "..","sleuthkit", "win32", "Release", "libtsk_jni.dll") - try: - open(chk) - except IOError as e: - global errorem - global attachl - if(not tryredo): - errorem += "LIBTSK C++ failed to build.\n" - attachl.append(VSpth) - Emailer.send_email(parsed, errorem, attachl, False) - tryredo = True - passed = False - redo = True - - - -#Builds Autopsy or the Datamodel -def antBuild(which, Build): - global redo - global passed - global tryredo - global parsed - directory = os.path.join("..", "..") - ant = [] - if which == "datamodel": - directory = os.path.join("..", "..", "..", "sleuthkit", "bindings", "java") - ant.append("ant") - ant.append("-f") - ant.append(directory) - ant.append("clean") - if(Build): - ant.append("build") - else: - ant.append("dist") - antpth = Emailer.make_local_path("..", "ant" + which + "Output.txt") - antout = open(antpth, 'a') - succd = subprocess.call(ant, stdout=antout) - antout.close() - global errorem - global attachl - if which == "datamodel": - chk = os.path.join("..", "..", "..","sleuthkit", "bindings", "java", "dist", "TSK_DataModel.jar") - try: - open(chk) - except IOError as e: - if(not tryredo): - errorem += "DataModel Java build failed.\n" - attachl.append(antpth) - Emailer.send_email(parsed, errorem, attachl, False) - passed = False - tryredo = True - elif (succd != 0 and (not tryredo)): - errorem += "Autopsy build failed.\n" - attachl.append(antpth) - Emailer.send_email(parsed, errorem, attachl, False) - tryredo = True - elif (succd != 0): - passed = False - - -def main(): - errore = "" - attachli = [] - config_file = "" - arg = sys.argv.pop(0) - arg = sys.argv.pop(0) - config_file = arg - parsedin = parse(config_file) - compile(errore, attachli, parsedin) - -class OS: - LINUX, MAC, WIN, CYGWIN = range(4) -if __name__ == "__main__": - global SYS - if _platform == "linux" or _platform == "linux2": - SYS = OS.LINUX - elif _platform == "darwin": - SYS = OS.MAC - elif _platform == "win32": - SYS = OS.WIN - elif _platform == "cygwin": - SYS = OS.CYGWIN - - if SYS is OS.WIN or SYS is OS.CYGWIN: - main() - else: - print("We only support Windows and Cygwin at this time.") \ No newline at end of file +import codecs +import datetime +import logging +import os +import re +import shutil +import socket +import sqlite3 +import subprocess +import sys +from sys import platform as _platform +import time +import traceback +import xml +from xml.dom.minidom import parse, parseString +import Emailer +from regression_utils import * + +def compile(errore, attachli, parsedin): + global redo + global tryredo + global failedbool + global errorem + errorem = errore + global attachl + attachl = attachli + global passed + global parsed + parsed = parsedin + passed = True + tryredo = False + redo = True + while(redo): + passed = True + if(passed): + gitPull("sleuthkit") + if(passed): + vsBuild() + if(passed): + gitPull("autopsy") + if(passed): + antBuild("datamodel", False) + if(passed): + antBuild("autopsy", True) + if(passed): + redo = False + else: + print("Compile Failed") + time.sleep(3600) + attachl = [] + errorem = "The test standard didn't match the gold standard.\n" + failedbool = False + if(tryredo): + errorem = "" + errorem += "Rebuilt properly.\n" + Emailer.send_email(parsed, errorem, attachl, True) + attachl = [] + passed = True + +#Pulls from git +def gitPull(TskOrAutopsy): + global SYS + global errorem + global attachl + ccwd = "" + gppth = make_local_path("..", "GitPullOutput" + TskOrAutopsy + ".txt") + attachl.append(gppth) + gpout = open(gppth, 'a') + toPull = "https://www.github.com/sleuthkit/" + TskOrAutopsy + call = ["git", "pull", toPull] + if TskOrAutopsy == "sleuthkit": + ccwd = os.path.join("..", "..", "..", "sleuthkit") + else: + ccwd = os.path.join("..", "..") + subprocess.call(call, stdout=sys.stdout, cwd=ccwd) + gpout.close() + + +#Builds TSK as a win32 applicatiion +def vsBuild(): + global redo + global tryredo + global passed + global parsed + #Please ensure that the current working directory is $autopsy/testing/script + oldpath = os.getcwd() + os.chdir(os.path.join("..", "..", "..","sleuthkit", "win32")) + vs = [] + vs.append("/cygdrive/c/windows/microsoft.NET/framework/v4.0.30319/MSBuild.exe") + vs.append(os.path.join("Tsk-win.sln")) + vs.append("/p:configuration=release") + vs.append("/p:platform=win32") + vs.append("/t:clean") + vs.append("/t:rebuild") + print(vs) + VSpth = make_local_path("..", "VSOutput.txt") + VSout = open(VSpth, 'a') + subprocess.call(vs, stdout=VSout) + VSout.close() + os.chdir(oldpath) + chk = os.path.join("..", "..", "..","sleuthkit", "win32", "Release", "libtsk_jni.dll") + try: + open(chk) + except IOError as e: + global errorem + global attachl + if(not tryredo): + errorem += "LIBTSK C++ failed to build.\n" + attachl.append(VSpth) + send_email(parsed, errorem, attachl, False) + tryredo = True + passed = False + redo = True + + + +#Builds Autopsy or the Datamodel +def antBuild(which, Build): + global redo + global passed + global tryredo + global parsed + directory = os.path.join("..", "..") + ant = [] + if which == "datamodel": + directory = os.path.join("..", "..", "..", "sleuthkit", "bindings", "java") + ant.append("ant") + ant.append("-f") + ant.append(directory) + ant.append("clean") + if(Build): + ant.append("build") + else: + ant.append("dist") + antpth = make_local_path("..", "ant" + which + "Output.txt") + antout = open(antpth, 'a') + succd = subprocess.call(ant, stdout=antout) + antout.close() + global errorem + global attachl + if which == "datamodel": + chk = os.path.join("..", "..", "..","sleuthkit", "bindings", "java", "dist", "TSK_DataModel.jar") + try: + open(chk) + except IOError as e: + if(not tryredo): + errorem += "DataModel Java build failed.\n" + attachl.append(antpth) + Emailer.send_email(parsed, errorem, attachl, False) + passed = False + tryredo = True + elif (succd != 0 and (not tryredo)): + errorem += "Autopsy build failed.\n" + attachl.append(antpth) + Emailer.send_email(parsed, errorem, attachl, False) + tryredo = True + elif (succd != 0): + passed = False + + +def main(): + errore = "" + attachli = [] + config_file = "" + arg = sys.argv.pop(0) + arg = sys.argv.pop(0) + config_file = arg + parsedin = parse(config_file) + compile(errore, attachli, parsedin) + +class OS: + LINUX, MAC, WIN, CYGWIN = range(4) +if __name__ == "__main__": + global SYS + if _platform == "linux" or _platform == "linux2": + SYS = OS.LINUX + elif _platform == "darwin": + SYS = OS.MAC + elif _platform == "win32": + SYS = OS.WIN + elif _platform == "cygwin": + SYS = OS.CYGWIN + + if SYS is OS.WIN or SYS is OS.CYGWIN: + main() + else: + print("We only support Windows and Cygwin at this time.") From 486307391a8eec5158e3d17d031d7f3430e32e42 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 19 Jul 2013 11:57:11 -0400 Subject: [PATCH 05/20] Removed ability to tag dot directories and added multi-selection tagging support to KeywaordSearchFilterNode class --- .../autopsy/datamodel/DirectoryNode.java | 18 ++++++++++++++++-- .../keywordsearch/KeywordSearchFilterNode.java | 1 - 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java index 6b3a474535..555aa49905 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DirectoryNode.java @@ -20,7 +20,9 @@ package org.sleuthkit.autopsy.datamodel; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; import javax.swing.Action; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.directorytree.ViewContextAction; @@ -67,6 +69,13 @@ public class DirectoryNode extends AbstractFsContentNode { @Override public Action[] getActions(boolean popup) { List actions = new ArrayList<>(); + + AbstractFile file = getLookup().lookup(AbstractFile.class); + if (file == null) { + Logger.getLogger(DirectoryNode.class.getName()).log(Level.SEVERE, "Node not associated with an AbstractFile object"); + return actions.toArray(new Action[0]); + } + if (!getDirectoryBrowseMode()) { actions.add(new ViewContextAction("View File in Directory", this)); actions.add(null); // creates a menu separator @@ -74,8 +83,13 @@ public class DirectoryNode extends AbstractFsContentNode { actions.add(new NewWindowViewAction("View in New Window", this)); actions.add(null); // creates a menu separator actions.add(new ExtractAction("Extract Directory", this)); - actions.add(null); // creates a menu separator - actions.add(getTagAbstractFileActionInstance()); + + String name = getDisplayName(); + if (!name.equals(DirectoryNode.DOTDIR) && !name.equals(DirectoryNode.DOTDOTDIR)) { + actions.add(null); // creates a menu separator + actions.add(getTagAbstractFileActionInstance()); + } + return actions.toArray(new Action[0]); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java index a1c6da0284..2afbd608f2 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java @@ -28,7 +28,6 @@ import org.openide.nodes.PropertySupport; import org.openide.nodes.Sheet; import org.openide.util.lookup.Lookups; import org.openide.util.lookup.ProxyLookup; -import org.sleuthkit.autopsy.directorytree.TagAction; import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; import org.sleuthkit.autopsy.directorytree.ExtractAction; import org.sleuthkit.autopsy.directorytree.HashSearchAction; From 1a9d55c25d91c1633bfc0676d89e29291614f9ce Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 19 Jul 2013 12:11:54 -0400 Subject: [PATCH 06/20] Modified ReportWizardAction to allow use of report wizard independent of coupling to toolbar --- .../autopsy/report/ReportWizardAction.java | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java index 34c58cf5cb..780e14351d 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java @@ -2,9 +2,9 @@ * * Autopsy Forensic Browser * - * Copyright 2012 Basis Technology Corp. + * Copyright 2013 Basis Technology Corp. * - * Copyright 2012 42six Solutions. + * Copyright 2013 42six Solutions. * Contact: aebadirad 42six com * Project Contact/Architect: carrier sleuthkit org * @@ -30,7 +30,6 @@ import java.beans.PropertyChangeListener; import java.io.File; import java.text.MessageFormat; import java.util.Map; -import java.util.Map.Entry; import java.util.logging.Level; import javax.swing.ImageIcon; import javax.swing.JButton; @@ -59,47 +58,12 @@ public final class ReportWizardAction extends CallableSystemAction implements P private JButton toolbarButton = new JButton(); private static final String ACTION_NAME = "Generate Report"; - public ReportWizardAction() { - setEnabled(false); - Case.addPropertyChangeListener(new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(Case.CASE_CURRENT_CASE)) { - Case newCase = (Case) evt.getNewValue(); - setEnabled(newCase != null); - - // Make the cases' Reoports folder, if it doesn't exist - if (newCase != null) { - boolean exists = (new File(newCase.getCaseDirectory() + File.separator + "Reports")).exists(); - if (!exists) { - boolean reportCreate = (new File(newCase.getCaseDirectory() + File.separator + "Reports")).mkdirs(); - if (!reportCreate) { - logger.log(Level.WARNING, "Could not create Reports directory for case. It does not exist."); - } - } - } - } - } - }); - - // Initialize the Generate Report button - toolbarButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ReportWizardAction.this.actionPerformed(e); - } - }); - - } - /** * When the Generate Report button or menu item is selected, open the reporting wizard. * When the wizard is finished, create a ReportGenerator with the wizard information, * and start all necessary reports. */ - @Override - @SuppressWarnings("unchecked") - public void actionPerformed(ActionEvent e) { + public static void doReportWizard() { // Create the wizard WizardDescriptor wiz = new WizardDescriptor(new ReportWizardIterator()); wiz.setTitleFormat(new MessageFormat("{0} {1}")); @@ -133,7 +97,45 @@ public final class ReportWizardAction extends CallableSystemAction implements P // Open the progress window for the user generator.displayProgressPanels(); - } + } + } + + public ReportWizardAction() { + setEnabled(false); + Case.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(Case.CASE_CURRENT_CASE)) { + Case newCase = (Case) evt.getNewValue(); + setEnabled(newCase != null); + + // Make the cases' Reoports folder, if it doesn't exist + if (newCase != null) { + boolean exists = (new File(newCase.getCaseDirectory() + File.separator + "Reports")).exists(); + if (!exists) { + boolean reportCreate = (new File(newCase.getCaseDirectory() + File.separator + "Reports")).mkdirs(); + if (!reportCreate) { + logger.log(Level.WARNING, "Could not create Reports directory for case. It does not exist."); + } + } + } + } + } + }); + + // Initialize the Generate Report button + toolbarButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ReportWizardAction.this.actionPerformed(e); + } + }); + } + + @Override + @SuppressWarnings("unchecked") + public void actionPerformed(ActionEvent e) { + doReportWizard(); } @Override From 0cf8ee8b5aa0e27b04ce2b3348b4602c34a994a4 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 19 Jul 2013 12:16:32 -0400 Subject: [PATCH 07/20] HTML reporting module no longer provides view file hyperlink for directories --- Core/src/org/sleuthkit/autopsy/report/ReportHTML.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java index 2332814cd7..99893fe7cf 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportHTML.java @@ -399,10 +399,10 @@ public class ReportHTML implements TableReportModule { try { AbstractFile file = Case.getCurrentCase().getSleuthkitCase().getAbstractFileById(sourceArtifact.getObjectID()); - // Don't make a local copy of the file if it is unallocated space or a virtual directory. - if (file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS || - file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS || - file.getType() == TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) { + // Don't make a local copy of the file if it is a directory or unallocated space. + if (file.isDir() || + file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS || + file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) { row.add(""); return; } From e440da75c683b2c5a48d152b3a94d443231323e0 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Tue, 23 Jul 2013 10:22:28 -0400 Subject: [PATCH 08/20] Moved TskDbDiff to a separate file. --- test/script/regression.py | 422 +++----------------------------- test/script/regression_utils.py | 1 + test/script/tskdbdiff.py | 255 +++++++++++++++++++ 3 files changed, 288 insertions(+), 390 deletions(-) create mode 100644 test/script/tskdbdiff.py diff --git a/test/script/regression.py b/test/script/regression.py index 6cf2172c4a..4ade7be66e 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -16,7 +16,7 @@ # 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. - +from tskdbdiff import TskDbDiff, TskDbDiffException import codecs import datetime import logging @@ -190,7 +190,8 @@ class TestRunner(object): try: # Dump the database before we diff or use it for rebuild - TskDbDiff.dump_output_db(test_data) + TskDbDiff.dump_output_db(test_data.get_db_path(DBType.OUTPUT), test_data.get_db_dump_path(DBType.OUTPUT), + test_data.get_sorted_data_path(DBType.OUTPUT)) except sqlite3.OperationalError as e: print("Ingest did not run properly.", "Make sure no other instances of Autopsy are open and try again.") @@ -221,8 +222,7 @@ class TestRunner(object): TestResultsDiffer.run_diff(test_data) test_data.overall_passed = (test_data.html_report_passed and - test_data.errors_diff_passed and test_data.sorted_data_passed and - test_data.db_dump_passed and test_data.db_diff_results.passed) + test_data.errors_diff_passed and test_data.db_diff_passed) Reports.generate_reports(test_data) if(not test_data.overall_passed): @@ -396,10 +396,9 @@ class TestData(object): gold_archive: a pathto_File, the gold standard archive logs_dir: a pathto_Dir, the location where autopsy logs are stored solr_index: a pathto_Dir, the locatino of the solr index - db_diff_results: a DiffResults, the results of the database comparison html_report_passed: a boolean, did the HTML report diff pass? errors_diff_passed: a boolean, did the error diff pass? - db_dump_passed: a boolean, did the db dump diff pass? + db_diff_passed: a boolean, did the db diff pass? overall_passed: a boolean, did the test pass? total_test_time: a String representation of the test duration start_date: a String representation of this TestData's start date @@ -448,11 +447,9 @@ class TestData(object): self.solr_index = make_path(self.output_path, AUTOPSY_TEST_CASE, "ModuleOutput", "KeywordSearch") # Results and Info - self.db_diff_results = None self.html_report_passed = False self.errors_diff_passed = False - self.sorted_data_passed = False - self.db_dump_passed = False + self.db_diff_passed = False self.overall_passed = False # Ingest info self.total_test_time = "" @@ -717,353 +714,6 @@ class TestConfiguration(object): self.email_enabled = True - -class TskDbDiff(object): - """Represents the differences between the gold and output databases. - - Contains methods to compare two databases. - - Attributes: - gold_artifacts: - autopsy_artifacts: - gold_attributes: - autopsy_attributes: - gold_objects: - autopsy_objects: - artifact_comparison: - attribute_comparision: - report_errors: a listof_listof_String, the error messages that will be - printed to screen in the run_diff method - passed: a boolean, did the diff pass? - autopsy_db_file: - gold_db_file: - """ - def __init__(self, output_db_path, gold_db_path): - """Constructor for TskDbDiff. - - Args: - output_db_path: a pathto_File, the output database - gold_db_path: a pathto_File, the gold database - """ - self.gold_artifacts = [] - self.autopsy_artifacts = [] - self.gold_attributes = 0 - self.autopsy_attributes = 0 - self.gold_objects = 0 - self.autopsy_objects = 0 - self.artifact_comparison = [] - self.attribute_comparison = [] - self.report_errors = [] - self.autopsy_db_file = output_db_path - self.gold_db_file = gold_db_path - - def _get_artifacts(self, cursor): - """Get a list of artifacts from the given SQLCursor. - - Args: - cursor: SQLCursor - the cursor to execute on - - Returns: - listof_Artifact - the artifacts found by the query - """ - cursor.execute("SELECT COUNT(*) FROM blackboard_artifact_types") - length = cursor.fetchone()[0] + 1 - artifacts = [] - for type_id in range(1, length): - cursor.execute("SELECT COUNT(*) FROM blackboard_artifacts WHERE artifact_type_id=%d" % type_id) - artifacts.append(cursor.fetchone()[0]) - return artifacts - - def _count_attributes(self, cursor): - """Count the attributes from the given SQLCursor. - - Args: - cursor: SQLCursor - the cursor to execute on - - Returns: - Nat - the number of attributes found by the query - """ - cursor.execute("SELECT COUNT(*) FROM blackboard_attributes") - return cursor.fetchone()[0] - - def _count_objects(self, cursor): - """Count the objects from the given SQLCursor. - - Args: - cursor: SQLCursor - the cursor to execute on - - Returns: - Nat - the number of objects found by the query - """ - cursor.execute("SELECT COUNT(*) FROM tsk_objects") - return cursor.fetchone()[0] - - def _compare_bb_artifacts(self): - """Compares the blackboard artifact counts of two databases. - - Returns: - True if the artifacts are the same, false otherwise. - """ - exceptions = [] - passed = True - if self.gold_artifacts != self.autopsy_artifacts: - msg = "There was a difference in the number of artifacts.\n" - exceptions.append(msg) - Errors.add_email_msg(msg) - passed = False - else: - rner = len(self.gold_artifacts) - for type_id in range(1, rner): - if self.gold_artifacts[type_id] != self.autopsy_artifacts[type_id]: - error = str("Artifact counts do not match for type id %d. " % type_id) - error += str("Gold: %d, Test: %d" % - (self.gold_artifacts[type_id], - self.autopsy_artifacts[type_id])) - exceptions.append(error) - passed = False - self.report_errors.append(exceptions) - return passed - - def _compare_bb_attributes(self): - """Compares the blackboard attribute counts of two databases. - - Updates this TskDbDiff's report_errors with the error messages from the - attribute diff - - Returns: - True is the attributes are the same, False otherwise. - """ - exceptions = [] - passed = True - if self.gold_attributes != self.autopsy_attributes: - error = "Attribute counts do not match. " - error += str("Gold: %d, Test: %d" % (self.gold_attributes, self.autopsy_attributes)) - exceptions.append(error) - msg = "There was a difference in the number of attributes.\n" - Errors.add_email_msg(msg) - passed = False - self.report_errors.append(exceptions) - return passed - - def _compare_tsk_objects(self): - """Compares the TSK object counts of two databases.""" - exceptions = [] - passed = True - if self.gold_objects != self.autopsy_objects: - error = "TSK Object counts do not match. " - error += str("Gold: %d, Test: %d" % (self.gold_objects, self.autopsy_objects)) - exceptions.append(error) - msg ="There was a difference between the tsk object counts.\n" - Errors.add_email_msg(msg) - passed = False - self.report_errors.append(exceptions) - return passed - - def _get_basic_counts(self, autopsy_cur, gold_cur): - """Count the items necessary to compare the databases. - - Gets the counts of objects, artifacts, and attributes in the Gold - and Ouput databases and updates this TskDbDiff's attributes - accordingly - - Args: - autopsy_cur: SQLCursor - the cursor for the output database - gold_cur: SQLCursor - the cursor for the gold database - """ - try: - # Objects - self.gold_objects = self._count_objects(gold_cur) - self.autopsy_objects = self._count_objects(autopsy_cur) - # Artifacts - self.gold_artifacts = self._get_artifacts(gold_cur) - self.autopsy_artifacts = self._get_artifacts(autopsy_cur) - # Attributes - self.gold_attributes = self._count_attributes(gold_cur) - self.autopsy_attributes = self._count_attributes(autopsy_cur) - except sqlite3.Error as e: - Errors.print_error("Error while querying the databases:" + str(e)) - - def run_diff(self): - """Basic test between output and gold databases. - - Compares only counts of objects and blackboard items. - Note: SQLITE needs unix style pathing - - Raises: - sqlite3.OperationalError, if either of the database files do not - exist - """ - # Get connections and cursors to output / gold databases - autopsy_con = sqlite3.connect(self.autopsy_db_file) - autopsy_cur = autopsy_con.cursor() - gold_con = sqlite3.connect(self.gold_db_file) - gold_cur = gold_con.cursor() - - # Get Counts of objects, artifacts, and attributes - self._get_basic_counts(autopsy_cur, gold_cur) - - # We're done with the databases, close up the connections - autopsy_con.close() - gold_con.close() - - # Compare counts - objects_passed = self._compare_tsk_objects() - artifacts_passed = self._compare_bb_artifacts() - attributes_passed = self._compare_bb_attributes() - - self.passed = objects_passed and artifacts_passed and attributes_passed - - self.artifact_comparison = self.report_errors[1] - self.attribute_comparison = self.report_errors[2] - - okay = "All counts match." - print_report(self.report_errors[0], "COMPARE TSK OBJECTS", okay) - print_report(self.report_errors[1], "COMPARE ARTIFACTS", okay) - print_report(self.report_errors[2], "COMPARE ATTRIBUTES", okay) - - return DiffResults(self) - - def _dump_output_db_bb(autopsy_con, db_file, data_file, sorted_data_file): - """Dumps sorted text results to the given output location. - - Smart method that deals with a blackboard comparison to avoid issues - with different IDs based on when artifacts were created. - - Args: - autopsy_con: a SQLConn to the autopsy database. - db_file: a pathto_File, the output database. - data_file: a pathto_File, the dump file to write to - sorted_data_file: a pathto_File, the sorted dump file to write to - """ - autopsy_cur2 = autopsy_con.cursor() - # Get the list of all artifacts - # @@@ Could add a SORT by parent_path in here since that is how we are going to later sort it. - autopsy_cur2.execute("SELECT tsk_files.parent_path, tsk_files.name, blackboard_artifact_types.display_name, blackboard_artifacts.artifact_id FROM blackboard_artifact_types INNER JOIN blackboard_artifacts ON blackboard_artifact_types.artifact_type_id = blackboard_artifacts.artifact_type_id INNER JOIN tsk_files ON tsk_files.obj_id = blackboard_artifacts.obj_id") - database_log = codecs.open(data_file, "wb", "utf_8") - rw = autopsy_cur2.fetchone() - appnd = False - counter = 0 - artifact_count = 0 - artifact_fail = 0 - # Cycle through artifacts - try: - while (rw != None): - # File Name and artifact type - if(rw[0] != None): - database_log.write(rw[0] + rw[1] + ' ') - else: - database_log.write(rw[1] + ' ') - - # Get attributes for this artifact - autopsy_cur1 = autopsy_con.cursor() - looptry = True - artifact_count += 1 - try: - key = "" - key = str(rw[3]) - key = key, - autopsy_cur1.execute("SELECT blackboard_attributes.source, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double FROM blackboard_attributes INNER JOIN blackboard_attribute_types ON blackboard_attributes.attribute_type_id = blackboard_attribute_types.attribute_type_id WHERE artifact_id =? ORDER BY blackboard_attributes.source, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double", key) - attributes = autopsy_cur1.fetchall() - except sqlite3.Error as e: - Errors.print_error(str(e)) - Errors.print_error(str(rw[3])) - msg ="Attributes in artifact id (in output DB)# " + str(rw[3]) + " encountered an error: " + str(e) +" .\n" - Errors.add_email_msg(msg) - looptry = False - print(artifact_fail) - artifact_fail += 1 - print(artifact_fail) - database_log.write('Error Extracting Attributes'); - - # Print attributes - if(looptry == True): - src = attributes[0][0] - for attr in attributes: - val = 3 + attr[2] - numvals = 0 - for x in range(3, 6): - if(attr[x] != None): - numvals += 1 - if(numvals > 1): - msg = "There were too many values for attribute type: " + attr[1] + " for artifact with id #" + str(rw[3]) + ".\n" - Errors.add_email_msg(msg) - Errors.print_error(msg) - if(not appnd): - Errors.add_email_attachment(db_file) - appnd = True - if(not attr[0] == src): - msg ="There were inconsistent sources for artifact with id #" + str(rw[3]) + ".\n" - Errors.add_email_msg(msg) - Errors.print_error(msg) - if(not appnd): - Errors.add_email_attachment(db_file) - appnd = True - try: - database_log.write('') - database_log.write(' \n') - rw = autopsy_cur2.fetchone() - - # Now sort the file - srtcmdlst = ["sort", data_file, "-o", sorted_data_file] - subprocess.call(srtcmdlst) - print(artifact_fail) - if(artifact_fail > 0): - msg ="There were " + str(artifact_count) + " artifacts and " + str(artifact_fail) + " threw an exception while loading.\n" - Errors.add_email_msg(msg) - except Exception as e: - Errors.print_error('outer exception: ' + str(e)) - - def _dump_output_db_nonbb(test_data): - """Dumps a database to a text file. - - Does not dump the artifact and attributes. - - Args: - test_data: the TestData that corresponds with this dump. - """ - # Make a copy of the DB - autopsy_db_file = test_data.get_db_path(DBType.OUTPUT) - backup_db_file = test_data.get_db_path(DBType.BACKUP) - shutil.copy(autopsy_db_file, backup_db_file) - autopsy_con = sqlite3.connect(backup_db_file) - - # Delete the blackboard tables - autopsy_con.execute("DROP TABLE blackboard_artifacts") - autopsy_con.execute("DROP TABLE blackboard_attributes") - - # Write to the database dump - with codecs.open(test_data.test_dbdump, "wb", "utf_8") as db_log: - for line in autopsy_con.iterdump(): - db_log.write('%s\n' %line) - - - def dump_output_db(test_data): - """Dumps the given database to text files for later comparison. - - Args: - test_data: the TestData that corresponds to this dump. - """ - autopsy_db_file = test_data.get_db_path(DBType.OUTPUT) - autopsy_con = sqlite3.connect(autopsy_db_file) - autopsy_cur = autopsy_con.cursor() - # Try to query the databases. Ignore any exceptions, the function will - # return an error later on if these do fail - TskDbDiff._dump_output_db_bb(autopsy_con, autopsy_db_file, - test_data.autopsy_data_file, - test_data.get_sorted_data_path(DBType.OUTPUT)) - TskDbDiff._dump_output_db_nonbb(test_data) - autopsy_con.close() - #-------------------------------------------------# # Functions relating to comparing outputs # #-------------------------------------------------# @@ -1078,11 +728,13 @@ class TestResultsDiffer(object): databaseDiff: TskDbDiff object created based off test_data """ try: - # Diff the gold and output databases - output_db_path = test_data.get_db_path(DBType.OUTPUT) - gold_db_path = test_data.get_db_path(DBType.GOLD) - db_diff = TskDbDiff(output_db_path, gold_db_path) - test_data.db_diff_results = db_diff.run_diff() + output_db = test_data.get_db_path(DBType.OUTPUT) + gold_db = test_data.get_db_path(DBType.GOLD) + output_dir = test_data.output_path + gold_bb_dump = test_data.get_sorted_data_path(DBType.GOLD) + gold_dump = test_data.get_db_dump_path(DBType.GOLD) + test_data.db_diff_pass = TskDbDiff(output_db, gold_db, output_dir=output_dir, gold_bb_dump=gold_bb_dump, + gold_dump=gold_dump).run_diff() # Compare Exceptions # replace is a fucntion that replaces strings of digits with 'd' @@ -1094,18 +746,6 @@ class TestResultsDiffer(object): replace) test_data.errors_diff_passed = passed - # Compare smart blackboard results - output_data = test_data.get_sorted_data_path(DBType.OUTPUT) - gold_data = test_data.get_sorted_data_path(DBType.GOLD) - passed = TestResultsDiffer._compare_text(output_data, gold_data) - test_data.sorted_data_passed = passed - - # Compare the rest of the database (non-BB) - output_dump = test_data.get_db_dump_path(DBType.OUTPUT) - gold_dump = test_data.get_db_dump_path(DBType.GOLD) - passed = TestResultsDiffer._compare_text(output_dump, gold_dump) - test_data.db_dump_passed = passed - # Compare html output gold_report_path = test_data.get_html_report_path(DBType.GOLD) output_report_path = test_data.get_html_report_path(DBType.OUTPUT) @@ -1119,6 +759,8 @@ class TestResultsDiffer(object): except sqlite3.OperationalError as e: Errors.print_error("Tests failed while running the diff:\n") Errors.print_error(str(e)) + except TskDbDiffException as e: + Errors.print_error(str(e)) except Exception as e: Errors.print_error("Tests failed due to an error, try rebuilding or creating gold standards.\n") Errors.print_error(str(e) + "\n") @@ -1339,14 +981,14 @@ class Reports(object): info += "Out Of Disk Space:\

(will skew other test results)

" info += "" + str(len(search_log_set("autopsy", "Stopping ingest due to low disk space on disk", test_data))) + "" - info += "TSK Objects Count:" - info += "" + str(test_data.db_diff_results.output_objs) + "" - info += "Artifacts Count:" - info += "" + str(test_data.db_diff_results.output_artifacts)+ "" - info += "Attributes Count:" - info += "" + str(test_data.db_diff_results.output_attrs) + "" +# info += "TSK Objects Count:" +# info += "" + str(test_data.db_diff_results.output_objs) + "" +# info += "Artifacts Count:" +# info += "" + str(test_data.db_diff_results.output_artifacts)+ "" +# info += "Attributes Count:" +# info += "" + str(test_data.db_diff_results.output_attrs) + "" info += "\ - " + " # For all the general print statements in the test_config output = "
\

General Output

\ @@ -1453,12 +1095,12 @@ class Reports(object): vars.append( str(test_data.indexed_files) ) vars.append( str(test_data.indexed_chunks) ) vars.append( str(len(search_log_set("autopsy", "Stopping ingest due to low disk space on disk", test_data))) ) - vars.append( str(test_data.db_diff_results.output_objs) ) - vars.append( str(test_data.db_diff_results.output_artifacts) ) - vars.append( str(test_data.db_diff_results.output_objs) ) +# vars.append( str(test_data.db_diff_results.output_objs) ) +# vars.append( str(test_data.db_diff_results.output_artifacts) ) +# vars.append( str(test_data.db_diff_results.output_objs) ) vars.append( make_local_path("gold", test_data.image_name, DB_FILENAME) ) - vars.append( test_data.db_diff_results.get_artifact_comparison() ) - vars.append( test_data.db_diff_results.get_attribute_comparison() ) +# vars.append( test_data.db_diff_results.get_artifact_comparison() ) +# vars.append( test_data.db_diff_results.get_attribute_comparison() ) vars.append( make_local_path("gold", test_data.image_name, "standard.html") ) vars.append( str(test_data.html_report_passed) ) vars.append( test_data.ant_to_string() ) @@ -1493,12 +1135,12 @@ class Reports(object): titles.append("Indexed Files Count") titles.append("Indexed File Chunks Count") titles.append("Out Of Disk Space") - titles.append("Tsk Objects Count") - titles.append("Artifacts Count") - titles.append("Attributes Count") +# titles.append("Tsk Objects Count") +# titles.append("Artifacts Count") +# titles.append("Attributes Count") titles.append("Gold Database Name") - titles.append("Artifacts Comparison") - titles.append("Attributes Comparison") +# titles.append("Artifacts Comparison") +# titles.append("Attributes Comparison") titles.append("Gold Report Name") titles.append("Report Comparison") titles.append("Ant Command Line") diff --git a/test/script/regression_utils.py b/test/script/regression_utils.py index cf3c117df4..025086f1ae 100644 --- a/test/script/regression_utils.py +++ b/test/script/regression_utils.py @@ -152,3 +152,4 @@ def get_files_by_ext(dir_path, ext): """ return [ os.path.join(dir_path, file) for file in os.listdir(dir_path) if file.endswith(ext) ] + diff --git a/test/script/tskdbdiff.py b/test/script/tskdbdiff.py new file mode 100644 index 0000000000..1e71d9e834 --- /dev/null +++ b/test/script/tskdbdiff.py @@ -0,0 +1,255 @@ +import re +import sqlite3 +import subprocess +import shutil +import os +import codecs +import datetime + +class TskDbDiff(object): + """Represents the differences between the gold and output databases. + + Contains methods to compare two databases. + + Attributes: + gold_artifacts: + autopsy_artifacts: + gold_attributes: + autopsy_attributes: + gold_objects: + autopsy_objects: + artifact_comparison: + attribute_comparision: + report_errors: a listof_listof_String, the error messages that will be + printed to screen in the run_diff method + passed: a boolean, did the diff pass? + autopsy_db_file: + gold_db_file: + """ + def __init__(self, output_db, gold_db, output_dir=None, gold_bb_dump=None, gold_dump=None, verbose=False): + """Constructor for TskDbDiff. + + Args: + output_db_path: a pathto_File, the output database + gold_db_path: a pathto_File, the gold database + output_dir: (optional) a pathto_Dir, the location where the generated files will be put. + gold_bb_dump: (optional) a pathto_File, the location where the gold blackboard dump is located + gold_dump: (optional) a pathto_File, the location where the gold non-blackboard dump is located + verbose: (optional) a boolean, should the diff results be printed to stdout? + """ + self.output_db_file = output_db + self.gold_db_file = gold_db + self.output_dir = output_dir + self.gold_bb_dump = gold_bb_dump + self.gold_dump = gold_dump + self._generate_gold_dump = gold_dump is None + self._generate_gold_bb_dump = gold_bb_dump is None + self._bb_dump_diff = "" + self._dump_diff = "" + self._bb_dump = "" + self._dump = "" + self.verbose = verbose + + def run_diff(self): + """Compare the databases. + + Raises: + TskDbDiffException: if an error occurs while diffing or dumping the database + """ + self._init_diff() + # generate the gold database dumps if necessary + if self._generate_gold_dump: + TskDbDiff._dump_output_db_nonbb(self.gold_db, self.gold_dump) + if self._generate_gold_bb_dump: + TskDbDiff._dump_ouput_db_bb(self.gold_db, self.gold_bb_dump) + + # generate the output database dumps + TskDbDiff.dump_output_db(self.output_db_file, self._dump, self._bb_dump) + + dump_diff_pass = self._diff(self._dump, self.gold_dump, self._dump_diff) + bb_dump_diff_pass = self._diff(self._bb_dump, self.gold_bb_dump, self._bb_dump_diff) + + self._cleanup_diff() + return dump_diff_pass and bb_dump_diff_pass + + def _init_diff(self): + """Set up the necessary files based on the arguments given at construction""" + if self.output_dir is None: + # No stored files + self._bb_dump = TskDbDiff._get_tmp_file("SortedData", ".txt") + self._bb_dump_diff = TskDbDiff._get_tmp_file("SortedData-Diff", ".txt") + self._dump = TskDbDiff._get_tmp_file("DBDump", ".txt") + self._dump_diff = TskDbDiff._get_tmp_file("DBDump-Diff", ".txt") + else: + self._bb_dump = os.path.join(self.output_dir, "SortedData.txt") + self._bb_dump_diff = os.path.join(self.output_dir, "SortedData-Diff.txt") + self._dump = os.path.join(self.output_dir, "DBDump.txt") + self._dump_diff = os.path.join(self.output_dir, "DBDump-Diff.txt") + + if self.gold_bb_dump is None: + self.gold_bb_dump = TskDbDiff._get_tmp_file("GoldSortedData", ".txt") + self.gold_dump = TskDbDiff._get_tmp_file("GoldDBDump", ".txt") + + def _cleanup_diff(self): + if self.output_dir is None: + #cleanup temp files + os.remove(self._dump) + os.remove(self._dump_diff) + os.remove(self._bb_dump) + os.remove(self._bb_dump_diff) + if self.gold_bb_dump is None: + os.remove(self.gold_bb_dump) + os.remove(self.gold_dump) + + def _diff(self, output_file, gold_file, diff_path): + """Compare two text files. + + Args: + output_file: a pathto_File, the output text file + gold_file: a pathto_File, the input text file + """ + if(not os.path.isfile(output_file)): + return False + output_data = codecs.open(output_file, "r", "utf_8").read() + gold_data = codecs.open(gold_file, "r", "utf_8").read() + + if (not(gold_data == output_data)): + diff_file = codecs.open(diff_path, "wb", "utf_8") + dffcmdlst = ["diff", output_file, gold_file] + subprocess.call(dffcmdlst, stdout = diff_file) + return False + else: + return True + + def _dump_output_db_bb(db_file, bb_dump_file): + """Dumps sorted text results to the given output location. + + Smart method that deals with a blackboard comparison to avoid issues + with different IDs based on when artifacts were created. + + Args: + db_file: a pathto_File, the output database. + bb_dump_file: a pathto_File, the sorted dump file to write to + """ + unsorted_dump = TskDbDiff._get_tmp_file("dump_data", ".txt") + conn = sqlite3.connect(db_file) + autopsy_cur2 = conn.cursor() + # Get the list of all artifacts + # @@@ Could add a SORT by parent_path in here since that is how we are going to later sort it. + autopsy_cur2.execute("SELECT tsk_files.parent_path, tsk_files.name, blackboard_artifact_types.display_name, blackboard_artifacts.artifact_id FROM blackboard_artifact_types INNER JOIN blackboard_artifacts ON blackboard_artifact_types.artifact_type_id = blackboard_artifacts.artifact_type_id INNER JOIN tsk_files ON tsk_files.obj_id = blackboard_artifacts.obj_id") + database_log = codecs.open(unsorted_dump, "wb", "utf_8") + rw = autopsy_cur2.fetchone() + appnd = False + counter = 0 + artifact_count = 0 + artifact_fail = 0 + # Cycle through artifacts + try: + while (rw != None): + # File Name and artifact type + if(rw[0] != None): + database_log.write(rw[0] + rw[1] + ' ') + else: + database_log.write(rw[1] + ' ') + + # Get attributes for this artifact + autopsy_cur1 = conn.cursor() + looptry = True + artifact_count += 1 + try: + key = "" + key = str(rw[3]) + key = key, + autopsy_cur1.execute("SELECT blackboard_attributes.source, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double FROM blackboard_attributes INNER JOIN blackboard_attribute_types ON blackboard_attributes.attribute_type_id = blackboard_attribute_types.attribute_type_id WHERE artifact_id =? ORDER BY blackboard_attributes.source, blackboard_attribute_types.display_name, blackboard_attributes.value_type, blackboard_attributes.value_text, blackboard_attributes.value_int32, blackboard_attributes.value_int64, blackboard_attributes.value_double", key) + attributes = autopsy_cur1.fetchall() + except sqlite3.Error as e: + msg ="Attributes in artifact id (in output DB)# " + str(rw[3]) + " encountered an error: " + str(e) +" .\n" + looptry = False + artifact_fail += 1 + database_log.write('Error Extracting Attributes') + database_log.close() + raise TskDbDiffException(msg) + + # Print attributes + if(looptry == True): + src = attributes[0][0] + for attr in attributes: + val = 3 + attr[2] + numvals = 0 + for x in range(3, 6): + if(attr[x] != None): + numvals += 1 + if(numvals > 1): + msg = "There were too many values for attribute type: " + attr[1] + " for artifact with id #" + str(rw[3]) + ".\n" + if(not attr[0] == src): + msg ="There were inconsistent sources for artifact with id #" + str(rw[3]) + ".\n" + try: + database_log.write('') + database_log.write(' \n') + rw = autopsy_cur2.fetchone() + + # Now sort the file + srtcmdlst = ["sort", unsorted_dump, "-o", bb_dump_file] + subprocess.call(srtcmdlst) + print(artifact_fail) + if(artifact_fail > 0): + msg ="There were " + str(artifact_count) + " artifacts and " + str(artifact_fail) + " threw an exception while loading.\n" + except Exception as e: + raise TskDbDiffException("Unexpected error while dumping blackboard database: " + str(e)) + finally: + database_log.close() + + def _dump_output_db_nonbb(db_file, dump_file): + """Dumps a database to a text file. + + Does not dump the artifact and attributes. + + Args: + db_file: a pathto_File, the database file to dump + dump_file: a pathto_File, the location to dump the non-blackboard database items + """ + backup_db_file = TskDbDiff._get_tmp_file("tsk_backup_db", ".db") + shutil.copy(db_file, backup_db_file) + conn = sqlite3.connect(backup_db_file) + + # Delete the blackboard tables + conn.execute("DROP TABLE blackboard_artifacts") + conn.execute("DROP TABLE blackboard_attributes") + + # Write to the database dump + with codecs.open(dump_file, "wb", "utf_8") as db_log: + for line in conn.iterdump(): + db_log.write('%s\n' % line) + + # cleanup the backup + os.remove(backup_db_file) + + def dump_output_db(db_file, dump_file, bb_dump_file): + """Dumps the given database to text files for later comparison. + + Args: + db_file: a pathto_File, the database file to dump + dump_file: a pathto_File, the location to dump the non-blackboard database items + bb_dump_file: a pathto_File, the location to dump the blackboard database items + """ + TskDbDiff._dump_output_db_nonbb(db_file, dump_file) + TskDbDiff._dump_output_db_bb(db_file, bb_dump_file) + + def _get_tmp_file(base, ext): + time = datetime.datetime.now().time().strftime("%H%M%f") + return os.path.join(os.environ['TMP'], base + time + ext) + + +class TskDbDiffException(Exception): + pass + From 08bb9f8bc81b9c82796f4c4ee79fa842b6c98801 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 23 Jul 2013 13:28:58 -0400 Subject: [PATCH 09/20] Added temporary special handling of image read errors preliminary to API change to allow such errors to be distinguished --- Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 7d39d8f05a..5dee9feb54 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -56,7 +56,7 @@ import org.sleuthkit.datamodel.SleuthkitJNI.CaseDbHandle.AddImageProcess; * open at a time. Use getCurrentCase() to retrieve the object for the current * case. */ -public class Case { +public class Case implements SleuthkitCase.ErrorObserver { private static final String autopsyVer = Version.getVersion(); // current version of autopsy. Change it when the version is changed private static final String appName = Version.getName() + " " + autopsyVer; @@ -130,6 +130,7 @@ public class Case { this.xmlcm = xmlcm; this.db = db; this.services = new Services(db); + db.addErrorObserver(this); } /** @@ -983,4 +984,9 @@ public class Case { CoreComponentControl.closeCoreWindows(); } } + + @Override + public void receiveError(String context, String errorMessage) { + MessageNotifyUtil.Notify.error(context, errorMessage); + } } From 31d93f0d114639425d32ab48d93be35959896fa4 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Tue, 23 Jul 2013 15:21:10 -0400 Subject: [PATCH 10/20] Added main function to tskdbdiff so it can be called directly. --- test/script/tskdbdiff.py | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/test/script/tskdbdiff.py b/test/script/tskdbdiff.py index 1e71d9e834..985cd2ba17 100644 --- a/test/script/tskdbdiff.py +++ b/test/script/tskdbdiff.py @@ -5,6 +5,7 @@ import shutil import os import codecs import datetime +import sys class TskDbDiff(object): """Represents the differences between the gold and output databases. @@ -59,9 +60,9 @@ class TskDbDiff(object): self._init_diff() # generate the gold database dumps if necessary if self._generate_gold_dump: - TskDbDiff._dump_output_db_nonbb(self.gold_db, self.gold_dump) + TskDbDiff._dump_output_db_nonbb(self.gold_db_file, self.gold_dump) if self._generate_gold_bb_dump: - TskDbDiff._dump_ouput_db_bb(self.gold_db, self.gold_bb_dump) + TskDbDiff._dump_output_db_bb(self.gold_db_file, self.gold_bb_dump) # generate the output database dumps TskDbDiff.dump_output_db(self.output_db_file, self._dump, self._bb_dump) @@ -94,9 +95,11 @@ class TskDbDiff(object): if self.output_dir is None: #cleanup temp files os.remove(self._dump) - os.remove(self._dump_diff) os.remove(self._bb_dump) - os.remove(self._bb_dump_diff) + if os.path.isfile(self._dump_diff): + os.remove(self._dump_diff) + if os.path.isfile(self._bb_dump_diff): + os.remove(self._bb_dump_diff) if self.gold_bb_dump is None: os.remove(self.gold_bb_dump) os.remove(self.gold_dump) @@ -251,5 +254,29 @@ class TskDbDiff(object): class TskDbDiffException(Exception): - pass + pass + + +def main(): + try: + sys.argv.pop(0) + output_db = sys.argv.pop(0) + gold_db = sys.argv.pop(0) + except: + print("usage: tskdbdiff [OUPUT DB PATH] [GOLD DB PATH]") + sys.exit() + + db_diff = TskDbDiff(output_db, gold_db) + passed = db_diff.run_diff() + + if passed: + print("Database comparison passed.") + else: + print("Database comparison failed.") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From c55c5c68cc2dc038535d0e812e52a469dedf5e80 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Wed, 24 Jul 2013 16:02:18 -0400 Subject: [PATCH 11/20] Fixed syntax errors and other small changes. --- update_versions.py | 1815 ++++++++++++++++++++++---------------------- 1 file changed, 920 insertions(+), 895 deletions(-) diff --git a/update_versions.py b/update_versions.py index 77d8b42ac5..593c1cdb0d 100644 --- a/update_versions.py +++ b/update_versions.py @@ -1,895 +1,920 @@ -# ============================================================ -# update_versions.py -# ============================================================ -# -# When run from the Autopsy build script, this script will: -# - Clone Autopsy and checkout to the previous release tag -# as found in the NEWS.txt file -# - Auto-discover all modules and packages -# - Run jdiff, comparing the current and previous modules -# - Use jdiff's output to determine if each module -# a) has no changes -# b) has backwards compatible changes -# c) has backwards incompatible changes -# - Based off it's compatibility, updates each module's -# a) Major version -# b) Specification version -# c) Implementation version -# - Updates the dependencies on each module depending on the -# updated version numbers -# -# Optionally, when run from the command line, one can provide the -# desired tag to compare the current version to, the directory for -# the current version of Autopsy, and whether to automatically -# update the version numbers and dependencies. -# ------------------------------------------------------------ - -import errno -import os -import shutil -import stat -import subprocess -import sys -import traceback -from os import remove, close -from shutil import move -from tempfile import mkstemp -from xml.dom.minidom import parse, parseString - -# An Autopsy module object -class Module: - # Initialize it with a name, return code, and version numbers - def __init__(self, name=None, ret=None, versions=None): - self.name = name - self.ret = ret - self.versions = versions - # As a string, the module should be it's name - def __str__(self): - return self.name - def __repr__(self): - return self.name - # When compared to another module, the two are equal if the names are the same - def __cmp__(self, other): - if isinstance(other, Module): - if self.name == other.name: - return 0 - elif self.name < other.name: - return -1 - else: - return 1 - return 1 - def __eq__(self, other): - if isinstance(other, Module): - if self.name == other.name: - return True - return False - def set_name(self, name): - self.name = name - def set_ret(self, ret): - self.ret = ret - def set_versions(self, versions): - self.versions = versions - def spec(self): - return self.versions[0] - def impl(self): - return self.versions[1] - def release(self): - return self.versions[2] - -# Representation of the Specification version number -class Spec: - # Initialize specification number, where num is a string like x.y - def __init__(self, num): - l, r = num.split(".") - self.left = int(l) - self.right = int(r) - def __str__(self): - return self.get() - def __cmp__(self, other): - if isinstance(other, Spec): - if self.left == other.left: - if self.right == other.right: - return 0 - if self.right < other.right: - return -1 - return 1 - if self.left < other.left: - return -1 - return 1 - elif isinstance(other, str): - l, r = other.split(".") - if self.left == int(l): - if self.right == int(r): - return 0 - if self.right < int(r): - return -1 - return 1 - if self.left < int(l): - return -1 - return 1 - return -1 - - def overflow(self): - return str(self.left + 1) + ".0" - def increment(self): - return str(self.left) + "." + str(self.right + 1) - def get(self): - return str(self.left) + "." + str(self.right) - def set(self, num): - if isinstance(num, str): - l, r = num.split(".") - self.left = int(l) - self.right = int(r) - elif isinstance(num, Spec): - self.left = num.left - self.right = num.right - return self - -# ================================ # -# Core Functions # -# ================================ # - -# Given a list of modules and the names for each version, compare -# the generated jdiff XML for each module and output the jdiff -# JavaDocs. -# -# modules: the list of all modules both versions have in common -# apiname_tag: the api name of the previous version, most likely the tag -# apiname_cur: the api name of the current version, most likely "Current" -# -# returns the exit code from the modified jdiff.jar -# return code 1 = error in jdiff -# return code 100 = no changes -# return code 101 = compatible changes -# return code 102 = incompatible changes -def compare_xml(module, apiname_tag, apiname_cur): - global docdir - make_dir(docdir) - null_file = fix_path(os.path.abspath("./thirdparty/jdiff/v-custom/lib/Null.java")) - jdiff = fix_path(os.path.abspath("./thirdparty/jdiff/v-custom/jdiff.jar")) - oldapi = fix_path("build/jdiff-xml/" + apiname_tag + "-" + module.name) - newapi = fix_path("build/jdiff-xml/" + apiname_cur + "-" + module.name) - docs = fix_path(docdir + "/" + module.name) - # Comments are strange. They look for a file with additional user comments in a - # directory like docs/user_comments_for_xyz. The problem being that xyz is the - # path to the new/old api. So xyz turns into multiple directories for us. - # i.e. user_comments_for_build/jdiff-xml/[tag name]-[module name]_to_build/jdiff-xml - comments = fix_path(docs + "/user_comments_for_build") - jdiff_com = fix_path(comments + "/jdiff-xml") - tag_comments = fix_path(jdiff_com + "/" + apiname_tag + "-" + module.name + "_to_build") - jdiff_tag_com = fix_path(tag_comments + "/jdiff-xml") - make_dir(docs) - make_dir(comments) - make_dir(jdiff_com) - make_dir(tag_comments) - make_dir(jdiff_tag_com) - make_dir("jdiff-logs") - log = open("jdiff-logs/COMPARE-" + module.name + ".log", "w") - cmd = ["javadoc", - "-doclet", "jdiff.JDiff", - "-docletpath", jdiff, - "-d", docs, - "-oldapi", oldapi, - "-newapi", newapi, - "-script", - null_file] - jdiff = subprocess.Popen(cmd, stdout=log, stderr=log) - jdiff.wait() - log.close() - code = jdiff.returncode - print("Compared XML for " + module.name) - if code == 100: - print(" No API changes") - elif code == 101: - print(" API Changes are backwards compatible") - elif code == 102: - print(" API Changes are not backwards compatible") - else: - print(" *Error in XML, most likely an empty module") - sys.stdout.flush() - return code - -# Generate the jdiff xml for the given module -# path: path to the autopsy source -# module: Module object -# name: api name for jdiff -def gen_xml(path, modules, name): - for module in modules: - # If its the regression test, the source is in the "test" dir - if module.name == "Testing": - src = os.path.join(path, module.name, "test", "qa-functional", "src") - else: - src = os.path.join(path, module.name, "src") - # xerces = os.path.abspath("./lib/xerces.jar") - xml_out = fix_path(os.path.abspath("./build/jdiff-xml/" + name + "-" + module.name)) - jdiff = fix_path(os.path.abspath("./thirdparty/jdiff/v-custom/jdiff.jar")) - make_dir("build/jdiff-xml") - make_dir("jdiff-logs") - log = open("jdiff-logs/GEN_XML-" + name + "-" + module.name + ".log", "w") - cmd = ["javadoc", - "-doclet", "jdiff.JDiff", - "-docletpath", jdiff, # ;" + xerces, <-- previous problems required this - "-apiname", xml_out, # leaving it in just in case it's needed once again - "-sourcepath", fix_path(src)] - cmd = cmd + get_packages(src) - jdiff = subprocess.Popen(cmd, stdout=log, stderr=log) - jdiff.wait() - log.close() - print("Generated XML for " + name + " " + module.name) - sys.stdout.flush() - -# Find all the modules in the given path -def find_modules(path): - modules = [] - # Step into each folder in the given path and - # see if it has manifest.mf - if so, it's a module - for dir in os.listdir(path): - directory = os.path.join(path, dir) - if os.path.isdir(directory): - for file in os.listdir(directory): - if file == "manifest.mf": - modules.append(Module(dir, None, None)) - return modules - -# Detects the differences between the source and tag modules -def module_diff(source_modules, tag_modules): - added_modules = [x for x in source_modules if x not in tag_modules] - removed_modules = [x for x in tag_modules if x not in source_modules] - similar_modules = [x for x in source_modules if x in tag_modules] - - added_modules = (added_modules if added_modules else []) - removed_modules = (removed_modules if removed_modules else []) - similar_modules = (similar_modules if similar_modules else []) - return similar_modules, added_modules, removed_modules - -# Reads the previous tag from NEWS.txt -def get_tag(sourcepath): - news = open(sourcepath + "/NEWS.txt", "r") - second_instance = False - for line in news: - if "----------------" in line: - if second_instance: - ver = line.split("VERSION ")[1] - ver = ver.split(" -")[0] - return "autopsy-" + ver - else: - second_instance = True - continue - news.close() - - -# ========================================== # -# Dependency Functions # -# ========================================== # - -# Write a new XML file, copying all the lines from projectxml -# and replacing the specification version for the code-name-base base -# with the supplied specification version spec -def set_dep_spec(projectxml, base, spec): - print(" Updating Specification version..") - orig = open(projectxml, "r") - f, abs_path = mkstemp() - new_file = open(abs_path, "w") - found_base = False - spacing = " " - sopen = "" - sclose = "\n" - for line in orig: - if base in line: - found_base = True - if found_base and sopen in line: - update = spacing + sopen + str(spec) + sclose - new_file.write(update) - else: - new_file.write(line) - new_file.close() - close(f) - orig.close() - remove(projectxml) - move(abs_path, projectxml) - -# Write a new XML file, copying all the lines from projectxml -# and replacing the release version for the code-name-base base -# with the supplied release version -def set_dep_release(projectxml, base, release): - print(" Updating Release version..") - orig = open(projectxml, "r") - f, abs_path = mkstemp() - new_file = open(abs_path, "w") - found_base = False - spacing = " " - ropen = "" - rclose = "\n" - for line in orig: - if base in line: - found_base = True - if found_base and ropen in line: - update = spacing + ropen + str(release) + rclose - new_file.write(update) - else: - new_file.write(line) - new_file.close() - close(f) - orig.close() - remove(projectxml) - move(abs_path, projectxml) - -# Return the dependency versions in the XML dependency node -def get_dep_versions(dep): - run_dependency = dep.getElementsByTagName("run-dependency")[0] - release_version = run_dependency.getElementsByTagName("release-version") - if release_version: - release_version = getTagText(release_version[0].childNodes) - specification_version = run_dependency.getElementsByTagName("specification-version") - if specification_version: - specification_version = getTagText(specification_version[0].childNodes) - return int(release_version), Spec(specification_version) - -# Given a code-name-base, see if it corresponds with any of our modules -def get_module_from_base(modules, code_name_base): - for module in modules: - if "org.sleuthkit.autopsy." + module.name.lower() == code_name_base: - return module - return None # If it didn't match one of our modules - -# Check the text between two XML tags -def getTagText(nodelist): - for node in nodelist: - if node.nodeType == node.TEXT_NODE: - return node.data - -# Check the projectxml for a dependency on any module in modules -def check_for_dependencies(projectxml, modules): - dom = parse(projectxml) - dep_list = dom.getElementsByTagName("dependency") - for dep in dep_list: - code_name_base = dep.getElementsByTagName("code-name-base")[0] - code_name_base = getTagText(code_name_base.childNodes) - module = get_module_from_base(modules, code_name_base) - if module: - print(" Found dependency on " + module.name) - release, spec = get_dep_versions(dep) - if release != module.release() and module.release() is not None: - set_dep_release(projectxml, code_name_base, module.release()) - else: print(" Release version is correct") - if spec != module.spec() and module.spec() is not None: - set_dep_spec(projectxml, code_name_base, module.spec()) - else: print(" Specification version is correct") - -# Given the module and the source directory, return -# the paths to the manifest and project properties files -def get_dependency_file(module, source): - projectxml = os.path.join(source, module.name, "nbproject", "project.xml") - if os.path.isfile(projectxml): - return projectxml - -# Verify/Update the dependencies for each module, basing the dependency -# version number off the versions in each module -def update_dependencies(modules, source): - for module in modules: - print("Checking the dependencies for " + module.name + "...") - projectxml = get_dependency_file(module, source) - if projectxml == None: - print(" Error finding project xml file") - else: - other = [x for x in modules] - check_for_dependencies(projectxml, other) - sys.stdout.flush() - -# ======================================== # -# Versioning Functions # -# ======================================== # - -# Return the specification version in the given project.properties/manifest.mf file -def get_specification(project, manifest): - try: - # Try to find it in the project file - # it will be there if impl version is set to append automatically - f = open(project, 'r') - for line in f: - if "spec.version.base" in line: - return Spec(line.split("=")[1].strip()) - f.close() - # If not found there, try the manifest file - f = open(manifest, 'r') - for line in f: - if "OpenIDE-Module-Specification-Version:" in line: - return Spec(line.split(": ")[1].strip()) - except: - print("Error parsing Specification version for") - print(project) - -# Set the specification version in the given project properties file -# but if it can't be found there, set it in the manifest file -def set_specification(project, manifest, num): - try: - # First try the project file - f = open(project, 'r') - for line in f: - if "spec.version.base" in line: - f.close() - replace(project, line, "spec.version.base=" + str(num) + "\n") - return - f.close() - # If it's not there, try the manifest file - f = open(manifest, 'r') - for line in f: - if "OpenIDE-Module-Specification-Version:" in line: - f.close() - replace(manifest, line, "OpenIDE-Module-Specification-Version: " + str(num) + "\n") - return - # Otherwise we're out of luck - print(" Error finding the Specification version to update") - print(" " + manifest) - f.close() - except: - print(" Error incrementing Specification version for") - print(" " + project) - -# Return the implementation version in the given manifest.mf file -def get_implementation(manifest): - try: - f = open(manifest, 'r') - for line in f: - if "OpenIDE-Module-Implementation-Version" in line: - return int(line.split(": ")[1].strip()) - f.close() - except: - print("Error parsing Implementation version for") - print(manifest) - -# Set the implementation version in the given manifest file -def set_implementation(manifest, num): - try: - f = open(manifest, 'r') - for line in f: - if "OpenIDE-Module-Implementation-Version" in line: - f.close() - replace(manifest, line, "OpenIDE-Module-Implementation-Version: " + str(num) + "\n") - return - # If it isn't there, add it - f.close() - write_implementation(manifest, num) - except: - print(" Error incrementing Implementation version for") - print(" " + manifest) - -# Rewrite the manifest file to include the implementation version -def write_implementation(manifest, num): - f = open(manifest, "r") - contents = f.read() - contents = contents[:-2] + "OpenIDE-Module-Implementation-Version: " + str(num) + "\n\n" - f.close() - f = open(manifest, "w") - f.write(contents) - f.close() - -# Return the release version in the given manifest.mf file -def get_release(manifest): - try: - f = open(manifest, 'r') - for line in f: - if "OpenIDE-Module:" in line: - return int(line.split("/")[1].strip()) - f.close() - except: - print("Error parsing Release version for") - print(manifest) - -# Set the release version in the given manifest file -def set_release(manifest, num): - try: - f = open(manifest, 'r') - for line in f: - if "OpenIDE-Module:" in line: - f.close() - index = line.index('/') - len(line) + 1 - newline = line[:index] + str(num) - replace(manifest, line, newline + "\n") - return - print(" Error finding the release version to update") - print(" " + manifest) - f.close() - except: - print(" Error incrementing release version for") - print(" " + manifest) - -# Given the module and the source directory, return -# the paths to the manifest and project properties files -def get_version_files(module, source): - manifest = os.path.join(source, module.name, "manifest.mf") - project = os.path.join(source, module.name, "nbproject", "project.properties") - if os.path.isfile(manifest) and os.path.isfile(project): - return manifest, project - -# Returns a the current version numbers for the module in source -def get_versions(module, source): - manifest, project = get_version_files(module, source) - if manifest == None or project == None: - print(" Error finding manifeset and project properties files") - return - spec = get_specification(project, manifest) - impl = get_implementation(manifest) - release = get_release(manifest) - return [spec, impl, release] - -# Update the version numbers for every module in modules -def update_versions(modules, source): - for module in modules: - versions = module.versions - manifest, project = get_version_files(module, source) - print("Updating " + module.name + "...") - if manifest == None or project == None: - print(" Error finding manifeset and project properties files") - return - if module.ret == 101: - versions = [versions[0].set(versions[0].increment()), versions[1] + 1, versions[2]] - set_specification(project, manifest, versions[0]) - set_implementation(manifest, versions[1]) - module.set_versions(versions) - elif module.ret == 102: - versions = [versions[0].set(versions[0].overflow()), versions[1] + 1, versions[2] + 1] - set_specification(project, manifest, versions[0]) - set_implementation(manifest, versions[1]) - set_release(manifest, versions[2]) - module.set_versions(versions) - elif module.ret == 100: - versions = [versions[0], versions[1] + 1, versions[2]] - set_implementation(manifest, versions[1]) - module.set_versions(versions) - elif module.ret == None: - versions = [Spec("1.0"), 1, 1] - set_specification(project, manifest, versions[0]) - set_implementation(manifest, versions[1]) - set_release(manifest, versions[2]) - module.set_versions(versions) - sys.stdout.flush() - -# Given a list of the added modules, remove the modules -# which have the correct 'new module default' version number -def remove_correct_added(modules): - correct = [x for x in modules] - for module in modules: - if module.spec() == "1.0" or module.spec() == "0.0": - if module.impl() == 1: - if module.release() == 1 or module.release() == 0: - correct.remove(module) - return correct - -# ==================================== # -# Helper Functions # -# ==================================== # - -# Replace pattern with subst in given file -def replace(file, pattern, subst): - #Create temp file - fh, abs_path = mkstemp() - new_file = open(abs_path,'w') - old_file = open(file) - for line in old_file: - new_file.write(line.replace(pattern, subst)) - #close temp file - new_file.close() - close(fh) - old_file.close() - #Remove original file - remove(file) - #Move new file - move(abs_path, file) - -# Given a list of modules print the version numbers that need changing -def print_version_updates(modules): - f = open("gen_version.txt", "a") - for module in modules: - versions = module.versions - if module.ret == 101: - output = (module.name + ":\n") - output += (" Current Specification version:\t" + str(versions[0]) + "\n") - output += (" Updated Specification version:\t" + str(versions[0].increment()) + "\n") - output += ("\n") - output += (" Current Implementation version:\t" + str(versions[1]) + "\n") - output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n") - output += ("\n") - print(output) - f.write(output) - elif module.ret == 102: - output = (module.name + ":\n") - output += (" Current Specification version:\t" + str(versions[0]) + "\n") - output += (" Updated Specification version:\t" + str(versions[0].overflow()) + "\n") - output += ("\n") - output += (" Current Implementation version:\t" + str(versions[1]) + "\n") - output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n") - output += ("\n") - output += (" Current Release version:\t\t" + str(versions[2]) + "\n") - output += (" Updated Release version:\t\t" + str(versions[2] + 1) + "\n") - output += ("\n") - print(output) - f.write(output) - elif module.ret == 1: - output = (module.name + ":\n") - output += (" *Unable to detect necessary changes\n") - output += (" Current Specification version:\t" + str(versions[0]) + "\n") - output += (" Current Implementation version:\t" + str(versions[1]) + "\n") - output += (" Current Release version:\t\t" + str(versions[2]) + "\n") - output += ("\n") - print(output) - f.write(output) - elif module.ret == 100: - output = (module.name + ":\n") - output += (" Current Implementation version:\t" + str(versions[1]) + "\n") - output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n") - output += ("\n") - print(output) - f.write(output) - elif module.ret is None: - output = ("Added " + module.name + ":\n") - if module.spec() != "1.0" and module.spec() != "0.0": - output += (" Current Specification version:\t" + str(module.spec()) + "\n") - output += (" Updated Specification version:\t1.0\n") - output += ("\n") - if module.impl() != 1: - output += (" Current Implementation version:\t" + str(module.impl()) + "\n") - output += (" Updated Implementation version:\t1\n") - output += ("\n") - if module.release() != 1 and module.release() != 0: - output += (" Current Release version:\t\t" + str(module.release()) + "\n") - output += (" Updated Release version:\t\t1\n") - output += ("\n") - print(output) - f.write(output) - sys.stdout.flush() - f.close() - -# Changes cygwin paths to Windows -def fix_path(path): - if "cygdrive" in path: - new_path = path[11:] - return "C:/" + new_path - else: - return path - -# Print a 'title' -def printt(title): - print("\n" + title) - lines = "" - for letter in title: - lines += "-" - print(lines) - sys.stdout.flush() - -# Get a list of package names in the given path -# The path is expected to be of the form {base}/module/src -# -# NOTE: We currently only check for packages of the form -# org.sleuthkit.autopsy.x -# If we add other namespaces for commercial modules we will -# have to add a check here -def get_packages(path): - packages = [] - package_path = os.path.join(path, "org", "sleuthkit", "autopsy") - for folder in os.listdir(package_path): - package_string = "org.sleuthkit.autopsy." - packages.append(package_string + folder) - return packages - -# Create the given directory, if it doesn't already exist -def make_dir(dir): - try: - if not os.path.isdir(dir): - os.mkdir(dir) - if os.path.isdir(dir): - return True - return False - except: - print("Exception thrown when creating directory") - return False - -# Delete the given directory, and make sure it is deleted -def del_dir(dir): - try: - if os.path.isdir(dir): - shutil.rmtree(dir, ignore_errors=False, onerror=handleRemoveReadonly) - if os.path.isdir(dir): - return False - else: - return True - return True - except: - print("Exception thrown when deleting directory") - traceback.print_exc() - return False - -# Handle any permisson errors thrown by shutil.rmtree -def handleRemoveReadonly(func, path, exc): - excvalue = exc[1] - if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES: - os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777 - func(path) - else: - raise - -# Run git clone and git checkout for the tag -def do_git(tag, tag_dir): - try: - printt("Cloning Autopsy tag " + tag + " into dir " + tag_dir + " (this could take a while)...") - subprocess.call(["git", "clone", "https://github.com/sleuthkit/autopsy.git", tag_dir], - stdout=subprocess.PIPE) - printt("Checking out tag " + tag + "...") - subprocess.call(["git", "checkout", tag], - stdout=subprocess.PIPE, - cwd=tag_dir) - return True - except Exception as ex: - print("Error cloning and checking out Autopsy: ", sys.exc_info()[0]) - print ex - print("The terminal you are using most likely does not recognize git commands.") - return False - -# Get the flags from argv -def args(): - try: - sys.argv.pop(0) - while sys.argv: - arg = sys.argv.pop(0) - if arg == "-h" or arg == "--help": - return 1 - elif arg == "-t" or arg == "--tag": - global tag - tag = sys.argv.pop(0) - elif arg == "-s" or arg == "--source": - global source - source = sys.argv.pop(0) - elif arg == "-d" or arg == "--dir": - global docdir - docdir = sys.argv.pop(0) - elif arg == "-a" or arg == "--auto": - global dry - dry = False - else: - raise Exception() - except: - pass - -# Print script run info -def printinfo(): - global tag - global source - global docdir - global dry - printt("Release script information:") - if source is None: - source = fix_path(os.path.abspath(".")) - print("Using source directory:\n " + source) - if tag is None: - tag = get_tag(source) - print("Checking out to tag:\n " + tag) - if docdir is None: - docdir = fix_path(os.path.abspath("./jdiff-javadocs")) - print("Generating jdiff JavaDocs in:\n " + docdir) - if dry is True: - print("Dry run: will not auto-update version numbers") - sys.stdout.flush() - -# Print the script's usage/help -def usage(): - return \ - """ - USAGE: - Run this script to generate a jdiff XML summary for every module - in the current Autopsy source and in a previous source specified - by the given tag. Then, compare the XML files to see which modules - need updated version numbers. If the dry run tag is not given, the - module numbers will be automatically updated. - - OPTIONAL FLAGS: - -t --tag The tag name in git. Otherwise the NEWS file in source - will be used to determine the previous tag. - - -d --dir The output directory for the jdiff JavaDocs. If no - directory is given, the default is /javadocs/{module}. - - -s --source The directory containing Autopsy's source code. - - -a --auto Automatically update version numbers (not dry). - - -h --help Prints this usage. - """ - -# ==================================== # -# Main Functionality # -# ==================================== # - -# Where the magic happens -def main(): - global tag; global source; global docdir; global dry - tag = None; source = None; docdir = None; dry = True - - ret = args() - if ret: - print(usage()) - return 0 - printinfo() - - # ----------------------------------------------- - # 1) Clone Autopsy, checkout to given tag/commit - # 2) Get the modules in the clone and the source - # 3) Generate the xml comparison - # ----------------------------------------------- - if not del_dir("./build/" + tag): - print("\n\n=========================================") - print(" Failed to delete previous Autopsy clone.") - print(" Unable to continue...") - print("=========================================") - return 1 - tag_dir = os.path.abspath("./build/" + tag) - if not do_git(tag, tag_dir): - return 1 - sys.stdout.flush() - - tag_modules = find_modules(tag_dir) - source_modules = find_modules(source) - - printt("Generating jdiff XML reports...") - apiname_tag = tag - apiname_cur = "current" - gen_xml(tag_dir, tag_modules, apiname_tag) - gen_xml(source, source_modules, apiname_cur) - - printt("Deleting cloned Autopsy directory...") - print("Clone successfully deleted" if del_dir(tag_dir) else "Failed to delete clone") - sys.stdout.flush() - - # ----------------------------------------------------- - # 1) Seperate modules into added, similar, and removed - # 2) Compare XML for each module - # ----------------------------------------------------- - printt("Comparing modules found...") - similar_modules, added_modules, removed_modules = module_diff(source_modules, tag_modules) - if added_modules or removed_modules: - for m in added_modules: - print("+ Added " + m.name) - sys.stdout.flush() - for m in removed_modules: - print("- Removed " + m.name) - sys.stdout.flush() - else: - print("No added or removed modules") - sys.stdout.flush() - - printt("Comparing jdiff outputs...") - for module in similar_modules: - module.set_ret(compare_xml(module, apiname_tag, apiname_cur)) - - # ------------------------------------------------------------ - # 1) Do versioning - # 2) Auto-update version numbers in files and the_modules list - # 3) Auto-update dependencies - # ------------------------------------------------------------ - printt("Auto-detecting version numbers and changes...") - for module in added_modules: - module.set_versions(get_versions(module, source)) - for module in similar_modules: - module.set_versions(get_versions(module, source)) - - added_modules = remove_correct_added(added_modules) - the_modules = similar_modules + added_modules - print_version_updates(the_modules) - - if not dry: - printt("Auto-updating version numbers...") - update_versions(the_modules, source) - print("All auto-updates complete") - - printt("Detecting and auto-updating dependencies...") - update_dependencies(the_modules, source) - - printt("Deleting jdiff XML...") - xml_dir = os.path.abspath("./build/jdiff-xml") - print("XML successfully deleted" if del_dir(xml_dir) else "Failed to delete XML") - - print("\n--- Script completed successfully ---") - return 0 - -# Start off the script -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file +# ============================================================ +# update_versions.py +# ============================================================ +# +# When run from the Autopsy build script, this script will: +# - Clone Autopsy and checkout to the previous release tag +# as found in the NEWS.txt file +# - Auto-discover all modules and packages +# - Run jdiff, comparing the current and previous modules +# - Use jdiff's output to determine if each module +# a) has no changes +# b) has backwards compatible changes +# c) has backwards incompatible changes +# - Based off it's compatibility, updates each module's +# a) Major version +# b) Specification version +# c) Implementation version +# - Updates the dependencies on each module depending on the +# updated version numbers +# +# Optionally, when run from the command line, one can provide the +# desired tag to compare the current version to, the directory for +# the current version of Autopsy, and whether to automatically +# update the version numbers and dependencies. +# ------------------------------------------------------------ + +import errno +import os +import shutil +import stat +import subprocess +import sys +import traceback +from os import remove, close +from shutil import move +from tempfile import mkstemp +from xml.dom.minidom import parse, parseString + +# An Autopsy module object +class Module: + # Initialize it with a name, return code, and version numbers + def __init__(self, name=None, ret=None, versions=None): + self.name = name + self.ret = ret + self.versions = versions + # As a string, the module should be it's name + def __str__(self): + return self.name + def __repr__(self): + return self.name + # When compared to another module, the two are equal if the names are the same + def __cmp__(self, other): + if isinstance(other, Module): + if self.name == other.name: + return 0 + elif self.name < other.name: + return -1 + else: + return 1 + return 1 + def __eq__(self, other): + if isinstance(other, Module): + if self.name == other.name: + return True + return False + def set_name(self, name): + self.name = name + def set_ret(self, ret): + self.ret = ret + def set_versions(self, versions): + self.versions = versions + def spec(self): + return self.versions[0] + def impl(self): + return self.versions[1] + def release(self): + return self.versions[2] + +# Representation of the Specification version number +class Spec: + # Initialize specification number, where num is a string like x.y + def __init__(self, num): + self.third = None + spec_nums = num.split(".") + if len(spec_nums) == 3: + final = spec_nums[2] + self.third = int(final) + + l, r = spec_nums[0], spec_nums[1] + + self.left = int(l) + self.right = int(r) + + def __str__(self): + return self.get() + def __cmp__(self, other): + if isinstance(other, Spec): + if self.left == other.left: + if self.right == other.right: + return 0 + if self.right < other.right: + return -1 + return 1 + if self.left < other.left: + return -1 + return 1 + elif isinstance(other, str): + l, r = other.split(".") + if self.left == int(l): + if self.right == int(r): + return 0 + if self.right < int(r): + return -1 + return 1 + if self.left < int(l): + return -1 + return 1 + return -1 + + def overflow(self): + return str(self.left + 1) + ".0" + def increment(self): + return str(self.left) + "." + str(self.right + 1) + def get(self): + spec_str = str(self.left) + "." + str(self.right) + if self.third is not None: + spec_str += "." + str(self.final) + return spec_str + def set(self, num): + if isinstance(num, str): + l, r = num.split(".") + self.left = int(l) + self.right = int(r) + elif isinstance(num, Spec): + self.left = num.left + self.right = num.right + return self + +# ================================ # +# Core Functions # +# ================================ # + +# Given a list of modules and the names for each version, compare +# the generated jdiff XML for each module and output the jdiff +# JavaDocs. +# +# modules: the list of all modules both versions have in common +# apiname_tag: the api name of the previous version, most likely the tag +# apiname_cur: the api name of the current version, most likely "Current" +# +# returns the exit code from the modified jdiff.jar +# return code 1 = error in jdiff +# return code 100 = no changes +# return code 101 = compatible changes +# return code 102 = incompatible changes +def compare_xml(module, apiname_tag, apiname_cur): + global docdir + make_dir(docdir) + null_file = fix_path(os.path.abspath("./thirdparty/jdiff/v-custom/lib/Null.java")) + jdiff = fix_path(os.path.abspath("./thirdparty/jdiff/v-custom/jdiff.jar")) + oldapi = fix_path("build/jdiff-xml/" + apiname_tag + "-" + module.name) + newapi = fix_path("build/jdiff-xml/" + apiname_cur + "-" + module.name) + docs = fix_path(docdir + "/" + module.name) + # Comments are strange. They look for a file with additional user comments in a + # directory like docs/user_comments_for_xyz. The problem being that xyz is the + # path to the new/old api. So xyz turns into multiple directories for us. + # i.e. user_comments_for_build/jdiff-xml/[tag name]-[module name]_to_build/jdiff-xml + comments = fix_path(docs + "/user_comments_for_build") + jdiff_com = fix_path(comments + "/jdiff-xml") + tag_comments = fix_path(jdiff_com + "/" + apiname_tag + "-" + module.name + "_to_build") + jdiff_tag_com = fix_path(tag_comments + "/jdiff-xml") + + if not os.path.exists(jdiff): + print("JDIFF doesn't exist.") + + make_dir(docs) + make_dir(comments) + make_dir(jdiff_com) + make_dir(tag_comments) + make_dir(jdiff_tag_com) + make_dir("jdiff-logs") + log = open("jdiff-logs/COMPARE-" + module.name + ".log", "w") + cmd = ["javadoc", + "-doclet", "jdiff.JDiff", + "-docletpath", jdiff, + "-d", docs, + "-oldapi", oldapi, + "-newapi", newapi, + "-script", + null_file] + jdiff = subprocess.Popen(cmd, stdout=log, stderr=log) + jdiff.wait() + log.close() + code = jdiff.returncode + print("Compared XML for " + module.name) + if code == 100: + print(" No API changes") + elif code == 101: + print(" API Changes are backwards compatible") + elif code == 102: + print(" API Changes are not backwards compatible") + else: + print(" *Error in XML, most likely an empty module") + sys.stdout.flush() + return code + +# Generate the jdiff xml for the given module +# path: path to the autopsy source +# module: Module object +# name: api name for jdiff +def gen_xml(path, modules, name): + for module in modules: + # If its the regression test, the source is in the "test" dir + if module.name == "Testing": + src = os.path.join(path, module.name, "test", "qa-functional", "src") + else: + src = os.path.join(path, module.name, "src") + # xerces = os.path.abspath("./lib/xerces.jar") + xml_out = fix_path(os.path.abspath("./build/jdiff-xml/" + name + "-" + module.name)) + jdiff = fix_path(os.path.abspath("./thirdparty/jdiff/v-custom/jdiff.jar")) + make_dir("build/jdiff-xml") + make_dir("jdiff-logs") + log = open("jdiff-logs/GEN_XML-" + name + "-" + module.name + ".log", "w") + cmd = ["javadoc", + "-doclet", "jdiff.JDiff", + "-docletpath", jdiff, # ;" + xerces, <-- previous problems required this + "-apiname", xml_out, # leaving it in just in case it's needed once again + "-sourcepath", fix_path(src)] + cmd = cmd + get_packages(src) + jdiff = subprocess.Popen(cmd, stdout=log, stderr=log) + jdiff.wait() + log.close() + print("Generated XML for " + name + " " + module.name) + sys.stdout.flush() + +# Find all the modules in the given path +def find_modules(path): + modules = [] + # Step into each folder in the given path and + # see if it has manifest.mf - if so, it's a module + for dir in os.listdir(path): + directory = os.path.join(path, dir) + if os.path.isdir(directory): + for file in os.listdir(directory): + if file == "manifest.mf": + modules.append(Module(dir, None, None)) + return modules + +# Detects the differences between the source and tag modules +def module_diff(source_modules, tag_modules): + added_modules = [x for x in source_modules if x not in tag_modules] + removed_modules = [x for x in tag_modules if x not in source_modules] + similar_modules = [x for x in source_modules if x in tag_modules] + + added_modules = (added_modules if added_modules else []) + removed_modules = (removed_modules if removed_modules else []) + similar_modules = (similar_modules if similar_modules else []) + return similar_modules, added_modules, removed_modules + +# Reads the previous tag from NEWS.txt +def get_tag(sourcepath): + news = open(sourcepath + "/NEWS.txt", "r") + second_instance = False + for line in news: + if "----------------" in line: + if second_instance: + ver = line.split("VERSION ")[1] + ver = ver.split(" -")[0] + return ("autopsy-" + ver).strip() + else: + second_instance = True + continue + news.close() + + +# ========================================== # +# Dependency Functions # +# ========================================== # + +# Write a new XML file, copying all the lines from projectxml +# and replacing the specification version for the code-name-base base +# with the supplied specification version spec +def set_dep_spec(projectxml, base, spec): + print(" Updating Specification version..") + orig = open(projectxml, "r") + f, abs_path = mkstemp() + new_file = open(abs_path, "w") + found_base = False + spacing = " " + sopen = "" + sclose = "\n" + for line in orig: + if base in line: + found_base = True + if found_base and sopen in line: + update = spacing + sopen + str(spec) + sclose + new_file.write(update) + else: + new_file.write(line) + new_file.close() + close(f) + orig.close() + remove(projectxml) + move(abs_path, projectxml) + +# Write a new XML file, copying all the lines from projectxml +# and replacing the release version for the code-name-base base +# with the supplied release version +def set_dep_release(projectxml, base, release): + print(" Updating Release version..") + orig = open(projectxml, "r") + f, abs_path = mkstemp() + new_file = open(abs_path, "w") + found_base = False + spacing = " " + ropen = "" + rclose = "\n" + for line in orig: + if base in line: + found_base = True + if found_base and ropen in line: + update = spacing + ropen + str(release) + rclose + new_file.write(update) + else: + new_file.write(line) + new_file.close() + close(f) + orig.close() + remove(projectxml) + move(abs_path, projectxml) + +# Return the dependency versions in the XML dependency node +def get_dep_versions(dep): + run_dependency = dep.getElementsByTagName("run-dependency")[0] + release_version = run_dependency.getElementsByTagName("release-version") + if release_version: + release_version = getTagText(release_version[0].childNodes) + specification_version = run_dependency.getElementsByTagName("specification-version") + if specification_version: + specification_version = getTagText(specification_version[0].childNodes) + return int(release_version), Spec(specification_version) + +# Given a code-name-base, see if it corresponds with any of our modules +def get_module_from_base(modules, code_name_base): + for module in modules: + if "org.sleuthkit.autopsy." + module.name.lower() == code_name_base: + return module + return None # If it didn't match one of our modules + +# Check the text between two XML tags +def getTagText(nodelist): + for node in nodelist: + if node.nodeType == node.TEXT_NODE: + return node.data + +# Check the projectxml for a dependency on any module in modules +def check_for_dependencies(projectxml, modules): + dom = parse(projectxml) + dep_list = dom.getElementsByTagName("dependency") + for dep in dep_list: + code_name_base = dep.getElementsByTagName("code-name-base")[0] + code_name_base = getTagText(code_name_base.childNodes) + module = get_module_from_base(modules, code_name_base) + if module: + print(" Found dependency on " + module.name) + release, spec = get_dep_versions(dep) + if release != module.release() and module.release() is not None: + set_dep_release(projectxml, code_name_base, module.release()) + else: print(" Release version is correct") + if spec != module.spec() and module.spec() is not None: + set_dep_spec(projectxml, code_name_base, module.spec()) + else: print(" Specification version is correct") + +# Given the module and the source directory, return +# the paths to the manifest and project properties files +def get_dependency_file(module, source): + projectxml = os.path.join(source, module.name, "nbproject", "project.xml") + if os.path.isfile(projectxml): + return projectxml + +# Verify/Update the dependencies for each module, basing the dependency +# version number off the versions in each module +def update_dependencies(modules, source): + for module in modules: + print("Checking the dependencies for " + module.name + "...") + projectxml = get_dependency_file(module, source) + if projectxml == None: + print(" Error finding project xml file") + else: + other = [x for x in modules] + check_for_dependencies(projectxml, other) + sys.stdout.flush() + +# ======================================== # +# Versioning Functions # +# ======================================== # + +# Return the specification version in the given project.properties/manifest.mf file +def get_specification(project, manifest): + try: + # Try to find it in the project file + # it will be there if impl version is set to append automatically + f = open(project, 'r') + for line in f: + if "spec.version.base" in line: + return Spec(line.split("=")[1].strip()) + f.close() + # If not found there, try the manifest file + f = open(manifest, 'r') + for line in f: + if "OpenIDE-Module-Specification-Version:" in line: + return Spec(line.split(": ")[1].strip()) + except Exception as e: + print("Error parsing Specification version for") + print(project) + print(e) + +# Set the specification version in the given project properties file +# but if it can't be found there, set it in the manifest file +def set_specification(project, manifest, num): + try: + # First try the project file + f = open(project, 'r') + for line in f: + if "spec.version.base" in line: + f.close() + replace(project, line, "spec.version.base=" + str(num) + "\n") + return + f.close() + # If it's not there, try the manifest file + f = open(manifest, 'r') + for line in f: + if "OpenIDE-Module-Specification-Version:" in line: + f.close() + replace(manifest, line, "OpenIDE-Module-Specification-Version: " + str(num) + "\n") + return + # Otherwise we're out of luck + print(" Error finding the Specification version to update") + print(" " + manifest) + f.close() + except: + print(" Error incrementing Specification version for") + print(" " + project) + +# Return the implementation version in the given manifest.mf file +def get_implementation(manifest): + try: + f = open(manifest, 'r') + for line in f: + if "OpenIDE-Module-Implementation-Version" in line: + return int(line.split(": ")[1].strip()) + f.close() + except: + print("Error parsing Implementation version for") + print(manifest) + +# Set the implementation version in the given manifest file +def set_implementation(manifest, num): + try: + f = open(manifest, 'r') + for line in f: + if "OpenIDE-Module-Implementation-Version" in line: + f.close() + replace(manifest, line, "OpenIDE-Module-Implementation-Version: " + str(num) + "\n") + return + # If it isn't there, add it + f.close() + write_implementation(manifest, num) + except: + print(" Error incrementing Implementation version for") + print(" " + manifest) + +# Rewrite the manifest file to include the implementation version +def write_implementation(manifest, num): + f = open(manifest, "r") + contents = f.read() + contents = contents[:-2] + "OpenIDE-Module-Implementation-Version: " + str(num) + "\n\n" + f.close() + f = open(manifest, "w") + f.write(contents) + f.close() + +# Return the release version in the given manifest.mf file +def get_release(manifest): + try: + f = open(manifest, 'r') + for line in f: + if "OpenIDE-Module:" in line: + return int(line.split("/")[1].strip()) + f.close() + except: + #print("Error parsing Release version for") + #print(manifest) + return 0 + +# Set the release version in the given manifest file +def set_release(manifest, num): + try: + f = open(manifest, 'r') + for line in f: + if "OpenIDE-Module:" in line: + f.close() + index = line.index('/') - len(line) + 1 + newline = line[:index] + str(num) + replace(manifest, line, newline + "\n") + return + print(" Error finding the release version to update") + print(" " + manifest) + f.close() + except: + print(" Error incrementing release version for") + print(" " + manifest) + +# Given the module and the source directory, return +# the paths to the manifest and project properties files +def get_version_files(module, source): + manifest = os.path.join(source, module.name, "manifest.mf") + project = os.path.join(source, module.name, "nbproject", "project.properties") + if os.path.isfile(manifest) and os.path.isfile(project): + return manifest, project + +# Returns a the current version numbers for the module in source +def get_versions(module, source): + manifest, project = get_version_files(module, source) + if manifest == None or project == None: + print(" Error finding manifeset and project properties files") + return + spec = get_specification(project, manifest) + impl = get_implementation(manifest) + release = get_release(manifest) + return [spec, impl, release] + +# Update the version numbers for every module in modules +def update_versions(modules, source): + for module in modules: + versions = module.versions + manifest, project = get_version_files(module, source) + print("Updating " + module.name + "...") + if manifest == None or project == None: + print(" Error finding manifeset and project properties files") + return + if module.ret == 101: + versions = [versions[0].set(versions[0].increment()), versions[1] + 1, versions[2]] + set_specification(project, manifest, versions[0]) + set_implementation(manifest, versions[1]) + module.set_versions(versions) + elif module.ret == 102: + versions = [versions[0].set(versions[0].overflow()), versions[1] + 1, versions[2] + 1] + set_specification(project, manifest, versions[0]) + set_implementation(manifest, versions[1]) + set_release(manifest, versions[2]) + module.set_versions(versions) + elif module.ret == 100: + versions = [versions[0], versions[1] + 1, versions[2]] + set_implementation(manifest, versions[1]) + module.set_versions(versions) + elif module.ret == None: + versions = [Spec("1.0"), 1, 1] + set_specification(project, manifest, versions[0]) + set_implementation(manifest, versions[1]) + set_release(manifest, versions[2]) + module.set_versions(versions) + sys.stdout.flush() + +# Given a list of the added modules, remove the modules +# which have the correct 'new module default' version number +def remove_correct_added(modules): + correct = [x for x in modules] + for module in modules: + if module.spec() == "1.0" or module.spec() == "0.0": + if module.impl() == 1: + if module.release() == 1 or module.release() == 0: + correct.remove(module) + return correct + +# ==================================== # +# Helper Functions # +# ==================================== # + +# Replace pattern with subst in given file +def replace(file, pattern, subst): + #Create temp file + fh, abs_path = mkstemp() + new_file = open(abs_path,'w') + old_file = open(file) + for line in old_file: + new_file.write(line.replace(pattern, subst)) + #close temp file + new_file.close() + close(fh) + old_file.close() + #Remove original file + remove(file) + #Move new file + move(abs_path, file) + +# Given a list of modules print the version numbers that need changing +def print_version_updates(modules): + f = open("gen_version.txt", "a") + for module in modules: + versions = module.versions + if module.ret == 101: + output = (module.name + ":\n") + output += (" Current Specification version:\t" + str(versions[0]) + "\n") + output += (" Updated Specification version:\t" + str(versions[0].increment()) + "\n") + output += ("\n") + output += (" Current Implementation version:\t" + str(versions[1]) + "\n") + output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n") + output += ("\n") + print(output) + sys.stdout.flush() + f.write(output) + elif module.ret == 102: + output = (module.name + ":\n") + output += (" Current Specification version:\t" + str(versions[0]) + "\n") + output += (" Updated Specification version:\t" + str(versions[0].overflow()) + "\n") + output += ("\n") + output += (" Current Implementation version:\t" + str(versions[1]) + "\n") + output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n") + output += ("\n") + output += (" Current Release version:\t\t" + str(versions[2]) + "\n") + output += (" Updated Release version:\t\t" + str(versions[2] + 1) + "\n") + output += ("\n") + print(output) + sys.stdout.flush() + f.write(output) + elif module.ret == 1: + output = (module.name + ":\n") + output += (" *Unable to detect necessary changes\n") + output += (" Current Specification version:\t" + str(versions[0]) + "\n") + output += (" Current Implementation version:\t" + str(versions[1]) + "\n") + output += (" Current Release version:\t\t" + str(versions[2]) + "\n") + output += ("\n") + print(output) + f.write(output) + sys.stdout.flush() + elif module.ret == 100: + output = (module.name + ":\n") + if versions[1] is None: + output += (" No Implementation version.\n") + else: + output += (" Current Implementation version:\t" + str(versions[1]) + "\n") + output += (" Updated Implementation version:\t" + str(versions[1] + 1) + "\n") + output += ("\n") + print(output) + sys.stdout.flush() + f.write(output) + elif module.ret is None: + output = ("Added " + module.name + ":\n") + if module.spec() != "1.0" and module.spec() != "0.0": + output += (" Current Specification version:\t" + str(module.spec()) + "\n") + output += (" Updated Specification version:\t1.0\n") + output += ("\n") + if module.impl() != 1: + output += (" Current Implementation version:\t" + str(module.impl()) + "\n") + output += (" Updated Implementation version:\t1\n") + output += ("\n") + if module.release() != 1 and module.release() != 0: + output += (" Current Release version:\t\t" + str(module.release()) + "\n") + output += (" Updated Release version:\t\t1\n") + output += ("\n") + print(output) + sys.stdout.flush() + f.write(output) + sys.stdout.flush() + f.close() + +# Changes cygwin paths to Windows +def fix_path(path): + if "cygdrive" in path: + new_path = path[11:] + return "C:/" + new_path + else: + return path + +# Print a 'title' +def printt(title): + print("\n" + title) + lines = "" + for letter in title: + lines += "-" + print(lines) + sys.stdout.flush() + +# Get a list of package names in the given path +# The path is expected to be of the form {base}/module/src +# +# NOTE: We currently only check for packages of the form +# org.sleuthkit.autopsy.x +# If we add other namespaces for commercial modules we will +# have to add a check here +def get_packages(path): + packages = [] + package_path = os.path.join(path, "org", "sleuthkit", "autopsy") + for folder in os.listdir(package_path): + package_string = "org.sleuthkit.autopsy." + packages.append(package_string + folder) + return packages + +# Create the given directory, if it doesn't already exist +def make_dir(dir): + try: + if not os.path.isdir(dir): + os.mkdir(dir) + if os.path.isdir(dir): + return True + return False + except: + print("Exception thrown when creating directory") + return False + +# Delete the given directory, and make sure it is deleted +def del_dir(dir): + try: + if os.path.isdir(dir): + shutil.rmtree(dir, ignore_errors=False, onerror=handleRemoveReadonly) + if os.path.isdir(dir): + return False + else: + return True + return True + except: + print("Exception thrown when deleting directory") + traceback.print_exc() + return False + +# Handle any permisson errors thrown by shutil.rmtree +def handleRemoveReadonly(func, path, exc): + excvalue = exc[1] + if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES: + os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777 + func(path) + else: + raise + +# Run git clone and git checkout for the tag +def do_git(tag, tag_dir): + try: + printt("Cloning Autopsy tag " + tag + " into dir " + tag_dir + " (this could take a while)...") + subprocess.call(["git", "clone", "https://github.com/sleuthkit/autopsy.git", tag_dir], + stdout=subprocess.PIPE) + printt("Checking out tag " + tag + "...") + subprocess.call(["git", "checkout", tag], + stdout=subprocess.PIPE, + cwd=tag_dir) + return True + except Exception as ex: + print("Error cloning and checking out Autopsy: ", sys.exc_info()[0]) + print(str(ex)) + print("The terminal you are using most likely does not recognize git commands.") + return False + +# Get the flags from argv +def args(): + try: + sys.argv.pop(0) + while sys.argv: + arg = sys.argv.pop(0) + if arg == "-h" or arg == "--help": + return 1 + elif arg == "-t" or arg == "--tag": + global tag + tag = sys.argv.pop(0) + elif arg == "-s" or arg == "--source": + global source + source = sys.argv.pop(0) + elif arg == "-d" or arg == "--dir": + global docdir + docdir = sys.argv.pop(0) + elif arg == "-a" or arg == "--auto": + global dry + dry = False + else: + raise Exception() + except: + pass + +# Print script run info +def printinfo(): + global tag + global source + global docdir + global dry + printt("Release script information:") + if source is None: + source = fix_path(os.path.abspath(".")) + print("Using source directory:\n " + source) + if tag is None: + tag = get_tag(source) + print("Checking out to tag:\n " + tag) + if docdir is None: + docdir = fix_path(os.path.abspath("./jdiff-javadocs")) + print("Generating jdiff JavaDocs in:\n " + docdir) + if dry is True: + print("Dry run: will not auto-update version numbers") + sys.stdout.flush() + +# Print the script's usage/help +def usage(): + return \ + """ + USAGE: + Run this script to generate a jdiff XML summary for every module + in the current Autopsy source and in a previous source specified + by the given tag. Then, compare the XML files to see which modules + need updated version numbers. If the dry run tag is not given, the + module numbers will be automatically updated. + + OPTIONAL FLAGS: + -t --tag The tag name in git. Otherwise the NEWS file in source + will be used to determine the previous tag. + + -d --dir The output directory for the jdiff JavaDocs. If no + directory is given, the default is /javadocs/{module}. + + -s --source The directory containing Autopsy's source code. + + -a --auto Automatically update version numbers (not dry). + + -h --help Prints this usage. + """ + +# ==================================== # +# Main Functionality # +# ==================================== # + +# Where the magic happens +def main(): + global tag; global source; global docdir; global dry + tag = None; source = None; docdir = None; dry = True + + ret = args() + if ret: + print(usage()) + return 0 + printinfo() + + # ----------------------------------------------- + # 1) Clone Autopsy, checkout to given tag/commit + # 2) Get the modules in the clone and the source + # 3) Generate the xml comparison + # ----------------------------------------------- + if not del_dir("./build/" + tag): + print("\n\n=========================================") + print(" Failed to delete previous Autopsy clone.") + print(" Unable to continue...") + print("=========================================") + return 1 + tag_dir = os.path.abspath("./build/" + tag) + if not do_git(tag, tag_dir): + return 1 + sys.stdout.flush() + + tag_modules = find_modules(tag_dir) + source_modules = find_modules(source) + + printt("Generating jdiff XML reports...") + apiname_tag = tag + apiname_cur = "current" + gen_xml(tag_dir, tag_modules, apiname_tag) + gen_xml(source, source_modules, apiname_cur) + + printt("Deleting cloned Autopsy directory...") + print("Clone successfully deleted" if del_dir(tag_dir) else "Failed to delete clone") + sys.stdout.flush() + + # ----------------------------------------------------- + # 1) Seperate modules into added, similar, and removed + # 2) Compare XML for each module + # ----------------------------------------------------- + printt("Comparing modules found...") + similar_modules, added_modules, removed_modules = module_diff(source_modules, tag_modules) + if added_modules or removed_modules: + for m in added_modules: + print("+ Added " + m.name) + sys.stdout.flush() + for m in removed_modules: + print("- Removed " + m.name) + sys.stdout.flush() + else: + print("No added or removed modules") + sys.stdout.flush() + + printt("Comparing jdiff outputs...") + for module in similar_modules: + module.set_ret(compare_xml(module, apiname_tag, apiname_cur)) + + # ------------------------------------------------------------ + # 1) Do versioning + # 2) Auto-update version numbers in files and the_modules list + # 3) Auto-update dependencies + # ------------------------------------------------------------ + printt("Auto-detecting version numbers and changes...") + for module in added_modules: + module.set_versions(get_versions(module, source)) + for module in similar_modules: + module.set_versions(get_versions(module, source)) + + added_modules = remove_correct_added(added_modules) + the_modules = similar_modules + added_modules + print_version_updates(the_modules) + + if not dry: + printt("Auto-updating version numbers...") + update_versions(the_modules, source) + print("All auto-updates complete") + + printt("Detecting and auto-updating dependencies...") + update_dependencies(the_modules, source) + + printt("Deleting jdiff XML...") + xml_dir = os.path.abspath("./build/jdiff-xml") + print("XML successfully deleted" if del_dir(xml_dir) else "Failed to delete XML") + + print("\n--- Script completed successfully ---") + return 0 + +# Start off the script +if __name__ == "__main__": + sys.exit(main()) From 26b43162bbb2577c5beed13f6bc7fa53beb767f2 Mon Sep 17 00:00:00 2001 From: Jeff Wallace Date: Thu, 25 Jul 2013 12:37:48 -0400 Subject: [PATCH 12/20] Changed return type of run_diff to allow more descriptive diff results --- test/script/regression.py | 14 ++++++++++---- test/script/tskdbdiff.py | 12 +++++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/test/script/regression.py b/test/script/regression.py index 4ade7be66e..b2ad319963 100644 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -600,7 +600,7 @@ class TestConfiguration(object): timer = 0 self.images = [] # Email info - self.email_enabled = False + self.email_enabled = args.email_enabled self.mail_server = "" self.mail_to = "" self.mail_subject = "" @@ -710,8 +710,11 @@ class TestConfiguration(object): if subject_elements: subject = subject_elements[0] self.mail_subject = subject.getAttribute("value").encode().decode("utf_8") - if self.mail_server and self.mail_to: + if self.mail_server and self.mail_to and self.args.email_enabled: self.email_enabled = True + print("Email will be sent to ", self.mail_to) + else: + print("No email will be sent.") #-------------------------------------------------# @@ -733,8 +736,8 @@ class TestResultsDiffer(object): output_dir = test_data.output_path gold_bb_dump = test_data.get_sorted_data_path(DBType.GOLD) gold_dump = test_data.get_db_dump_path(DBType.GOLD) - test_data.db_diff_pass = TskDbDiff(output_db, gold_db, output_dir=output_dir, gold_bb_dump=gold_bb_dump, - gold_dump=gold_dump).run_diff() + test_data.db_diff_pass = all(TskDbDiff(output_db, gold_db, output_dir=output_dir, gold_bb_dump=gold_bb_dump, + gold_dump=gold_dump).run_diff()) # Compare Exceptions # replace is a fucntion that replaces strings of digits with 'd' @@ -1593,6 +1596,7 @@ class Args(object): self.exception = False self.exception_string = "" self.fr = False + self.email_enabled = False def parse(self): """Get the command line arguments and parse them.""" @@ -1652,6 +1656,8 @@ class Args(object): elif arg == "-fr" or arg == "--forcerun": print("Not downloading new images") self.fr = True + elif arg == "-e" or arg == "-email": + self.email_enabled = True else: print(usage()) return False diff --git a/test/script/tskdbdiff.py b/test/script/tskdbdiff.py index 985cd2ba17..10bfcf73d2 100644 --- a/test/script/tskdbdiff.py +++ b/test/script/tskdbdiff.py @@ -71,7 +71,7 @@ class TskDbDiff(object): bb_dump_diff_pass = self._diff(self._bb_dump, self.gold_bb_dump, self._bb_dump_diff) self._cleanup_diff() - return dump_diff_pass and bb_dump_diff_pass + return dump_diff_pass, bb_dump_diff_pass def _init_diff(self): """Set up the necessary files based on the arguments given at construction""" @@ -267,12 +267,14 @@ def main(): sys.exit() db_diff = TskDbDiff(output_db, gold_db) - passed = db_diff.run_diff() + dump_passed, bb_dump_passed = db_diff.run_diff() - if passed: + if dump_passed and bb_dump_passed: print("Database comparison passed.") - else: - print("Database comparison failed.") + elif not dump_passed: + print("Non blackboard database comparison failed.") + elif not bb_dump_passed: + print("Blackboard database comparison failed.") return 0 From 1f884055c52baa6cd8e2aa4f38ffdda316e4faac Mon Sep 17 00:00:00 2001 From: "Samuel H. Kenyon" Date: Thu, 25 Jul 2013 15:41:30 -0400 Subject: [PATCH 13/20] Added new interface to enable extensibility of CaseNewAction lookup. --- .../autopsy/casemodule/CaseNewAction.java | 4 ++-- .../casemodule/CaseNewActionInterface.java | 24 +++++++++++++++++++ .../autopsy/casemodule/CueBannerPanel.java | 2 +- .../netbeans/core/startup/Bundle.properties | 4 ++-- .../core/windows/view/ui/Bundle.properties | 6 ++--- 5 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/CaseNewActionInterface.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java index 03cdf27c96..066cd10149 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewAction.java @@ -30,8 +30,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; * * @author jantonius */ -@ServiceProvider(service = CaseNewAction.class) -public final class CaseNewAction implements ActionListener { +@ServiceProvider(service = CaseNewActionInterface.class) +public final class CaseNewAction implements CaseNewActionInterface { private NewCaseWizardAction wizard = SystemAction.get(NewCaseWizardAction.class); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewActionInterface.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewActionInterface.java new file mode 100644 index 0000000000..e770d35094 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseNewActionInterface.java @@ -0,0 +1,24 @@ +/** + * ************************************************************************* + ** This data and information is proprietary to, and a valuable trade secret * + * of, Basis Technology Corp. It is given in confidence by Basis Technology * + * and may only be used as permitted under the license agreement under which * + * it has been distributed, and in no other way. * * Copyright (c) 2013 Basis + * Technology Corp. All rights reserved. * * The technical data and information + * provided herein are provided with * `limited rights', and the computer + * software provided herein is provided * with `restricted rights' as those + * terms are defined in DAR and ASPR * 7-104.9(a). + * ************************************************************************* + */ + + +package org.sleuthkit.autopsy.casemodule; + +import java.awt.event.ActionListener; + +/** + * + */ +public interface CaseNewActionInterface extends ActionListener { + +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java index 8674fe0788..48cef48e24 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.java @@ -195,7 +195,7 @@ public class CueBannerPanel extends javax.swing.JPanel { }// //GEN-END:initComponents private void newCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newCaseButtonActionPerformed - Lookup.getDefault().lookup(CaseNewAction.class).actionPerformed(evt); + Lookup.getDefault().lookup(CaseNewActionInterface.class).actionPerformed(evt); }//GEN-LAST:event_newCaseButtonActionPerformed private void openCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openCaseButtonActionPerformed diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index e5590ed3f3..89f5fc6173 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Sun, 02 Jun 2013 00:12:29 -0400 +#Thu, 25 Jul 2013 15:34:25 -0400 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=288 SPLASH_WIDTH=538 @@ -8,4 +8,4 @@ SplashRunningTextBounds=5,266,530,17 SplashRunningTextColor=0x0 SplashRunningTextFontSize=18 -currentVersion=Autopsy 20130602 +currentVersion=Autopsy 20130725 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index bbd446205d..cb822f501c 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Thu, 23 May 2013 00:04:58 -0400 +#Thu, 25 Jul 2013 15:34:25 -0400 -CTL_MainWindow_Title=Autopsy 20130523 -CTL_MainWindow_Title_No_Project=Autopsy 20130523 +CTL_MainWindow_Title=Autopsy 20130725 +CTL_MainWindow_Title_No_Project=Autopsy 20130725 From d83c58b9265e0d55cca92b8148e67b57dd590428 Mon Sep 17 00:00:00 2001 From: "Samuel H. Kenyon" Date: Thu, 25 Jul 2013 17:19:53 -0400 Subject: [PATCH 14/20] Swapped positions of the Open Existing and Open Recent buttons. --- .../autopsy/casemodule/CueBannerPanel.form | 123 ++++++++---------- .../autopsy/casemodule/CueBannerPanel.java | 90 ++++++------- 2 files changed, 95 insertions(+), 118 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.form index ef800ab9f0..22f3d74c28 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CueBannerPanel.form @@ -1,4 +1,4 @@ - +
@@ -36,7 +36,7 @@