diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DerivedFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DerivedFileNode.java index 01f25e80cf..9b1fd9ab5f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DerivedFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DerivedFileNode.java @@ -1,23 +1,45 @@ /* - * To change this template, choose Tools | Templates - * and open the template in the editor. + * 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.datamodel; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import javax.swing.Action; import org.openide.nodes.Sheet; -import org.openide.util.Exceptions; +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.TagFileAction; import org.sleuthkit.datamodel.DerivedFile; import org.sleuthkit.datamodel.LayoutFile; -import org.sleuthkit.datamodel.TskCoreException; /** * A Node for a DerivedFile content object. - * - * TODO should be able to extend FileNode after FileNode extends AbstractFsContentNode + * + * TODO should be able to extend FileNode after FileNode extends + * AbstractFsContentNode */ -public class DerivedFileNode extends AbstractAbstractFileNode { +public class DerivedFileNode extends AbstractAbstractFileNode { public static String nameForLayoutFile(LayoutFile lf) { return lf.getName(); @@ -27,14 +49,14 @@ public class DerivedFileNode extends AbstractAbstractFileNode { super(df); this.setDisplayName(df.getName()); - - // set name, display name, and icon + + // set name, display name, and icon if (df.isDir()) { this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/Folder-icon.png"); } else { this.setIconBaseWithExtension(FileNode.getIconForFileType(df)); } - + } @Override @@ -65,6 +87,21 @@ public class DerivedFileNode extends AbstractAbstractFileNode { return s; } + @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", 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(new TagFileAction(content)); + + return actionsList.toArray(new Action[0]); + } + @Override public T accept(ContentNodeVisitor v) { return v.visit(this); @@ -79,8 +116,4 @@ public class DerivedFileNode extends AbstractAbstractFileNode { public boolean isLeafTypeNode() { return true; //!this.hasContentChildren(); } - - - - } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java index a4daa30206..7e02f807a1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ImageNode.java @@ -18,8 +18,13 @@ */ package org.sleuthkit.autopsy.datamodel; +import java.util.ArrayList; +import java.util.List; import javax.swing.Action; import org.openide.nodes.Sheet; +import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; +import org.sleuthkit.autopsy.directorytree.FileSearchAction; +import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.datamodel.Image; /** @@ -64,9 +69,13 @@ public class ImageNode extends AbstractContentNode { */ @Override public Action[] getActions(boolean context) { - return new Action[]{ // SystemAction.get( NewAction.class ), - // SystemAction.get( PasteAction.class ) - }; + List actionsList = new ArrayList(); + + actionsList.add(new NewWindowViewAction("View in New Window", this)); + actionsList.add(new FileSearchAction("Open File Search by Attributes")); + actionsList.addAll(ExplorerNodeActionVisitor.getActions(content)); + + return actionsList.toArray(new Action[0]); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index f6254cab94..00f86cee17 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -18,9 +18,17 @@ */ package org.sleuthkit.autopsy.datamodel; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import javax.swing.Action; import org.openide.nodes.Sheet; +import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; +import org.sleuthkit.autopsy.directorytree.ExternalViewerAction; +import org.sleuthkit.autopsy.directorytree.ExtractAction; +import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.autopsy.directorytree.TagFileAction; import org.sleuthkit.datamodel.LayoutFile; /** @@ -87,6 +95,20 @@ public class LayoutFileNode extends AbstractAbstractFileNode { return v.visit(this); } + @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(null); // creates a menu separator + actionsList.add(new TagFileAction(content)); + + return actionsList.toArray(new Action[0]); + } + private static void fillPropertyMap(Map map, LayoutFile content) { AbstractAbstractFileNode.fillPropertyMap(map, content); map.put(LayoutContentPropertyType.PARTS.toString(), content.getNumParts()); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java index 091ac4797e..98a2cea96a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VolumeNode.java @@ -18,9 +18,12 @@ */ package org.sleuthkit.autopsy.datamodel; +import java.util.ArrayList; +import java.util.List; import javax.swing.Action; import org.openide.nodes.Sheet; -import org.sleuthkit.autopsy.directorytree.ExtractUnallocAction; +import org.sleuthkit.autopsy.directorytree.ExplorerNodeActionVisitor; +import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.datamodel.Volume; /** @@ -65,10 +68,12 @@ public class VolumeNode extends AbstractContentNode { */ @Override public Action[] getActions(boolean popup) { - return new Action[]{ //new ShowDetailAction("Volume Details", this.getName(), this), - //new ShowDetailAction("File System Details", this.getName(), this) - //new ExtractUnallocAction("Extract Unallocated Files to single Single", this) - }; + List actionsList = new ArrayList(); + + actionsList.add(new NewWindowViewAction("View in New Window", this)); + actionsList.addAll(ExplorerNodeActionVisitor.getActions(content)); + + return actionsList.toArray(new Action[0]); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index e531caf30a..10e98b0d45 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -21,6 +21,7 @@ package org.sleuthkit.autopsy.directorytree; import java.awt.event.ActionEvent; import java.beans.PropertyVetoException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.sleuthkit.autopsy.datamodel.VolumeNode; import org.sleuthkit.autopsy.datamodel.DirectoryNode; @@ -147,111 +148,11 @@ public class DataResultFilterNode extends FilterNode { return propertySets; } + /** + * Uses the default nodes actions per node, adds some custom ones and returns them per visited node type + */ private static class GetPopupActionsDisplayableItemNodeVisitor extends DisplayableItemNodeVisitor.Default> { - @Override - public List visit(ImageNode img) { - List actions = new ArrayList(); - - //retain actions from the node if any - for (Action a : img.getActions(true)) { - actions.add(a); - } - - actions.add(new NewWindowViewAction("View in New Window", img)); - actions.add(new FileSearchAction("Open File Search by Attributes")); - actions.addAll(ExplorerNodeActionVisitor.getActions(img.getLookup().lookup(Content.class))); - return actions; - } - - @Override - public List visit(VolumeNode vol) { - List actions = new ArrayList(); - - //retain actions from the node if any - for (Action a : vol.getActions(true)) { - actions.add(a); - } - - actions.add(new NewWindowViewAction("View in New Window", vol)); - actions.addAll(ExplorerNodeActionVisitor.getActions(vol.getLookup().lookup(Content.class))); - return actions; - } - - @Override - public List visit(DirectoryNode dir) { - //preserve the default node's actions - List actions = new ArrayList(); - - for (Action action : dir.getActions(true)) { - actions.add(action); - } - - return actions; - } - - @Override - public List visit(LayoutFileNode lf) { - List actions = new ArrayList(); - - //retain actions from the node if any - for (Action a : lf.getActions(true)) { - actions.add(a); - } - - actions.add(new NewWindowViewAction("View in New Window", lf)); - actions.add(new ExternalViewerAction("Open in External Viewer", lf)); - actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract File", lf)); - actions.add(null); // creates a menu separator - actions.add(new TagFileAction(lf)); - return actions; - } - - @Override - public List visit(VirtualDirectoryNode ld) { - List actions = new ArrayList(); - - //retain actions from the node if any - for (Action a : ld.getActions(true)) { - actions.add(a); - } - - actions.add(new TagFileAction(ld)); - return actions; - } - - @Override - public List visit(DerivedFileNode dfn) { - List actions = new ArrayList(); - - //retain actions from the node if any - for (Action a : dfn.getActions(true)) { - actions.add(a); - } - - actions.add(new NewWindowViewAction("View in New Window", dfn)); - actions.add(new ExternalViewerAction("Open in External Viewer", dfn)); - actions.add(null); // creates a menu separator - actions.add(new ExtractAction("Extract", dfn)); //might not need this actions - already local file - actions.add(new HashSearchAction("Search for files with the same MD5 hash", dfn)); - actions.add(null); // creates a menu separator - actions.add(new TagFileAction(dfn)); - - return actions; - } - - @Override - public List visit(FileNode f) { - //preserve the default node's actions - List actions = new ArrayList(); - - for (Action action : f.getActions(true)) { - actions.add(action); - } - - return actions; - } @Override public List visit(BlackboardArtifactNode ban) { @@ -322,39 +223,22 @@ public class DataResultFilterNode extends FilterNode { actions.add(new TagResultAction(ba)); } } - //if (artifactTypeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { - //actions.add(null); // creates a menu separator - //actions.add(new ResultDeleteAction("Delete Result", ba)); - //} return actions; } - @Override - public List visit(KeywordHitsRootNode khrn) { - //List actions = new ArrayList(); - //actions.add(null); // creates a menu separator - //actions.add(new ResultDeleteAction("Delete Results", BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT)); - //return actions; - return super.visit(khrn); - } - - @Override - public List visit(KeywordHitsListNode khsn) { - //TODO delete by list - return super.visit(khsn); - } - - @Override - public List visit(KeywordHitsKeywordNode khmln) { - //TODO delete by keyword hit - return super.visit(khmln); - } @Override protected List defaultVisit(DisplayableItemNode ditem) { - return new ArrayList(); + //preserve the default node's actions + List actions = new ArrayList(); + + for (Action action : ditem.getActions(true)) { + actions.add(action); + } + + return actions; } private Content findLinked(BlackboardArtifactNode ba) { diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 0f9896bbcf..ed779dcca4 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -80,7 +80,6 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private transient ExplorerManager em = new ExplorerManager(); private static DirectoryTreeTopComponent instance; private DataResultTopComponent dataResult = new DataResultTopComponent(true, "Directory Listing"); - private LinkedList backList; private LinkedList forwardList; /** @@ -567,100 +566,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // } // change in node selection else if (changed.equals(ExplorerManager.PROP_SELECTED_NODES)) { - if (getSelectedNode() == null && oldValue != null) { - try { - em.setSelectedNodes((Node[]) oldValue); - } catch (PropertyVetoException ex) { - logger.log(Level.WARNING, "Error resetting node", ex); - } - } - final Node[] oldNodes = (Node[]) oldValue; - final Node[] newNodes = (Node[]) newValue; - // Some lock that prevents certain Node operations is set during the - // ExplorerManager selection-change, so we must handle changes after the - // selection-change event is processed. - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - // change the cursor to "waiting cursor" for this operation - DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - try { - - // make sure dataResult is open - dataResult.open(); - - Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode(); - if (treeNode != null) { - OriginalNode origin = treeNode.getLookup().lookup(OriginalNode.class); - if (origin == null) { - return; - } - Node originNode = origin.getNode(); - - DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - //set node, wrap in filter node first to filter out children - Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em); - DirectoryTreeTopComponent.this.dataResult.setNode(new TableFilterNode(drfn, true)); - - String displayName = ""; - if (originNode.getLookup().lookup(Content.class) != null) { - Content content = originNode.getLookup().lookup(Content.class); - if (content != null) { - try { - displayName = content.getUniquePath(); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Exception while calling Content.getUniquePath() for node: " + originNode); - } - } - } else if (originNode.getLookup().lookup(String.class) != null) { - displayName = originNode.getLookup().lookup(String.class); - } - DirectoryTreeTopComponent.this.dataResult.setPath(displayName); - } - - // set the directory listing to be active - if (oldNodes != null && newNodes != null - && (oldNodes.length == newNodes.length)) { - boolean sameNodes = true; - for (int i = 0; i < oldNodes.length; i++) { - sameNodes = sameNodes && oldNodes[i].getName().equals(newNodes[i].getName()); - } - if (!sameNodes) { - dataResult.requestActive(); - } - } - } finally { - DirectoryTreeTopComponent.this.setCursor(null); - } - } - }); - - // update the back and forward list - Node[] selectedNode = em.getSelectedNodes(); - if (selectedNode.length > 0 ) { - Node selectedContext = selectedNode[0]; - - final String[] selectedPath = NodeOp.createPath(selectedContext, em.getRootContext()); - String[] currentLast = backList.peekLast(); - String lastNodeName = null; - if (currentLast != null) { - lastNodeName = currentLast[currentLast.length-1]; - } - String selectedNodeName = selectedContext.getName(); - if (currentLast == null || !selectedNodeName.equals(lastNodeName)) { - //add to the list if the last if not the same as current - - backList.addLast(selectedPath); // add the node to the "backList" - if (backList.size() > 1) { - backButton.setEnabled(true); - } else { - backButton.setEnabled(false); - } - - forwardList.clear(); // clear the "forwardList" - forwardButton.setEnabled(false); // disable the forward Button - } - } + respondSelection((Node[]) oldValue, (Node[]) newValue); } else if (changed.equals(IngestModuleEvent.DATA.toString())) { final ModuleDataEvent event = (ModuleDataEvent) oldValue; SwingUtilities.invokeLater(new Runnable() { @@ -687,6 +593,114 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } } + /** + * Event handler to run when selection changed + * + * TODO this needs to be revised + * + * @param oldNodes + * @param newNodes + */ + private void respondSelection(final Node[] oldNodes, final Node[] newNodes) { + + //this looks redundant? +// if (getSelectedNode() == null && oldNodes != null) { +// try { +// em.setSelectedNodes(oldNodes); +// } catch (PropertyVetoException ex) { +// logger.log(Level.WARNING, "Error resetting node", ex); +// } +// } + + // Some lock that prevents certain Node operations is set during the + // ExplorerManager selection-change, so we must handle changes after the + // selection-change event is processed. + //TODO find a different way to refresh data result viewer, scheduling this + //to EDT breaks loading of nodes in the background + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + // change the cursor to "waiting cursor" for this operation + DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + try { + + // make sure dataResult is open, redundant? + //dataResult.open(); + + Node treeNode = DirectoryTreeTopComponent.this.getSelectedNode(); + if (treeNode != null) { + OriginalNode origin = treeNode.getLookup().lookup(OriginalNode.class); + if (origin == null) { + return; + } + Node originNode = origin.getNode(); + + DirectoryTreeTopComponent.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + //set node, wrap in filter node first to filter out children + Node drfn = new DataResultFilterNode(originNode, DirectoryTreeTopComponent.this.em); + DirectoryTreeTopComponent.this.dataResult.setNode(new TableFilterNode(drfn, true)); + + String displayName = ""; + if (originNode.getLookup().lookup(Content.class) != null) { + Content content = originNode.getLookup().lookup(Content.class); + if (content != null) { + try { + displayName = content.getUniquePath(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Exception while calling Content.getUniquePath() for node: " + originNode); + } + } + } else if (originNode.getLookup().lookup(String.class) != null) { + displayName = originNode.getLookup().lookup(String.class); + } + DirectoryTreeTopComponent.this.dataResult.setPath(displayName); + } + + // set the directory listing to be active + if (oldNodes != null && newNodes != null + && (oldNodes.length == newNodes.length)) { + boolean sameNodes = true; + for (int i = 0; i < oldNodes.length; i++) { + sameNodes = sameNodes && oldNodes[i].getName().equals(newNodes[i].getName()); + } + if (!sameNodes) { + dataResult.requestActive(); + } + } + } finally { + DirectoryTreeTopComponent.this.setCursor(null); + } + } + }); + + // update the back and forward list + Node[] selectedNode = em.getSelectedNodes(); + if (selectedNode.length > 0) { + Node selectedContext = selectedNode[0]; + + final String[] selectedPath = NodeOp.createPath(selectedContext, em.getRootContext()); + String[] currentLast = backList.peekLast(); + String lastNodeName = null; + if (currentLast != null) { + lastNodeName = currentLast[currentLast.length - 1]; + } + String selectedNodeName = selectedContext.getName(); + if (currentLast == null || !selectedNodeName.equals(lastNodeName)) { + //add to the list if the last if not the same as current + + backList.addLast(selectedPath); // add the node to the "backList" + if (backList.size() > 1) { + backButton.setEnabled(true); + } else { + backButton.setEnabled(false); + } + + forwardList.clear(); // clear the "forwardList" + forwardButton.setEnabled(false); // disable the forward Button + } + } + } + @Override public synchronized void addPropertyChangeListener(PropertyChangeListener listener) { pcs.addPropertyChangeListener(listener); @@ -739,8 +753,8 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat RootContentChildren contentRootChildren = (RootContentChildren) imagesNode.getChildren(); contentRootChildren.refreshContentKeys(); - final TreeView tree = getTree(); - tree.expandNode(imagesNode); + //final TreeView tree = getTree(); + //tree.expandNode(imagesNode); setSelectedNode(selectedPath, ImagesNode.NAME); @@ -751,6 +765,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * be called in the gui thread */ void refreshTree(final BlackboardArtifact.ARTIFACT_TYPE... types) { + //save current selection Node selectedNode = getSelectedNode(); final String[] selectedPath = NodeOp.createPath(selectedNode, em.getRootContext()); @@ -791,6 +806,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat } tree.expandNode(childNode); + //restores selection if it was under the Results node setSelectedNode(selectedPath, ResultsNode.NAME); } @@ -816,13 +832,13 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat Node newSelection = NodeOp.findPath(em.getRootContext(), path); //resetHistoryListAndButtons(); if (newSelection != null) { - if (rootNodeName != null) { + if (rootNodeName != null) { //called from tree auto refresh context //remove last from backlist, because auto select will result in duplication backList.pollLast(); } //select - tree.expandNode(newSelection); + //tree.expandNode(newSelection); em.setExploredContextAndSelection(newSelection, new Node[]{newSelection}); } // We need to set the selection, which will refresh dataresult and get rid of the oob exception diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java index dee3b68d3d..f5b22c8395 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java @@ -45,7 +45,7 @@ import org.sleuthkit.datamodel.FileSystem; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.Volume; -class ExplorerNodeActionVisitor extends ContentVisitor.Default> { +public class ExplorerNodeActionVisitor extends ContentVisitor.Default> { private static ExplorerNodeActionVisitor instance = new ExplorerNodeActionVisitor(); diff --git a/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java b/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java index e217f1aefd..26ea16c14d 100644 --- a/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java +++ b/ExifParser/src/org/sleuthkit/autopsy/exifparser/ExifParserFileIngestModule.java @@ -31,6 +31,7 @@ import com.drew.metadata.exif.GpsDirectory; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; @@ -70,6 +71,10 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil private String args; + private static final int readHeaderSize = 2; + private final byte[] fileHeaderBuffer = new byte[readHeaderSize]; + private static final char JPEG_SIGNATURE_BE = 0xFFD8; + private static final Logger logger = Logger.getLogger(ExifParserFileIngestModule.class.getName()); private static ExifParserFileIngestModule defaultInstance = null; private static int messageId = 0; @@ -183,6 +188,12 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil return IngestModuleAbstractFile.ProcessResult.ERROR; } + /** + * Checks if should try to attempt to extract exif. + * Currently checks if JPEG image, first by extension, then by signature (if extension fails) + * @param f file to be checked + * @return true if to be processed + */ private boolean parsableFormat(AbstractFile f) { // Get the name, extension String name = f.getName(); @@ -195,8 +206,38 @@ public final class ExifParserFileIngestModule implements IngestModuleAbstractFil return true; } - return false; + return isJpegFileHeader(f); + } + + /** + * Check if is jpeg file based on header + * @param file + * @return true if jpeg file, false otherwise + */ + private boolean isJpegFileHeader(AbstractFile file) { + if (file.getSize() < readHeaderSize) { + return false; + } + + int bytesRead = 0; + try { + bytesRead = file.read(fileHeaderBuffer, 0, readHeaderSize); + } catch (TskCoreException ex) { + //ignore if can't read the first few bytes, not a JPEG + return false; + } + if (bytesRead != readHeaderSize) { + return false; + } + + ByteBuffer bytes = ByteBuffer.wrap(fileHeaderBuffer); + char signature = bytes.getChar(); + + return signature == JPEG_SIGNATURE_BE; + + } + @Override public void complete() { diff --git a/NEWS.txt b/NEWS.txt index ca7a861593..d55db86783 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -11,6 +11,8 @@ Improvements: Bugfixes: - show error message in hex and string viewer if specific offset of a file could not be read. - file search actions not always enabled when new case is open. +- fixed directory tree history being reset when tree is refreshed. +- exif module better jpeg detection using signature and not only file extension. ---------------- VERSION 3.0.4 -------------- diff --git a/SevenZip/src/org/sleuthkit/autopsy/sevenzip/SevenZipIngestModule.java b/SevenZip/src/org/sleuthkit/autopsy/sevenzip/SevenZipIngestModule.java index 4e625793fe..c27cba4193 100644 --- a/SevenZip/src/org/sleuthkit/autopsy/sevenzip/SevenZipIngestModule.java +++ b/SevenZip/src/org/sleuthkit/autopsy/sevenzip/SevenZipIngestModule.java @@ -24,6 +24,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -90,6 +91,11 @@ public final class SevenZipIngestModule implements IngestModuleAbstractFile { private static final long MIN_FREE_DISK_SPACE = 1 * 1000 * 1000000L; //1GB //counts archive depth private ArchiveDepthCountTree archiveDepthCountTree; + //buffer for checking file headers and signatures + private static final int readHeaderSize = 4; + private final byte[] fileHeaderBuffer = new byte[readHeaderSize]; + private static final int ZIP_SIGNATURE_BE = 0x504B0304; + //private constructor to ensure singleton instance private SevenZipIngestModule() { @@ -617,8 +623,41 @@ public final class SevenZipIngestModule implements IngestModuleAbstractFile { return true; } } - return false; + + //if no extension match, check for zip signature + //(note, in near future, we will use pre-detected content type) + return isZipFileHeader(file); + } + + /** + * Check if is zip file based on header + * @param file + * @return true if zip file, false otherwise + */ + private boolean isZipFileHeader(AbstractFile file) { + if (file.getSize() < readHeaderSize) { + return false; + } + + int bytesRead = 0; + try { + bytesRead = file.read(fileHeaderBuffer, 0, readHeaderSize); + } catch (TskCoreException ex) { + //ignore if can't read the first few bytes, not a ZIP + return false; + } + if (bytesRead != readHeaderSize) { + return false; + } + + ByteBuffer bytes = ByteBuffer.wrap(fileHeaderBuffer); + int signature = bytes.getInt(); + + return signature == ZIP_SIGNATURE_BE; + + } + /** * Stream used to unpack the archive to local file diff --git a/Timeline/src/org/sleuthkit/autopsy/timeline/Simile2.java b/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java similarity index 64% rename from Timeline/src/org/sleuthkit/autopsy/timeline/Simile2.java rename to Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java index 0805331cae..6e6c02047b 100644 --- a/Timeline/src/org/sleuthkit/autopsy/timeline/Simile2.java +++ b/Timeline/src/org/sleuthkit/autopsy/timeline/Timeline.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.timeline; import com.sun.javafx.application.PlatformImpl; import java.awt.Component; +import java.awt.Cursor; import java.awt.Dimension; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -42,6 +43,8 @@ import java.util.Scanner; import java.util.Stack; import java.util.logging.Level; import javafx.application.Platform; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.embed.swing.JFXPanel; @@ -78,7 +81,6 @@ import org.openide.modules.InstalledFileLocator; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; -import org.openide.util.Exceptions; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.actions.CallableSystemAction; @@ -104,15 +106,15 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; -@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Simile2") +@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.timeline.Timeline") @ActionRegistration(displayName = "#CTL_MakeTimeline") @ActionReferences(value = { @ActionReference(path = "Menu/Tools", position = 100)}) @NbBundle.Messages(value = "CTL_TimelineView=Generate Timeline") +public class Timeline extends CallableSystemAction implements Presenter.Toolbar, PropertyChangeListener { -public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, PropertyChangeListener { - private static final Logger logger = Logger.getLogger(Simile2.class.getName()); - private final java.io.File macRoot = InstalledFileLocator.getDefault().locate("mactime", Simile2.class.getPackage().getName(), false); + private static final Logger logger = Logger.getLogger(Timeline.class.getName()); + private final java.io.File macRoot = InstalledFileLocator.getDefault().locate("mactime", Timeline.class.getPackage().getName(), false); private JFrame jf; //frame for holding all the elements private Group group_Charts; //Orders the charts private Scene scene_Charts; //Displays the charts @@ -123,8 +125,7 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, private ScrollPane scroll_Events; //Scroll Panes for dealing with oversized an oversized chart private final int Height_Frame = 850; //Sizing constants private final int Width_Frame = 1300; - private Button button_DrillUp; //Navigation buttons - private Button button_Go; + private Button zoomOutButton; //Navigation buttons private ComboBox dropdown_SelectYears; //Dropdown box for selecting years. Useful when the charts' scale means some years are unclickable, despite having events. private final Stack stack_PrevCharts = new Stack(); //Stack for storing drill-up information. private BarChart chart_TopLevel; //the topmost chart, used for resetting to default view. @@ -136,51 +137,76 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, private List data; private boolean listeningToAddImage = false; private long lastObjectId = -1; - private TimelineProgressDialog dialog; + private TimelineProgressDialog progressDialog; + private EventHandler mouseEnteredListener; + private EventHandler mouseExitedListener; + private JSplitPane splitXPane, splitYPane; + private SleuthkitCase skCase; //Swing components and JavafX components don't play super well together //Swing components need to be initialized first, in the swing specific thread //Next, the javafx components may be initialized. - private void customizeSwing() { + private void customize() { + + //listeners + mouseEnteredListener = new EventHandler() { + @Override + public void handle(MouseEvent e) { + panel_Charts.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + }; + mouseExitedListener = new EventHandler() { + @Override + public void handle(MouseEvent e) { + panel_Charts.setCursor(null); + } + }; + SwingUtilities.invokeLater(new Runnable() { @Override public void run() { + //Making the main frame * + jf = new JFrame(Case.getCurrentCase().getName() + " - Autopsy Timeline (Beta)"); + + //use the same icon on jframe as main application + jf.setIconImage(WindowManager.getDefault().getMainWindow().getIconImage()); + jf.setSize(Width_Frame, Height_Frame); //(Width, Height) + + dataContentPanel = new DataContentPanel(); + dataResult = DataResultPanel.createInstance("Timeline Results", "", Node.EMPTY, 0, dataContentPanel); dataResult.setContentViewer(new DataContentPanel()); dataResult.setAlignmentX(Component.LEFT_ALIGNMENT); dataResult.setPreferredSize(new Dimension(700, 300)); logger.log(Level.INFO, "Successfully created viewers"); + + //ViewerJPanel holds both of the DataResult/DataContent viewers, + //aligned horizontally (X_AXIS) + final JPanel viewerJPanel = new JPanel(); + viewerJPanel.setLayout(new BoxLayout(viewerJPanel, BoxLayout.X_AXIS)); + + splitXPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, dataResult, dataContentPanel); + splitXPane.setDividerLocation(Width_Frame / 2); + viewerJPanel.add(splitXPane); + viewerJPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + + //The chartJpanel holds the chart, + //aligned vertically (Y_AXIS) + final JPanel chartJPanel = new JPanel(); + chartJPanel.setLayout(new BoxLayout(chartJPanel, BoxLayout.Y_AXIS)); + + splitYPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, chartJPanel, viewerJPanel); + splitYPane.setDividerLocation(Height_Frame / 2); + + runJavaFxThread(chartJPanel); } }); + + } - - private void customize() { - //Making the main frame * - jf = new JFrame(Case.getCurrentCase().getName() + " - Autopsy Timeline (Beta)"); - - //use the same icon on jframe as main application - jf.setIconImage(WindowManager.getDefault().getMainWindow().getIconImage()); - jf.setSize(Width_Frame, Height_Frame); //(Width, Height) - - //JPanels are used as the cohesive glue that binds everything together.*/ - //The chartJpanel holds the chart, - //aligned vertically (Y_AXIS) - final JPanel chartJPanel = new JPanel(); - chartJPanel.setLayout(new BoxLayout(chartJPanel, BoxLayout.Y_AXIS)); - - //ViewerJPanel holds both of the DataResult/DataContent viewers, - //aligned horizontally (X_AXIS) - final JPanel viewerJPanel = new JPanel(); - viewerJPanel.setLayout(new BoxLayout(viewerJPanel, BoxLayout.X_AXIS)); - - //ComboJPanel holds both of the above JPanels together, - //aligned vertically (Y_AXIS) - - // create a horizontal split pane - final JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, chartJPanel, viewerJPanel); - splitPane.setDividerLocation(450); + private void runJavaFxThread(final JPanel chartJPanel) { //JavaFX thread //JavaFX components MUST be run in the JavaFX thread, otherwise massive amounts of exceptions will be thrown and caught. Liable to freeze up and crash. //Components can be declared whenever, but initialization and manipulation must take place here. @@ -192,6 +218,7 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, progress = ProgressHandleFactory.createHandle("Creating timeline . . ."); progress.start(); + chart_Events = null; panel_Charts = new JFXPanel(); group_Charts = new Group(); scene_Charts = new Scene(group_Charts, Width_Frame, Math.round(Height_Frame / .75)); //Width, Height @@ -210,20 +237,32 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, if (!moduleDir.exists()) { moduleDir.mkdir(); } - + + int currentProgress = 0; java.io.File mactimeFile = new java.io.File(moduleDir, mactimeFileName); if (!mactimeFile.exists()) { - logger.log(Level.INFO, "Creating mactime file."); + progressDialog.setProgressTotal(3); //total 3 units + logger.log(Level.INFO, "Creating body file"); + progressDialog.updateProgressBar("Generating Bodyfile"); String bodyFilePath = makeBodyFile(); + progressDialog.updateProgressBar(++currentProgress); + logger.log(Level.INFO, "Creating mactime file: " + mactimeFile.getAbsolutePath()); + progressDialog.updateProgressBar("Generating Mactime"); makeMacTime(bodyFilePath); + progressDialog.updateProgressBar(++currentProgress); data = null; } else { - logger.log(Level.INFO, "mactime file already exists; parsing that."); + progressDialog.setProgressTotal(1); //total 1 units + logger.log(Level.INFO, "Mactime file already exists; parsing that: " + mactimeFile.getAbsolutePath()); } + + progressDialog.updateProgressBar("Parsing Mactime"); if (data == null) { + logger.log(Level.INFO, "Parsing mactime file: " + mactimeFile.getAbsolutePath()); data = parseMacTime(mactimeFile); //The sum total of the mactime parsing. YearEpochs contain everything you need to make a timeline. } + progressDialog.updateProgressBar(++currentProgress); //Making a dropdown box to select years. List lsi = new ArrayList(); //List is in the format of {Year : Number of Events}, used for selecting from the dropdown. @@ -233,9 +272,9 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, ObservableList listSelect = FXCollections.observableArrayList(lsi); dropdown_SelectYears = new ComboBox(listSelect); - //Buttons for navigating up and down the timeline - button_DrillUp = new Button("Zoom Out"); - button_DrillUp.setOnAction(new EventHandler() { + //Buttons for navigating up and down the timeline + zoomOutButton = new Button("Zoom Out"); + zoomOutButton.setOnAction(new EventHandler() { @Override public void handle(ActionEvent e) { BarChart bc; @@ -248,21 +287,25 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, scroll_Events.setContent(chart_Events); } }); - - button_Go = new Button("►"); - button_Go.setOnAction(new EventHandler() { + + dropdown_SelectYears.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() { @Override - public void handle(ActionEvent e) { + public void changed(ObservableValue ov, String t, String t1) { if (dropdown_SelectYears.getValue() != null) { - chart_Events = createMonthsWithDrill(findYear(data, Integer.valueOf(dropdown_SelectYears.getValue().split(" ")[0]))); - scroll_Events.setContent(chart_Events); + chartJPanel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + try { + chart_Events = createMonthsWithDrill(findYear(data, Integer.valueOf(dropdown_SelectYears.getValue().split(" ")[0]))); + scroll_Events.setContent(chart_Events); + } finally { + chartJPanel.setCursor(null); + } } } }); - //Adding things to the V and H boxes. + //Adding things to the V and H boxes. //hBox_Charts stores the pseudo menu bar at the top of the timeline. |Zoom Out|View Year: [Select Year]|►| - hBox_Charts.getChildren().addAll(button_DrillUp, new Label("View Year:"), dropdown_SelectYears, button_Go); + hBox_Charts.getChildren().addAll(zoomOutButton, new Label("Go To:"), dropdown_SelectYears); vBox_FX.getChildren().addAll(hBox_Charts, scroll_Events); //FxBox_V holds things in a visual stack. group_Charts.getChildren().add(vBox_FX); //Adding the FxBox to the group. Groups make things easier to manipulate without having to update a hundred things every change. panel_Charts.setScene(scene_Charts); @@ -271,24 +314,22 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, panel_Charts.setAlignmentX(Component.LEFT_ALIGNMENT); chartJPanel.add(panel_Charts); - viewerJPanel.add(dataResult); - viewerJPanel.add(dataContentPanel); chartJPanel.setAlignmentX(Component.LEFT_ALIGNMENT); - viewerJPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + chart_TopLevel = createYearChartWithDrill(data); chart_Events = chart_TopLevel; scroll_Events.setContent(chart_Events); - jf.add(splitPane); + jf.add(splitYPane); jf.setVisible(true); } finally { // stop the progress bar progress.finish(); - - // close the dialog - dialog.doClose(0); + + // close the progressDialog + progressDialog.doClose(0); } } }); @@ -298,7 +339,7 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, * Creates a BarChart with datapoints for all the years from the parsed * mactime file. * - * @param allYears The list of years that have data from the mactime file + * @param allYears The list of years that have barData from the mactime file * @return BarChart scaled to the year level */ private BarChart createYearChartWithDrill(final List allYears) { @@ -309,7 +350,7 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, l.setTextFill(Color.AZURE); xAxis.setLabel("Years"); yAxis.setLabel("Number of Events"); - //Charts are made up of individual pieces of Chart.Data. In this case, a piece of data is a single bar on the graph. + //Charts are made up of individual pieces of Chart.Data. In this case, a piece of barData is a single bar on the graph. //Data is packaged into a series, which can be assigned custom colors or styling //After the series are created, 1 or more series are packaged into a single chart. ObservableList> bcData = FXCollections.observableArrayList(); @@ -326,31 +367,34 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, // But it is for this reason that the chart generating functions have two forloops. I do not believe they can be condensed into a single loop due to the nodes being null until // an undetermined point in time. BarChart bc = new BarChart(xAxis, yAxis, bcData); - for (final BarChart.Data data : bc.getData().get(0).getData()) { //.get(0) refers to the BarChart.Series class to work on. There is only one series in this graph, so get(0) is safe. - data.getNode().setScaleX(.5); - data.getNode().addEventHandler(MouseEvent.MOUSE_CLICKED, + for (final BarChart.Data barData : bc.getData().get(0).getData()) { //.get(0) refers to the BarChart.Series class to work on. There is only one series in this graph, so get(0) is safe. + barData.getNode().setScaleX(.5); + + final javafx.scene.Node barNode = barData.getNode(); + //hover listener + barNode.addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, mouseEnteredListener); + barNode.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, mouseExitedListener); + + //click listener + barNode.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler() { - @Override - public void handle(MouseEvent e) { - if (e.getButton().equals(MouseButton.PRIMARY)) { - if (e.getClickCount() == 2) { //Checking for a doubleclick - PlatformImpl.startup(new Runnable() { - @Override - public void run() { - BarChart b = createMonthsWithDrill((YearEpoch) findYear(allYears, Integer.valueOf((String) data.getXValue()))); - chart_Events = b; - scroll_Events.setContent(chart_Events); - } - }); - //If a single click, hover a label over the cursor with information about the selection - } else if (e.getClickCount() == 1) { - l.setText(findYear(allYears, Integer.valueOf((String) data.getXValue())).getNumFiles() + " events"); - l.setTranslateX(e.getX()); - l.setTranslateY(e.getY()); + @Override + public void handle(MouseEvent e) { + if (e.getButton().equals(MouseButton.PRIMARY)) { + if (e.getClickCount() == 1) { + Platform.runLater(new Runnable() { + @Override + public void run() { + BarChart b = createMonthsWithDrill((YearEpoch) findYear(allYears, Integer.valueOf((String) barData.getXValue()))); + chart_Events = b; + scroll_Events.setContent(chart_Events); } - } + }); + } - }); + } + } + }); } bc.autosize(); //Get an auto height @@ -376,37 +420,43 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, String monthName = new DateFormatSymbols().getMonths()[monthNum]; MonthEpoch month = ye.getMonth(monthNum); int numEvents = month == null ? 0 : month.getNumFiles(); - se.getData().add(new BarChart.Data(monthName, numEvents)); //Adding new data at {X-pos, Y-Pos} + se.getData().add(new BarChart.Data(monthName, numEvents)); //Adding new barData at {X-pos, Y-Pos} } bcData.add(se); final BarChart bc = new BarChart(xAxis, yAxis, bcData); for (int i = 0; i < 12; i++) { - for (final BarChart.Data data : bc.getData().get(0).getData()) { + for (final BarChart.Data barData : bc.getData().get(0).getData()) { //Note: - // All the charts of this package have a problem where when the chart gets below a certain pixel ratio, the data stops drawing. The axes and the labels remain, - // But the actual chart data is invisible, unclickable, and unrendered. To partially compensate for that, data.getNode() can be manually scaled up to increase visibility. + // All the charts of this package have a problem where when the chart gets below a certain pixel ratio, the barData stops drawing. The axes and the labels remain, + // But the actual chart barData is invisible, unclickable, and unrendered. To partially compensate for that, barData.getNode() can be manually scaled up to increase visibility. // Sometimes I've had it jacked up to as much as x2400 just to see a sliver of information. // But that doesn't work all the time. Adding it to a scrollpane and letting the user scroll up and down to view the chart is the other workaround. Both of these fixes suck. + final javafx.scene.Node barNode = barData.getNode(); + barNode.setScaleX(.5); - data.getNode().setScaleX(.5); - data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED, + //hover listener + barNode.addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, mouseEnteredListener); + barNode.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, mouseExitedListener); + + //clicks + barNode.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler() { - @Override - public void handle(MouseEvent e) { - if (e.getButton().equals(MouseButton.PRIMARY)) { - if (e.getClickCount() == 2) { - PlatformImpl.startup(new Runnable() { - @Override - public void run() { - chart_Events = createEventsByMonth(findMonth(ye.months, monthStringToInt((String) data.getXValue())), ye); - scroll_Events.setContent(chart_Events); - } - }); + @Override + public void handle(MouseEvent e) { + if (e.getButton().equals(MouseButton.PRIMARY)) { + if (e.getClickCount() == 1) { + Platform.runLater(new Runnable() { + @Override + public void run() { + chart_Events = createEventsByMonth(findMonth(ye.months, monthStringToInt((String) barData.getXValue())), ye); + scroll_Events.setContent(chart_Events); } - } + }); } - }); + } + } + }); } } @@ -430,40 +480,50 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, ObservableList bcData = makeObservableListByMonthAllDays(me, ye.getYear()); BarChart.Series series = new BarChart.Series(bcData); series.setName(me.getMonthName() + " " + ye.getYear()); - + ObservableList> ol = FXCollections.observableArrayList(series); final BarChart bc = new BarChart(xAxis, yAxis, ol); - for (final BarChart.Data data : bc.getData().get(0).getData()) { + for (final BarChart.Data barData : bc.getData().get(0).getData()) { //data.getNode().setScaleX(2); - data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED, - new EventHandler() { - MonthEpoch myme = me; + final javafx.scene.Node barNode = barData.getNode(); + + //hover listener + barNode.addEventHandler(MouseEvent.MOUSE_ENTERED_TARGET, mouseEnteredListener); + barNode.addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, mouseExitedListener); + + barNode.addEventHandler(MouseEvent.MOUSE_PRESSED, + new EventHandler() { + MonthEpoch myme = me; + + @Override + public void handle(MouseEvent e) { + final int day = (Integer.valueOf(((String) barData.getXValue()).split("-")[1])); + final DayEpoch de = myme.getDay(day); + final List afs; + if (de != null) { + afs = de.getEvents(); + } else { + logger.log(Level.SEVERE, "There were no events for the clicked-on day: " + day); + return; + } + + SwingUtilities.invokeLater(new Runnable() { @Override - public void handle(MouseEvent e) { - int day = (Integer.valueOf(((String) data.getXValue()).split("-")[1])); - DayEpoch de = myme.getDay(day); - List afs = Collections.EMPTY_LIST; - if (de != null) { - afs = de.getEvents(); - } else { - logger.log(Level.SEVERE, "There were no events for the clicked-on day."); - } + public void run() { final FsContentRootNode d = new FsContentRootNode("Test Root", afs); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - dataResult.setNode(d); - } - }); - + dataResult.setNode(d); //set result viewer title path with the current date - String dateString = ye.getYear() + "-" + (1+me.getMonthInt()) + "-" + + de.dayNum; + String dateString = ye.getYear() + "-" + (1 + me.getMonthInt()) + "-" + +de.dayNum; dataResult.setPath(dateString); } }); + + + } + }); } bc.autosize(); bc.setPrefWidth(Width_Frame); @@ -487,8 +547,9 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, * Section for Utility functions */ /** - * - * @param mon The month to convert. Must be minimum 4 characters long "February" and "Febr" are acceptable. + * + * @param mon The month to convert. Must be minimum 4 characters long + * "February" and "Febr" are acceptable. * @return The integer value of the month. February = 1, July = 6 */ private static int monthStringToInt(String mon) { @@ -505,8 +566,10 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, /** * Used for finding the proper month in a list of available months - * @param lst The list of months to search through. It is assumed that the desired match is in this list. - * @param match The month, in integer format, to retrieve. + * + * @param lst The list of months to search through. It is assumed that the + * desired match is in this list. + * @param match The month, in integer format, to retrieve. * @return The month epoch as specified by match. */ private static MonthEpoch findMonth(List lst, int match) { @@ -518,10 +581,12 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, return null; } - /** + /** * Used for finding the proper year in a list of available years - * @param lst The list of years to search through. It is assumed that the desired match is in this list. - * @param match The year to retrieve. + * + * @param lst The list of years to search through. It is assumed that the + * desired match is in this list. + * @param match The year to retrieve. * @return The year epoch as specified by match. */ private static YearEpoch findYear(List lst, int match) { @@ -538,7 +603,7 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, String prop = evt.getPropertyName(); if (prop.equals(Case.CASE_ADD_IMAGE)) { if (jf != null && !jf.isVisible()) { - // change the lastObjectId to trigger a reparse of mactime data + // change the lastObjectId to trigger a reparse of mactime barData ++lastObjectId; return; } @@ -557,13 +622,13 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, jf.dispose(); jf = null; } - + data = null; } } - + private void clearMactimeData() { - // get rid of the old data + // get rid of the old barData data = null; // get rid of the mactime file @@ -571,15 +636,16 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, mactimeFile.delete(); // close the jframe - jf.setVisible(false); - jf.dispose(); + if (jf != null) { + jf.setVisible(false); + jf.dispose(); + jf = null; + } // remove ourself as change listener on Case - Case currcase = Case.getCurrentCase(); - if (currcase != null) { - currcase.removePropertyChangeListener(this); - listeningToAddImage = false; - } + Case.removePropertyChangeListener(this); + listeningToAddImage = false; + } /* @@ -587,6 +653,7 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, * All of those are Epochs. */ abstract class Epoch { + abstract public int getNumFiles(); } @@ -594,15 +661,15 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, private int year; private List months = new ArrayList<>(); - + YearEpoch(int year) { this.year = year; } - + public int getYear() { return year; } - + @Override public int getNumFiles() { int size = 0; @@ -611,10 +678,10 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, } return size; } - + public MonthEpoch getMonth(int monthNum) { MonthEpoch month = null; - for (MonthEpoch me :months) { + for (MonthEpoch me : months) { if (me.getMonthInt() == monthNum) { month = me; break; @@ -622,7 +689,7 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, } return month; } - + public void add(AbstractFile af, int month, int day) { // see if this month is in the list MonthEpoch monthEpoch = null; @@ -632,12 +699,12 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, break; } } - + if (monthEpoch == null) { monthEpoch = new MonthEpoch(month); months.add(monthEpoch); } - + // add the file the the MonthEpoch object monthEpoch.add(af, day); } @@ -651,17 +718,17 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, MonthEpoch(int month) { this.month = month; } - + public int getMonthInt() { return month; } - + public int getTotalNumDays(int year) { Calendar cal = Calendar.getInstance(); cal.set(year, month, 1); return cal.getActualMaximum(Calendar.DAY_OF_MONTH); } - + @Override public int getNumFiles() { int numFiles = 0; @@ -670,7 +737,7 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, } return numFiles; } - + public DayEpoch getDay(int dayNum) { DayEpoch de = null; for (DayEpoch d : days) { @@ -681,7 +748,7 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, } return de; } - + public void add(AbstractFile af, int day) { DayEpoch dayEpoch = null; for (DayEpoch de : days) { @@ -690,12 +757,12 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, break; } } - + if (dayEpoch == null) { dayEpoch = new DayEpoch(day); days.add(dayEpoch); } - + dayEpoch.add(af); } @@ -718,20 +785,20 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, private List files = new ArrayList<>(); int dayNum = 0; //Day of the month this Epoch represents, 1 indexed: 28=28. - + DayEpoch(int dayOfMonth) { this.dayNum = dayOfMonth; } - + public int getDayInt() { return dayNum; } - + @Override public int getNumFiles() { return files.size(); } - + public void add(AbstractFile af) { files.add(af); } @@ -743,37 +810,44 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, // The node factories used to make lists of files to send to the result viewer private class FsContentNodeChildFactory extends ChildFactory { + List l; + FsContentNodeChildFactory(List l) { this.l = l; } + @Override protected boolean createKeys(List list) { list.addAll(l); return true; } + @Override protected Node createNodeForKey(AbstractFile file) { Node wrapped; - if (file.isDir()) { + if (file.isDir()) { wrapped = new DirectoryNode((Directory) file, false); } else { - wrapped = new FileNode((File) file, false); + wrapped = new FileNode((File) file, false); } return new FilterNodeLeaf(wrapped); } } private class FsContentRootNode extends DisplayableItemNode { + FsContentRootNode(String NAME, List l) { super(Children.create(new FsContentNodeChildFactory(l), true)); super.setName(NAME); super.setDisplayName(NAME); } + @Override public DisplayableItemNode.TYPE getDisplayableItemNodeType() { return DisplayableItemNode.TYPE.CONTENT; } + @Override public T accept(DisplayableItemNodeVisitor v) { return null; @@ -791,7 +865,6 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, } scan.useDelimiter(","); scan.nextLine(); // skip the header line - SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase(); int prevYear = -1; YearEpoch ye = null; @@ -823,20 +896,25 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, ye.add(file, month, day); } } - + scan.close(); return years; } + /** + * Crate a body file and return its path or null if error + * + * @return absolute path string or null if error + */ private String makeBodyFile() { // Setup timestamp DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss"); Date date = new Date(); String datenotime = dateFormat.format(date); - Case currentCase = Case.getCurrentCase(); - SleuthkitCase skCase = currentCase.getSleuthkitCase(); + final Case currentCase = Case.getCurrentCase(); + // Get report path String bodyFilePath = moduleDir.getAbsolutePath() + java.io.File.separator + currentCase.getName() + "-" + datenotime + ".txt"; @@ -845,34 +923,39 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, String filesAndDirs = "type = '" + TskData.TSK_DB_FILES_TYPE_ENUM.FS.getFileType() + "' " + "AND name != '.' " + "AND name != '..'"; - List fs = Collections.EMPTY_LIST; + List fs = null; try { fs = skCase.findFilesWhere(filesAndDirs); } catch (TskCoreException ex) { - Exceptions.printStackTrace(ex); + logger.log(Level.SEVERE, "Error querying image files to make a body file: " + bodyFilePath, ex); + return null; } // Loop files and write info to report + FileWriter fileWriter = null; + try { + fileWriter = new FileWriter(bodyFilePath, true); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error creating output stream to write body file to: " + bodyFilePath, ex); + return null; + } + BufferedWriter out = null; try { - out = new BufferedWriter(new FileWriter(bodyFilePath, true)); - } catch (IOException ex) { - logger.log(Level.WARNING, "Could not create new BufferedWriter for body file.", ex); - } - for (FsContent file : fs) { - try { + out = new BufferedWriter(fileWriter); + for (FsContent file : fs) { + // try { // MD5|name|inode|mode_as_string|ObjId|GID|size|atime|mtime|ctime|crtime - //out = new BufferedWriter(new FileWriter(bodyFilePath, true)); - if (file.getMd5Hash() != null) { out.write(file.getMd5Hash()); } out.write("|"); - String path = ""; + String path = null; try { path = file.getUniquePath(); } catch (TskCoreException e) { - logger.log(Level.WARNING, "Failed to get the unique path.", e); + logger.log(Level.SEVERE, "Failed to get the unique path of: " + file + " and writing body file.", e); + return null; } out.write(path); @@ -899,18 +982,21 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, out.write("|"); out.write(Long.toString(file.getCrtime())); out.write("\n"); - } catch (IOException ex) { - logger.log(Level.WARNING, "Probelm while trying to write data to the body file.", ex); - break; + } + } catch (IOException ex) { + logger.log(Level.WARNING, "Error while trying to write data to the body file.", ex); + return null; + } finally { + if (out != null) { + try { + out.flush(); + out.close(); + } catch (IOException ex1) { + logger.log(Level.WARNING, "Could not flush and/or close body file.", ex1); + } } } - - try { - out.flush(); - out.close(); - } catch (IOException ex1) { - logger.log(Level.WARNING, "Could not flush and/or close body file.", ex1); - } + return bodyFilePath; } @@ -948,15 +1034,22 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, @Override public void performAction() { + initTimeline(); + } + + private void initTimeline() { if (!Case.existsCurrentCase()) { return; } + final Case currentCase = Case.getCurrentCase(); + skCase = currentCase.getSleuthkitCase(); + try { - if (Case.getCurrentCase().getImages().isEmpty()) { + if (currentCase.getImages().isEmpty()) { logger.log(Level.INFO, "Error creating timeline, there are no images to parse"); } else { - + if (IngestManager.getDefault().isIngestRunning()) { int answer = JOptionPane.showConfirmDialog(new JFrame(), "You are trying to generate a timeline before " @@ -967,48 +1060,48 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, return; } } - + logger.log(Level.INFO, "Beginning generation of timeline"); - + // if the timeline window is already open, do nothing if (jf != null && jf.isVisible()) { return; } - + Platform.setImplicitExit(false); - + // listen for case changes (specifically images being added). - Case currcase = Case.getCurrentCase(); - if (currcase != null && !listeningToAddImage) { - currcase.addPropertyChangeListener(this); + if (Case.isCaseOpen() && !listeningToAddImage) { + Case.addPropertyChangeListener(this); listeningToAddImage = true; } - - // create the modal dialog - SwingUtilities.invokeLater(new Runnable () { + + // create the modal progressDialog + SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - dialog = new TimelineProgressDialog(jf, true); - dialog.setVisible(true); + progressDialog = new TimelineProgressDialog(jf, true); + progressDialog.setVisible(true); } }); // initialize mactimeFileName - mactimeFileName = Case.getCurrentCase().getName() + "-MACTIME.txt"; - - // see if data has been added to the database since the last + mactimeFileName = currentCase.getName() + "-MACTIME.txt"; + + // see if barData has been added to the database since the last // time timeline ran - long objId = Case.getCurrentCase().getSleuthkitCase().getLastObjectId(); + long objId = skCase.getLastObjectId(); if (objId != lastObjectId && lastObjectId != -1) { clearMactimeData(); } lastObjectId = objId; - customizeSwing(); customize(); } } catch (TskCoreException ex) { - Exceptions.printStackTrace(ex); + logger.log(Level.SEVERE, "Error when generating timeline, ", ex); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Unexpected error when generating timeline, ", ex); } } @@ -1026,154 +1119,4 @@ public class Simile2 extends CallableSystemAction implements Presenter.Toolbar, public boolean asynchronous() { return false; } - // - /** - * Old Functions * - */ - // private BarChart createEventsByYear(final YearEpoch ye) { - // final CategoryAxis xAxis = new CategoryAxis(); - // final NumberAxis yAxis = new NumberAxis(); - // final int maxEvents = ye.max; - // final int maxNumDays = ye.numDays; - // xAxis.setLabel("Day of Year"); - // yAxis.setLabel("Number of Events"); - // ObservableList> seriesList = FXCollections.observableArrayList(); - // for (final MonthEpoch me : ye.months) { - // BarChart.Series series = new BarChart.Series(makeObservableListByMonthAllDays(me)); - // series.setName(me.getMonthName() + " " + me.year); - // seriesList.add(series); - // } - // final BarChart bc = new BarChart(xAxis, yAxis, seriesList); - // for (int i = 0; i < bc.getData().size(); i++) { - // for (final BarChart.Data data : bc.getData().get(i).getData()) { - // data.getNode().setScaleX(240); - // data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED, - // new EventHandler() { - // @Override - // public void handle(MouseEvent e) { - // final FsContentRootNode d = new FsContentRootNode("Test Year Nodes", (((MonthEpoch) data.getExtraValue()).getDays().get(Integer.valueOf(((String) data.getXValue()).split("-")[1]))).getEvents()); - // SwingUtilities.invokeLater(new Runnable() { - // @Override - // public void run() { - // dataResult.setNode(d); - // } - // }); - // } - // }); - // } - // } - // bc.autosize(); - // bc.setPrefWidth(63 * maxNumDays); - // //bc.setScaleX(1.25); - // //bc.setMinSize(30 * maxNumDays, 4 * maxEvents); // 40 * numDays in year, 4 * maxEvents - // //bc.setMaxSize(30 * maxNumDays, 4 * maxEvents); //Width, Height - // return bc; - // } - /* - * the small 12-month chart - */ - // private BarChart createMonthBarChartFromYear(final YearEpoch ye) { - // final CategoryAxis xAxis = new CategoryAxis(); - // final NumberAxis yAxis = new NumberAxis(); - // final int numMonths = 12; - // final int maxEvents = ye.max; - // xAxis.setLabel("Month (" + ye.year + ")"); - // yAxis.setLabel("Number of Events"); - // ObservableList> bcData = FXCollections.observableArrayList(); - // - // BarChart.Series se = new BarChart.Series(); - // for (final MonthEpoch me : ye.months) { - // se.getData().add(new BarChart.Data(me.getMonthName(), me.total)); - // } - // bcData.add(se); - // - // - // final BarChart bc = new BarChart(xAxis, yAxis, bcData);//bcData); - // for (int i = 0; i < numMonths; i++) { - // for (final BarChart.Data data : bc.getData().get(0).getData()) { - // data.getNode().setScaleX(26); - // data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED, - // new EventHandler() { - // @Override - // public void handle(MouseEvent e) { - // - // PlatformImpl.startup(new Runnable() { - // @Override - // public void run() { - // chart_Events = createEventsByMonth(findMonth(ye.months, month_StringtoInt((String) data.getXValue()))); - // scroll_Events.setContent(chart_Events); - // } - // }); - // - // } - // }); - // } - // } - // - // bc.setPrefHeight(Height_PieBoxes); - // bc.setPrefWidth(Width_PieBoxes); - // bc.setLegendVisible(false); - // return bc; - // } - // boolean checkCache(List epochs, int match) { - // for (Epoch e : epochs) { - // if (e.year == match) { - // return true; - // } - // } - // return false; - // } - // - // YearEpoch getFromCache(List epochs, int match) { - // for (YearEpoch e : epochs) { - // if (e.year == match) { - // return e; - // } - // } - // return null; - // } - // private PieChart initYearChart() { - // ObservableList pieChartData = - // FXCollections.observableArrayList(); - // - // double totalEvents = 0.0; - // String sep = java.io.File.separator; - // java.io.File mactime = new java.io.File("C:" + sep + "Users" + sep + "nflower" + sep + "Downloads" + sep + "kanarazu-12-21-2012-11-15-46-mactime.txt"); - // List ls = parseMacTime(mactime); - // - // for (int i = 0; i < ls.size(); i++) { - // cachedYears.add(ls.get(i)); - // totalEvents += ls.get(i).total; - // } - // - // for (YearEpoch e : ls) { - // PieChart.Data d = new PieChart.Data(String.valueOf(e.year), (e.total / totalEvents) * 100); - // pieChartData.add(d); - // } - // final PieChart chart = new PieChart(pieChartData); - // chart.setTitle("Years with Activity"); - // for (int i = 0; i < chart.getData().size(); i++) { - // final PieChart.Data data = chart.getData().get(i); - // final YearEpoch y = findYear(ls, Integer.valueOf(data.getName())); - // data.getNode().setUserData(y); - // data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED, - // new EventHandler() { - // @Override - // public void handle(MouseEvent e) { - // chart_Months = createMonthBarChartFromYear(((YearEpoch) data.getNode().getUserData())); - // scroll_Months.setContent(chart_Months); - // - // chart_Events = createEventsByYear(y); - // scroll_Events.setContent(chart_Events); - // chart_Events.getBoundsInParent(); - // } - // }); - // - // } - // chart.setLegendVisible(false); - // chart.setPrefHeight(Height_PieBoxes / 1.3); - // chart.setPrefWidth(Width_PieBoxes / 1.1); - // return chart; - // } - // } diff --git a/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineJPanel.form b/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineJPanel.form deleted file mode 100644 index 57be0d6614..0000000000 --- a/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineJPanel.form +++ /dev/null @@ -1,28 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineJPanel.java b/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineJPanel.java deleted file mode 100644 index 04e5bdf459..0000000000 --- a/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineJPanel.java +++ /dev/null @@ -1,835 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package org.sleuthkit.autopsy.timeline; - -import com.sun.javafx.application.PlatformImpl; -import java.awt.Component; -import java.awt.Dimension; -import java.io.BufferedWriter; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileWriter; -import java.io.IOException; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.text.DateFormat; -import java.text.DateFormatSymbols; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.Scanner; -import java.util.Stack; -import java.util.logging.Level; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.embed.swing.JFXPanel; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.geometry.Pos; -import javafx.scene.Group; -import javafx.scene.Scene; -import javafx.scene.chart.BarChart; -import javafx.scene.chart.CategoryAxis; -import javafx.scene.chart.NumberAxis; -import javafx.scene.control.Button; -import javafx.scene.control.ComboBox; -import javafx.scene.control.Label; -import javafx.scene.control.ScrollPane; -import javafx.scene.input.MouseButton; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.HBox; -import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import javax.swing.BoxLayout; -import javax.swing.JFrame; -import javax.swing.JPanel; -import javax.swing.SwingUtilities; -import org.openide.modules.InstalledFileLocator; -import org.openide.nodes.ChildFactory; -import org.openide.nodes.Children; -import org.openide.nodes.Node; -import org.sleuthkit.autopsy.casemodule.Case; -import org.sleuthkit.autopsy.corecomponents.DataContentPanel; -import org.sleuthkit.autopsy.corecomponents.DataResultPanel; -import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.PlatformUtil; -import org.sleuthkit.autopsy.datamodel.DirectoryNode; -import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; -import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; -import org.sleuthkit.autopsy.datamodel.FileNode; -import org.sleuthkit.autopsy.recentactivity.JavaSystemCaller; -import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.Directory; -import org.sleuthkit.datamodel.File; -import org.sleuthkit.datamodel.FsContent; -import org.sleuthkit.datamodel.SleuthkitCase; -import org.sleuthkit.datamodel.TskCoreException; -import org.sleuthkit.datamodel.TskData; - -/** - * - */ -public class TimelineJPanel extends javax.swing.JPanel { - - private static final Logger logger = Logger.getLogger(TimelineJPanel.class.getName()); - private final java.io.File macRoot = InstalledFileLocator.getDefault().locate("mactime", TimelineJPanel.class.getPackage().getName(), false); - //private JFrame jf; //frame for holding all the elements - private Group group_Charts; //Orders the charts - private Scene scene_Charts; //Displays the charts - private HBox hBox_Charts; //Holds the navigation buttons in horiztonal fashion. - private VBox vBox_FX; //Holds the JavaFX Elements in vertical fashion. - private JFXPanel panel_Charts; //FX panel to hold the group - private BarChart chart_Events; //Yearly/Monthly events - Bar chart - private ScrollPane scroll_Events; //Scroll Panes for dealing with oversized an oversized chart - private final int Height_Frame = 850; //Sizing constants - private final int Width_Frame = 1300; - private Button button_DrillUp; //Navigation buttons - private Button button_DrillDown; - private Button button_Reset; - private Button button_Go; - private ComboBox dropdown_SelectYears; //Dropdown box for selecting years. Useful when the charts' scale means some years are unclickable, despite having events. - private final Stack stack_PrevCharts = new Stack(); //Stack for storing drill-up information. - private BarChart chart_TopLevel; //the topmost chart, used for resetting to default view. - private DataResultPanel dataResultPanel; - - /** - * Creates new form Timeline - */ - public TimelineJPanel() { - initComponents(); - //customize(); - } - - public void setDataResultPanel(DataResultPanel dataResultPanel) { - this.dataResultPanel = dataResultPanel; - } - - public void initialize() { - customize(); - } - - /** - * 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 - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 400, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 300, Short.MAX_VALUE) - ); - }// //GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables - // End of variables declaration//GEN-END:variables - - private void customize() { - - //Making the main frame * - //jf = new JFrame(Case.getCurrentCase().getName() + " - Autopsy Timeline"); - //jf.setSize(Width_Frame, Height_Frame); //(Width, Height) - - //JPanels are used as the cohesive glue that binds everything together.*/ - //The chartJpanel holds the chart, - //aligned vertically (Y_AXIS) - final JPanel chartJPanel = new JPanel(); - chartJPanel.setLayout(new BoxLayout(chartJPanel, BoxLayout.Y_AXIS)); - - //ComboJPanel holds both of the above JPanels together, - //aligned vertically (Y_AXIS) - final JPanel comboJPanel = new JPanel(); - comboJPanel.setLayout(new BoxLayout(comboJPanel, BoxLayout.Y_AXIS)); - - //JavaFX thread - //JavaFX components MUST be run in the JavaFX thread, otherwise massive amounts of exceptions will be thrown and caught. Liable to freeze up and crash. - //Components can be declared whenever, but initialization and manipulation must take place here. - PlatformImpl.startup(new Runnable() { - @Override - public void run() { - System.out.println("JavaFX thread running . . ."); - panel_Charts = new JFXPanel(); - group_Charts = new Group(); - scene_Charts = new Scene(group_Charts, Width_Frame, Math.round(Height_Frame / .75)); //Width, Height - vBox_FX = new VBox(5); - vBox_FX.setAlignment(Pos.BOTTOM_CENTER); - hBox_Charts = new HBox(10); - hBox_Charts.setAlignment(Pos.BOTTOM_CENTER); - - //Initializing default values for the scroll pane - scroll_Events = new ScrollPane(); - scroll_Events.setPrefSize(Width_Frame, Math.round(Height_Frame / .75)); //Width, Height - scroll_Events.setContent(null); //Needs some content, otherwise it crashes - - //mactime file placeholder, goes to pre-grenerated one on my desktop - String bodyFilePath = makeBodyFile(); - java.io.File mactimePath = new java.io.File(makeMacTime(bodyFilePath)); - //java.io.File mactime = new java.io.File("C:" + java.io.File.separator + "Users" + java.io.File.separator + "nflower" + java.io.File.separator + "Downloads" + java.io.File.separator + "kanarazu-12-21-2012-11-15-46-mactime.txt"); - final List lsye = parseMacTime(mactimePath); //The sum total of the mactime parsing. YearEpochs contain everything you need to make a timeline. - - //Making a dropdown box to select years. - List lsi = new ArrayList(); //List is in the format of {Year : Number of Events}, used for selecting from the dropdown. - for (YearEpoch ye : lsye) { - lsi.add(ye.year + " : " + ye.getNumFiles()); - } - ObservableList listSelect = FXCollections.observableArrayList(lsi); - dropdown_SelectYears = new ComboBox(listSelect); - - //Buttons for navigating up and down the timeline - button_DrillDown = new Button("Drill down"); - button_DrillDown.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent e) { - //Placeholder, does nothing. Need to store a chart_LastSelected or something - } - }); - button_DrillUp = new Button("Drill up"); - button_DrillUp.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent e) { - BarChart bc; - if (stack_PrevCharts.size() == 0) { - bc = chart_TopLevel; - } else { - bc = stack_PrevCharts.pop(); - } - chart_Events = bc; - scroll_Events.setContent(chart_Events); - } - }); - button_Reset = new Button("Reset"); - button_Reset.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent e) { - stack_PrevCharts.clear(); - chart_Events = chart_TopLevel; - scroll_Events.setContent(chart_Events); - } - }); - button_Go = new Button("►"); - button_Go.setOnAction(new EventHandler() { - @Override - public void handle(ActionEvent e) { - if (dropdown_SelectYears.getValue() != null) { - chart_Events = createMonthsWithDrill(findYear(lsye, Integer.valueOf(dropdown_SelectYears.getValue().split(" ")[0]))); - scroll_Events.setContent(chart_Events); - } - } - }); - - //Adding things to the V and H boxes. - //hBox_Charts stores the pseudo menu bar at the top of the timeline. |Drill Up|Drill Down|Reset|View Year: [Select Year]|►| - hBox_Charts.getChildren().addAll(button_DrillUp, button_DrillDown, button_Reset, new Label("View Year:"), dropdown_SelectYears, button_Go); - vBox_FX.getChildren().addAll(hBox_Charts, scroll_Events); //FxBox_V holds things in a visual stack. - group_Charts.getChildren().add(vBox_FX); //Adding the FxBox to the group. Groups make things easier to manipulate without having to update a hundred things every change. - panel_Charts.setScene(scene_Charts); - - - panel_Charts.setAlignmentX(Component.LEFT_ALIGNMENT); - - chartJPanel.add(panel_Charts); - chartJPanel.setAlignmentX(Component.LEFT_ALIGNMENT); - comboJPanel.add(chartJPanel); - - chart_TopLevel = createYearChartWithDrill(lsye); - chart_Events = chart_TopLevel; - scroll_Events.setContent(chart_Events); - - // set bg color of comboJPanel - comboJPanel.setBackground(java.awt.Color.red); - comboJPanel.setVisible(true); - - setLayout(new BoxLayout(TimelineJPanel.this, BoxLayout.PAGE_AXIS)); - add(comboJPanel); - //setVisible(true); - } - }); - } - - private String makeBodyFile() { - // Setup timestamp - DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss"); - Date date = new Date(); - String datenotime = dateFormat.format(date); - - Case currentCase = Case.getCurrentCase(); - SleuthkitCase skCase = currentCase.getSleuthkitCase(); - // Get report path - String bodyFilePath = currentCase.getCaseDirectory() + java.io.File.separator + "temp" - + java.io.File.separator + currentCase.getName() + "-" + datenotime + ".txt"; - - // Run query to get all files - ResultSet rs = null; - try { - // exclude non-fs files/dirs and . and .. files - rs = skCase.runQuery("SELECT * FROM tsk_files " - + "WHERE type = '" + TskData.TSK_DB_FILES_TYPE_ENUM.FS.getFileType() + "' " - + "AND name != '.' " - + "AND name != '..'"); - List fs = skCase.resultSetToFsContents(rs); - // Loop files and write info to report - for (FsContent file : fs) { - - BufferedWriter out = null; - try { - // MD5|name|inode|mode_as_string|ObjId|GID|size|atime|mtime|ctime|crtime - out = new BufferedWriter(new FileWriter(bodyFilePath, true)); - - if (file.getMd5Hash() != null) { - out.write(file.getMd5Hash()); - } - out.write("|"); - if (file.getUniquePath() != null) { - out.write(file.getUniquePath()); - } - out.write("|"); - out.write(Long.toString(file.getMetaAddr())); - out.write("|"); - String modeString = file.getModesAsString(); - if (modeString != null) { - out.write(modeString); - } - out.write("|"); - out.write(Long.toString(file.getId())); - out.write("|"); - out.write(Long.toString(file.getGid())); - out.write("|"); - out.write(Long.toString(file.getSize())); - out.write("|"); - out.write(Long.toString(file.getAtime())); - out.write("|"); - out.write(Long.toString(file.getMtime())); - out.write("|"); - out.write(Long.toString(file.getCtime())); - out.write("|"); - out.write(Long.toString(file.getCrtime())); - out.write("\n"); - } catch (IOException ex) { - logger.log(Level.WARNING, "Could not write the temp body file report.", ex); - } finally { - try { - out.flush(); - out.close(); - } catch (IOException ex) { - logger.log(Level.WARNING, "Could not flush and close the BufferedWriter.", ex); - } - } - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "Failed to get all file information.", ex); - } catch (TskCoreException ex) { - logger.log(Level.WARNING, "Failed to get the unique path.", ex); - } finally { - try {// Close the query - if (rs != null) { - skCase.closeRunQuery(rs); - } - } catch (SQLException ex) { - logger.log(Level.WARNING, "Failed to close the query.", ex); - } - } - return bodyFilePath; - } - - private String makeMacTime(String pathToBodyFile) { - String macpath = ""; - final String machome = macRoot.getAbsolutePath(); - if (PlatformUtil.isWindowsOS()) { - macpath = machome + java.io.File.separator + "mactime.exe"; - } else { - macpath = "perl " + machome + java.io.File.separator + "mactime.pl"; - } - String macfile = Case.getCurrentCase().getCaseDirectory() + java.io.File.separator + "temp" + java.io.File.separator + Case.getCurrentCase().getName() + "-MACTIME.txt"; - String command = macpath + " -b " + "\"" + pathToBodyFile + "\"" + " -d " + " -y " + ">" + "\"" + macfile + "\""; - try { - JavaSystemCaller.Exec.execute("\"" + command + "\""); - return macfile; - } catch (InterruptedException ie) { - logger.log(Level.WARNING, "Mactime process was interrupted by user", ie); - } catch (IOException ioe) { - logger.log(Level.SEVERE, "Could not create mactime file, encountered error ", ioe); - } - return null; - } - - private List parseMacTime(java.io.File f) { - //System.out.println("Parsing mactime file."); - List years = new ArrayList<>(); - Scanner scan; - try { - scan = new Scanner(new FileInputStream(f)); - } catch (FileNotFoundException ex) { - logger.log(Level.SEVERE, "Error: could not find mactime file.", ex); - return years; - } - scan.useDelimiter(","); - scan.nextLine(); // skip the header line - SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase(); - - int prevYear = -1; - YearEpoch ye = null; - while (scan.hasNextLine()) { - String[] s = scan.nextLine().split(","); //1999-02-08T11:08:08Z, 78706, m..b, rrwxrwxrwx, 0, 0, 8355, /img... - String[] datetime = s[0].split("T"); //{1999-02-08, 11:08:08Z} - String[] date = datetime[0].split("-"); // {1999, 02, 08} - int year = Integer.valueOf(date[0]); - int month = Integer.valueOf(date[1]) - 1; //Months are zero indexed: 1 = February, 6 = July, 11 = December - int day = Integer.valueOf(date[2]); //Days are 1 indexed - long ObjId = Long.valueOf(s[4]); - - // when the year changes, create and add a new YearEpoch object to the list - if (year != prevYear) { - ye = new YearEpoch(year); - years.add(ye); - prevYear = year; - } - - // create and add the file - AbstractFile file; - try { - file = skCase.getAbstractFileById(ObjId); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Could not find a file with ID " + ObjId, ex); - continue; - } - ye.add(file, month, day); - } - - return years; - } - - /* - * The backbone of the timeline functionality, years are split into months, months into days, and days contain the events of that given day. - * All of those are Epochs. - */ - abstract class Epoch { - abstract public int getNumFiles(); - } - - private class YearEpoch extends Epoch { - - private int year; - private List months = new ArrayList<>(); - - YearEpoch(int year) { - this.year = year; - } - - public int getYear() { - return year; - } - - @Override - public int getNumFiles() { - int size = 0; - for (MonthEpoch me : months) { - size += me.getNumFiles(); - } - return size; - } - - public void add(AbstractFile af, int month, int day) { - // see if this month is in the list - MonthEpoch monthEpoch = null; - for (MonthEpoch me : months) { - if (me.getMonthInt() == month) { - monthEpoch = me; - break; - } - } - - if (monthEpoch == null) { - monthEpoch = new MonthEpoch(month); - months.add(monthEpoch); - } - - // add the file the the MonthEpoch object - monthEpoch.add(af, day); - } - } - - private class MonthEpoch extends Epoch { - - private int month; //Zero-indexed: June = 5, August = 7, etc - private List days = new ArrayList<>(); //List of DayEpochs in this month, max 31 - - MonthEpoch(int month) { - this.month = month; - } - - public int getMonthInt() { - return month; - } - - public int getTotalNumDays(int year) { - Calendar cal = Calendar.getInstance(); - cal.set(year, month, 1); - return cal.getActualMaximum(Calendar.DAY_OF_MONTH); - } - - @Override - public int getNumFiles() { - int numFiles = 0; - for (DayEpoch de : days) { - numFiles += de.getNumFiles(); - } - return numFiles; - } - - public DayEpoch getDay(int dayNum) { - DayEpoch de = null; - for (DayEpoch d : days) { - if (d.dayNum == dayNum) { - de = d; - break; - } - } - return de; - } - - public void add(AbstractFile af, int day) { - DayEpoch dayEpoch = null; - for (DayEpoch de : days) { - if (de.getDayInt() == day) { - dayEpoch = de; - break; - } - } - - if (dayEpoch == null) { - dayEpoch = new DayEpoch(day); - days.add(dayEpoch); - } - - dayEpoch.add(af); - } - - /** - * Returns the month's name in String format, e.g., September, July, - */ - String getMonthName() { - return new DateFormatSymbols().getMonths()[month]; - } - - /** - * @return the list of days in this month - */ - List getDays() { - return this.days; - } - } - - private class DayEpoch extends Epoch { - - private List files = new ArrayList<>(); - int dayNum = 0; //Day of the month this Epoch represents, 1 indexed: 28=28. - - DayEpoch(int dayOfMonth) { - this.dayNum = dayOfMonth; - } - - public int getDayInt() { - return dayNum; - } - - public int getNumFiles() { - return files.size(); - } - - public void add(AbstractFile af) { - files.add(af); - } - - List getEvents() { - return this.files; - } - } - - /* - * Displays a chart with events from one year only, separated into 1-month chunks. - * Always 12 per year, empty months are represented by no bar. - */ - private BarChart createMonthsWithDrill(final YearEpoch ye) { - - final CategoryAxis xAxis = new CategoryAxis(); - final NumberAxis yAxis = new NumberAxis(); - xAxis.setLabel("Month (" + ye.year + ")"); - yAxis.setLabel("Number of Events"); - ObservableList> bcData = FXCollections.observableArrayList(); - - BarChart.Series se = new BarChart.Series(); - for (final MonthEpoch me : ye.months) { - se.getData().add(new BarChart.Data(me.getMonthName(), me.getNumFiles())); //Adding new data at {X-pos, Y-Pos} - } - bcData.add(se); - final BarChart bc = new BarChart(xAxis, yAxis, bcData); - - for (int i = 0; i < 12; i++) { - for (final BarChart.Data data : bc.getData().get(0).getData()) { - //Note: - // All the charts of this package have a problem where when the chart gets below a certain pixel ratio, the data stops drawing. The axes and the labels remain, - // But the actual chart data is invisible, unclickable, and unrendered. To partially compensate for that, data.getNode() can be manually scaled up to increase visibility. - // Sometimes I've had it jacked up to as much as x2400 just to see a sliver of information. - // But that doesn't work all the time. Adding it to a scrollpane and letting the user scroll up and down to view the chart is the other workaround. Both of these fixes suck. - - data.getNode().setScaleX(.5); - data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED, - new EventHandler() { - @Override - public void handle(MouseEvent e) { - if (e.getButton().equals(MouseButton.PRIMARY)) { - if (e.getClickCount() == 2) { - PlatformImpl.startup(new Runnable() { - @Override - public void run() { - chart_Events = createEventsByMonth(findMonth(ye.months, month_StringtoInt((String) data.getXValue())), ye); - scroll_Events.setContent(chart_Events); - } - }); - } - } - } - }); - } - } - - bc.autosize(); - bc.setPrefWidth(Width_Frame); - bc.setLegendVisible(false); - stack_PrevCharts.push(bc); - return bc; - } - - /* - * Displays a chart with events from one month only. - * Up to 31 days per month, as low as 28 as determined by the specific MonthEpoch - */ - private BarChart createEventsByMonth(final MonthEpoch me, final YearEpoch ye) { - final CategoryAxis xAxis = new CategoryAxis(); - final NumberAxis yAxis = new NumberAxis(); - xAxis.setLabel("Day of Month"); - yAxis.setLabel("Number of Events"); - ObservableList bcData = makeObservableListByMonthAllDays(me, ye.getYear()); - BarChart.Series series = new BarChart.Series(bcData); - series.setName(me.getMonthName() + " " + ye.getYear()); - - ObservableList> ol = FXCollections.observableArrayList(series); - - final BarChart bc = new BarChart(xAxis, yAxis, ol); - for (final BarChart.Data data : bc.getData().get(0).getData()) { - //data.getNode().setScaleX(2); - data.getNode().addEventHandler(MouseEvent.MOUSE_PRESSED, - new EventHandler() { - MonthEpoch myme = me; - - @Override - public void handle(MouseEvent e) { - int day = (Integer.valueOf(((String) data.getXValue()).split("-")[1])); - DayEpoch de = myme.getDay(day); - List afs = Collections.EMPTY_LIST; - if (de != null) { - afs = de.getEvents(); - } else { - logger.log(Level.SEVERE, "There were no events for the clicked-on day."); - } - final FsContentRootNode d = new FsContentRootNode("Test Root", afs); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - dataResultPanel.setNode(d); - } - }); - } - }); - } - bc.autosize(); - bc.setPrefWidth(Width_Frame); - return bc; - } - - /** - * - * @param mon The month to convert. Must be minimum 4 characters long "February" and "Febr" are acceptable. - * @return The integer value of the month. February = 1, July = 6 - */ - private int month_StringtoInt(String mon) { - try { - Date date = new SimpleDateFormat("MMMM", Locale.ENGLISH).parse(mon); - Calendar cal = Calendar.getInstance(); - cal.setTime(date); - return cal.get(Calendar.MONTH); - } catch (ParseException ex) { - logger.log(Level.WARNING, "Unable to convert string " + mon + " to integer", ex); - return -1; - } - } - - /** - * Used for finding the proper month in a list of available months - * @param lst The list of months to search through. It is assumed that the desired match is in this list. - * @param match The month, in integer format, to retrieve. - * @return The month epoch as specified by match. - */ - private MonthEpoch findMonth(List lst, int match) { - for (MonthEpoch e : lst) { - if (e.month == match) { - return e; - } - } - return null; - } - - private ObservableList makeObservableListByMonthAllDays(final MonthEpoch me, int year) { - ObservableList bcData = FXCollections.observableArrayList(); - int totalDays = me.getTotalNumDays(year); - for (int i = 1; i <= totalDays; ++i) { - DayEpoch day = me.getDay(i); - int numFiles = day == null ? 0 : day.getNumFiles(); - BarChart.Data d = new BarChart.Data(me.month + 1 + "-" + i, numFiles); - d.setExtraValue(me); - bcData.add(d); - } - return bcData; - } - - // The node factories used to make lists of files to send to the result viewer - private class FsContentNodeChildFactory extends ChildFactory { - List l; - FsContentNodeChildFactory(List l) { - this.l = l; - } - @Override - protected boolean createKeys(List list) { - list.addAll(l); - return true; - } - @Override - protected Node createNodeForKey(AbstractFile file) { - Node n; - if (file.isDir()) { - n = new DirectoryNode((Directory) file); - } else { - n = new FileNode((File) file) { - - @Override - public boolean isLeafTypeNode() { - return false; - } - - }; - } - return n; - } - } - - private class FsContentRootNode extends DisplayableItemNode { - FsContentRootNode(String NAME, List l) { - super(Children.create(new FsContentNodeChildFactory(l), true)); - super.setName(NAME); - super.setDisplayName(NAME); - } - @Override - public DisplayableItemNode.TYPE getDisplayableItemNodeType() { - return DisplayableItemNode.TYPE.CONTENT; - } - @Override - public T accept(DisplayableItemNodeVisitor v) { - return null; - } - } - - /** - * Used for finding the proper year in a list of available years - * @param lst The list of years to search through. It is assumed that the desired match is in this list. - * @param match The year to retrieve. - * @return The year epoch as specified by match. - */ - private YearEpoch findYear(List lst, int match) { - for (YearEpoch e : lst) { - if (e.year == match) { - return e; - } - } - return null; - } - - /** - * Creates a BarChart with datapoints for all the years from the parsed - * mactime file. - * - * @param allYears The list of years that have data from the mactime file - * @return BarChart scaled to the year level - */ - private BarChart createYearChartWithDrill(final List allYears) { - final CategoryAxis xAxis = new CategoryAxis(); //Axes are very specific types. Categorys are strings. - final NumberAxis yAxis = new NumberAxis(); - final Label l = new Label(""); - l.setStyle("-fx-font: 24 arial;"); - l.setTextFill(Color.AZURE); - xAxis.setLabel("Years"); - yAxis.setLabel("Number of Events"); - //Charts are made up of individual pieces of Chart.Data. In this case, a piece of data is a single bar on the graph. - //Data is packaged into a series, which can be assigned custom colors or styling - //After the series are created, 1 or more series are packaged into a single chart. - ObservableList> bcData = FXCollections.observableArrayList(); - BarChart.Series se = new BarChart.Series(); - for (final YearEpoch ye : allYears) { - se.getData().add(new BarChart.Data(String.valueOf(ye.year), ye.getNumFiles())); - } - bcData.add(se); - - //Note: - // BarChart.Data wraps the Java Nodes class. BUT, until a BarChart.Data gets added to an actual series, it's node is null, and you can perform no operations on it. - // When the Data is added to a series(or a chart? I am unclear on where), a node is automaticaly generated for it, after which you can perform any of the operations it offers. - // In addtion, you are free to set the node to whatever you want. It wraps the most generic Node class. - // But it is for this reason that the chart generating functions have two forloops. I do not believe they can be condensed into a single loop due to the nodes being null until - // an undetermined point in time. - BarChart bc = new BarChart(xAxis, yAxis, bcData); - for (final BarChart.Data data : bc.getData().get(0).getData()) { //.get(0) refers to the BarChart.Series class to work on. There is only one series in this graph, so get(0) is safe. - data.getNode().setScaleX(.5); - data.getNode().addEventHandler(MouseEvent.MOUSE_CLICKED, - new EventHandler() { - @Override - public void handle(MouseEvent e) { - if (e.getButton().equals(MouseButton.PRIMARY)) { - if (e.getClickCount() == 2) { //Checking for a doubleclick - PlatformImpl.startup(new Runnable() { - @Override - public void run() { - BarChart b = createMonthsWithDrill((YearEpoch) findYear(allYears, Integer.valueOf((String) data.getXValue()))); - chart_Events = b; - scroll_Events.setContent(chart_Events); - } - }); - //If a single click, hover a label over the cursor with information about the selection - } else if (e.getClickCount() == 1) { - l.setText(findYear(allYears, Integer.valueOf((String) data.getXValue())).getNumFiles() + " events"); - l.setTranslateX(e.getX()); - l.setTranslateY(e.getY()); - } - } - } - }); - } - - bc.autosize(); //Get an auto height - bc.setPrefWidth(Width_Frame); //but override the width - bc.setLegendVisible(false); //The legend adds too much extra chart space, it's not necessary. - return bc; - - } -} diff --git a/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineProgressDialog.form b/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineProgressDialog.form index 4811094954..0a8a131d09 100644 --- a/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineProgressDialog.form +++ b/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineProgressDialog.form @@ -23,19 +23,27 @@ - - - - + + + + + + + + + + - + - + + + @@ -48,5 +56,7 @@ + + diff --git a/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineProgressDialog.java b/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineProgressDialog.java index ca5499cf1e..99f324b5d3 100644 --- a/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineProgressDialog.java +++ b/Timeline/src/org/sleuthkit/autopsy/timeline/TimelineProgressDialog.java @@ -16,9 +16,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.sleuthkit.autopsy.timeline; +import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import javax.swing.AbstractAction; @@ -29,8 +29,7 @@ import javax.swing.KeyStroke; import org.openide.windows.WindowManager; /** - * - * @author mciver + * Dialog with progress bar that pops up when timeline is being generated */ public class TimelineProgressDialog extends javax.swing.JDialog { @@ -49,11 +48,15 @@ public class TimelineProgressDialog extends javax.swing.JDialog { public TimelineProgressDialog(java.awt.Frame parent, boolean modal) { super(parent, modal); initComponents(); - + setLocationRelativeTo(null); //set icon the same as main app setIconImage(WindowManager.getDefault().getMainWindow().getIconImage()); + //progressBar.setIndeterminate(true); + + setName("Make Timeline (Beta)"); + // Close the dialog when Esc is pressed String cancelName = "cancel"; InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); @@ -73,6 +76,40 @@ public class TimelineProgressDialog extends javax.swing.JDialog { return returnStatus; } + void updateProgressBar(final int progress) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + progressBar.setValue(progress); + } + }); + + } + + void updateProgressBar(final String message) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + progressBar.setString(message); + } + }); + + } + + void setProgressTotal(final int total) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + //progressBar.setIndeterminate(false); + progressBar.setMaximum(total); + //progressBar.setValue(0); + progressBar.setStringPainted(true); + progressBar.setVisible(true); + } + }); + + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -83,6 +120,7 @@ public class TimelineProgressDialog extends javax.swing.JDialog { private void initComponents() { jLabel1 = new javax.swing.JLabel(); + progressBar = new javax.swing.JProgressBar(); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { @@ -97,36 +135,48 @@ public class TimelineProgressDialog extends javax.swing.JDialog { layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(51, 51, 51) - .addComponent(jLabel1) - .addContainerGap(131, Short.MAX_VALUE)) + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(progressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 504, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel1) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGap(37, 37, 37) + .addContainerGap() .addComponent(jLabel1) - .addContainerGap(64, Short.MAX_VALUE)) + .addGap(7, 7, 7) + .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(16, Short.MAX_VALUE)) ); pack(); }// //GEN-END:initComponents - + /** * Closes the dialog */ private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog doClose(RET_CANCEL); }//GEN-LAST:event_closeDialog - - public void doClose(int retStatus) { - returnStatus = retStatus; - setVisible(false); - dispose(); - } + void doClose(final int retStatus) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + returnStatus = retStatus; + setVisible(false); + dispose(); + } + }); + + } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel jLabel1; + private javax.swing.JProgressBar progressBar; // End of variables declaration//GEN-END:variables private int returnStatus = RET_CANCEL; } diff --git a/Timeline/src/org/sleuthkit/autopsy/timeline/layer.xml b/Timeline/src/org/sleuthkit/autopsy/timeline/layer.xml index 078f889c49..1a06f5740a 100644 --- a/Timeline/src/org/sleuthkit/autopsy/timeline/layer.xml +++ b/Timeline/src/org/sleuthkit/autopsy/timeline/layer.xml @@ -7,6 +7,10 @@ Windows2 ====================================================== --> + + + +