diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index faa57701f8..4398f99206 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -52,7 +52,7 @@ public abstract class AbstractAbstractFileNode extends A int dotIndex = name.lastIndexOf("."); if (dotIndex > 0) { String ext = name.substring(dotIndex).toLowerCase(); - + // If this is an archive file we will listen for ingest events // that will notify us when new content has been identified. for (String s : FileTypeExtensions.getArchiveExtensions()) { @@ -69,7 +69,7 @@ public abstract class AbstractAbstractFileNode extends A IngestManager.getInstance().removeIngestModuleEventListener(pcl); Case.removePropertyChangeListener(pcl); } - + private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java index 7654966472..d14a1fc912 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ExtractedContent.java @@ -375,58 +375,7 @@ public class ExtractedContent implements AutopsyVisitableItem { @Override public String getItemType() { - switch (type) { - case TSK_WEB_BOOKMARK: - return "ExtractedBookmarks"; - case TSK_WEB_COOKIE: - return "ExtractedCookies"; - case TSK_WEB_HISTORY: - return "ExtractedHistory"; - case TSK_WEB_DOWNLOAD: - return "ExtractedDownloads"; - case TSK_INSTALLED_PROG: - return "ExtractedPrograms"; - case TSK_RECENT_OBJECT: - return "ExtractedRecent"; - case TSK_DEVICE_ATTACHED: - return "ExtractedAttachedDevices"; - case TSK_WEB_SEARCH_QUERY: - return "ExtractedSearch"; - case TSK_METADATA_EXIF: - return "ExtractedMetadataExif"; - case TSK_EMAIL_MSG: - return "ExtractedEmailMsg"; - case TSK_CONTACT: - return "ExtractedContants"; - case TSK_MESSAGE: - return "ExtractedMessages"; - case TSK_CALLLOG: - return "ExtractedCallLog"; - case TSK_CALENDAR_ENTRY: - return "ExtractedCalendar"; - case TSK_SPEED_DIAL_ENTRY: - return "ExtractedSpeedDial"; - case TSK_BLUETOOTH_PAIRING: - return "ExtractedBluetooth"; - case TSK_GPS_BOOKMARK: - return "ExtractedGPSBookmarks"; - case TSK_GPS_LAST_KNOWN_LOCATION: - return "ExtractedGPSLastLocation"; - case TSK_GPS_SEARCH: - return "ExtractedGPSSearch"; - case TSK_SERVICE_ACCOUNT: - return "ExtractedServiceAccount"; - case TSK_ENCRYPTION_DETECTED: - return "ExtractedEncryption"; - case TSK_EXT_MISMATCH_DETECTED: - return "ExtractedExtMismatch"; - case TSK_OS_INFO: - return "ExtractedOS"; - case TSK_FACE_DETECTED: - return "ExtractedFaceDetected"; - - } - return "ExtractedContentType"; + return type.getDisplayName(); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java index 6a0dbde9de..f33690fc06 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java +++ b/Core/src/org/sleuthkit/autopsy/modules/embeddedfileextractor/SevenZipExtractor.java @@ -396,19 +396,18 @@ class SevenZipExtractor { if (colonIndex != -1) { // If alternate data stream is found, fix the name // so Windows doesn't choke on the colon character. - useName = base + ext.substring(0, colonIndex); - } else { - switch (ext) { - case ".gz": //NON-NLS - useName = base; - break; - case ".tgz": //NON-NLS - useName = base + ".tar"; //NON-NLS - break; - case ".bz2": //NON-NLS - useName = base + ".bz2"; //NON-NLS - break; - } + ext = ext.substring(0, colonIndex); + } + switch (ext) { + case ".gz": //NON-NLS + useName = base; + break; + case ".tgz": //NON-NLS + useName = base + ".tar"; //NON-NLS + break; + case ".bz2": //NON-NLS + useName = base; + break; } } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileIDSelectionModel.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileIDSelectionModel.java index 5392a619d0..e2109389fd 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileIDSelectionModel.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/FileIDSelectionModel.java @@ -18,41 +18,76 @@ */ package org.sleuthkit.autopsy.imagegallery; +import com.google.common.collect.ImmutableSet; +import java.beans.PropertyVetoException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Set; +import java.util.logging.Level; +import javafx.beans.Observable; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.ObservableSet; +import javax.swing.SwingUtilities; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.datamodel.TskCoreException; -/** Singleton that manages set of fileIds, as well as last selected fileID. +/** + * Manages set of selected fileIds, as well as last selected fileID. Since some + * actions (e.g. {@link ExtractAction} ) invoked through Image Gallery depend on + * what is available in the Utilities.actionsGlobalContext() lookup, we maintain + * that in sync with the local ObservableList based selection, via the + * ImageGalleryTopComponent's ExplorerManager. * * NOTE: When we had synchronization on selected and lastSelectedProp we got * deadlocks with the tiles during selection - * - * TODO: should this be singleton? selections are only within a single group - * now... -jm */ public class FileIDSelectionModel { private static final Logger LOGGER = Logger.getLogger(FileIDSelectionModel.class.getName()); - private static FileIDSelectionModel instance; - private final ObservableSet selected = FXCollections.observableSet(); private final ReadOnlyObjectWrapper lastSelectedProp = new ReadOnlyObjectWrapper<>(); - public static synchronized FileIDSelectionModel getInstance() { - if (instance == null) { - instance = new FileIDSelectionModel(); - } - return instance; - } + public FileIDSelectionModel(ImageGalleryController controller) { + /** + * Since some actions (e.g. {@link ExtractAction} ) invoked through + * Image Gallery depend on what is available in the + * Utilities.actionsGlobalContext() lookup, we maintain that in sync + * with the local ObservableList based selection, via the + * ImageGalleryTopComponent's ExplorerManager. + */ + selected.addListener((Observable observable) -> { + Set fileIDs = ImmutableSet.copyOf(selected); + SwingUtilities.invokeLater(() -> { + ArrayList fileNodes = new ArrayList<>(); + for (Long id : fileIDs) { + try { + fileNodes.add(new FileNode(controller.getSleuthKitCase().getAbstractFileById(id))); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to get abstract file by its ID", ex); + } + } + FileNode[] fileNodeArray = fileNodes.stream().toArray(FileNode[]::new); + Children.Array children = new Children.Array(); + children.add(fileNodeArray); - public FileIDSelectionModel() { - super(); + ImageGalleryTopComponent etc = (ImageGalleryTopComponent) WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID); + etc.getExplorerManager().setRootContext(new AbstractNode(children)); + try { + etc.getExplorerManager().setSelectedNodes(fileNodeArray); + } catch (PropertyVetoException ex) { + LOGGER.log(Level.SEVERE, "Explorer manager selection was vetoed.", ex); + } + }); + }); } public void toggleSelection(Long id) { @@ -110,6 +145,10 @@ public class FileIDSelectionModel { lastSelectedProp.set(id); } + /** + * expose the list of selected ids so that clients can listen for changes + */ + @SuppressWarnings("ReturnOfCollectionOrArrayField") public ObservableSet getSelected() { return selected; } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index 26e1812d02..1d7176421d 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -127,7 +127,7 @@ public final class ImageGalleryController { private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false); - private final FileIDSelectionModel selectionModel = FileIDSelectionModel.getInstance(); + private final FileIDSelectionModel selectionModel = new FileIDSelectionModel(this); private DBWorkerThread dbWorkerThread; @@ -167,7 +167,6 @@ public final class ImageGalleryController { } public synchronized FileIDSelectionModel getSelectionModel() { - return selectionModel; } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddDrawableTagAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddDrawableTagAction.java index 6201663428..28a377aade 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddDrawableTagAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/AddDrawableTagAction.java @@ -30,7 +30,6 @@ import javafx.scene.control.Menu; import javax.swing.SwingWorker; import org.openide.util.Utilities; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager; @@ -66,7 +65,7 @@ public class AddDrawableTagAction extends AddTagAction { @Override public void addTag(TagName tagName, String comment) { - Set selectedFiles = new HashSet<>(FileIDSelectionModel.getInstance().getSelected()); + Set selectedFiles = new HashSet<>(controller.getSelectionModel().getSelected()); addTagsToFiles(tagName, comment, selectedFiles); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java index 102ba5e7fa..35c6d9e85a 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/actions/CategorizeAction.java @@ -30,7 +30,6 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javax.swing.JOptionPane; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.Category; import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager; @@ -69,13 +68,12 @@ public class CategorizeAction extends AddTagAction { @Override public void addTag(TagName tagName, String comment) { - Set selectedFiles = new HashSet<>(FileIDSelectionModel.getInstance().getSelected()); + Set selectedFiles = new HashSet<>(controller.getSelectionModel().getSelected()); addTagsToFiles(tagName, comment, selectedFiles); } @Override public void addTagsToFiles(TagName tagName, String comment, Set selectedFiles) { - Logger.getAnonymousLogger().log(Level.INFO, "categorizing{0} as {1}", new Object[]{selectedFiles.toString(), tagName.getDisplayName()}); for (Long fileID : selectedFiles) { @@ -129,7 +127,6 @@ public class CategorizeAction extends AddTagAction { final CategoryManager categoryManager = controller.getCategoryManager(); final DrawableTagsManager tagsManager = controller.getTagsManager(); - try { DrawableFile file = controller.getFileFromId(fileID); //drawable db final List fileTags = tagsManager.getContentTagsByContent(file); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java index 4f9e647011..ff0926a6f6 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/DrawableDB.java @@ -54,6 +54,8 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy; import static org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy.GROUP_BY_VALUE; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.SleuthkitCase; @@ -475,6 +477,32 @@ public final class DrawableDB { return con.isClosed(); } + /** + * get the names of the hashsets that the given fileID belongs to + * + * @param fileID the fileID to get all the Hashset names for + * + * @return a set of hash set names, each of which the given file belongs to + * + * @throws TskCoreException + * + * + * //TODO: this is mostly a cut and paste from * + * AbstractContent.getHashSetNames, is there away to dedupe? + */ + Set getHashSetsForFile(long fileID) throws TskCoreException { + Set hashNames = new HashSet<>(); + ArrayList artifacts = tskCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, fileID); + + for (BlackboardArtifact a : artifacts) { + List attributes = a.getAttributes(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME); + for (BlackboardAttribute attr : attributes) { + hashNames.add(attr.getValueString()); + } + } + return Collections.unmodifiableSet(hashNames); + } + /** * get all the hash set names used in the db * diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java index e86f5fb051..a04646c310 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/datamodel/HashSetManager.java @@ -44,7 +44,7 @@ public class HashSetManager { */ private Set getHashSetsForFileHelper(long fileID) { try { - return db.getFileFromID(fileID).getHashSetNames(); + return db.getHashSetsForFile(fileID); } catch (TskCoreException ex) { Logger.getLogger(HashSetManager.class.getName()).log(Level.SEVERE, "Failed to get Hash Sets for file", ex); return Collections.emptySet(); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java index 96e674dddb..6088540262 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTile.java @@ -32,7 +32,6 @@ import org.sleuthkit.autopsy.imagegallery.FXMLConstructor; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile; import org.sleuthkit.autopsy.imagegallery.gui.Toolbar; -import static org.sleuthkit.autopsy.imagegallery.gui.drawableviews.DrawableTileBase.globalSelectionModel; import org.sleuthkit.datamodel.AbstractContent; /** @@ -63,7 +62,7 @@ public class DrawableTile extends DrawableTileBase { imageView.fitHeightProperty().bind(Toolbar.getDefault(getController()).sizeSliderValue()); imageView.fitWidthProperty().bind(Toolbar.getDefault(getController()).sizeSliderValue()); - globalSelectionModel.lastSelectedProperty().addListener((observable, oldValue, newValue) -> { + selectionModel.lastSelectedProperty().addListener((observable, oldValue, newValue) -> { try { setEffect(Objects.equals(newValue, getFileID()) ? LAST_SELECTED_EFFECT : null); } catch (java.lang.IllegalStateException ex) { @@ -84,7 +83,7 @@ public class DrawableTile extends DrawableTileBase { @Override protected void updateSelectionState() { super.updateSelectionState(); - final boolean lastSelected = Objects.equals(globalSelectionModel.lastSelectedProperty().get(), getFileID()); + final boolean lastSelected = Objects.equals(selectionModel.lastSelectedProperty().get(), getFileID()); Platform.runLater(() -> { setEffect(lastSelected ? LAST_SELECTED_EFFECT : null); }); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java index 74ca57f399..12cb55358b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/DrawableTileBase.java @@ -92,7 +92,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { protected static final Image followUpIcon = new Image("org/sleuthkit/autopsy/imagegallery/images/flag_red.png"); protected static final Image followUpGray = new Image("org/sleuthkit/autopsy/imagegallery/images/flag_gray.png"); - protected static final FileIDSelectionModel globalSelectionModel = FileIDSelectionModel.getInstance(); + protected final FileIDSelectionModel selectionModel; private static ContextMenu contextMenu; /** @@ -143,9 +143,9 @@ public abstract class DrawableTileBase extends DrawableUIBase { */ protected DrawableTileBase(GroupPane groupPane, final ImageGalleryController controller) { super(controller); - this.groupPane = groupPane; - globalSelectionModel.getSelected().addListener((Observable observable) -> updateSelectionState()); + selectionModel = controller.getSelectionModel(); + selectionModel.getSelected().addListener((Observable observable) -> updateSelectionState()); //set up mouse listener //TODO: split this between DrawableTile and SingleDrawableViewBase @@ -159,7 +159,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { case PRIMARY: if (t.getClickCount() == 1) { if (t.isControlDown()) { - globalSelectionModel.toggleSelection(fileID); + selectionModel.toggleSelection(fileID); } else { groupPane.makeSelection(t.isShiftDown(), fileID); } @@ -173,7 +173,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { break; case SECONDARY: if (t.getClickCount() == 1) { - if (globalSelectionModel.isSelected(fileID) == false) { + if (selectionModel.isSelected(fileID) == false) { groupPane.makeSelection(false, fileID); } } @@ -257,7 +257,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { getFile().ifPresent(file -> { if (followUpToggle.isSelected() == true) { try { - globalSelectionModel.clearAndSelect(file.getId()); + selectionModel.clearAndSelect(file.getId()); new AddDrawableTagAction(getController()).addTag(getController().getTagsManager().getFollowUpTagName(), ""); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex); @@ -339,7 +339,7 @@ public abstract class DrawableTileBase extends DrawableUIBase { */ protected void updateSelectionState() { getFile().ifPresent(file -> { - final boolean selected = globalSelectionModel.isSelected(file.getId()); + final boolean selected = selectionModel.isSelected(file.getId()); Platform.runLater(() -> setBorder(selected ? SELECTED_BORDER : UNSELECTED_BORDER)); }); } diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java index 69e392a2ee..0f25e60d30 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/gui/drawableviews/GroupPane.java @@ -151,7 +151,7 @@ public class GroupPane extends BorderPane { new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR)) ); - private static final FileIDSelectionModel globalSelectionModel = FileIDSelectionModel.getInstance(); + private final FileIDSelectionModel selectionModel; private static final List categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5, KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5); @@ -257,6 +257,7 @@ public class GroupPane extends BorderPane { public GroupPane(ImageGalleryController controller) { this.controller = controller; + this.selectionModel = controller.getSelectionModel(); nextGroupAction = new NextUnseenGroup(controller); backAction = new Back(controller); forwardAction = new Forward(controller); @@ -303,7 +304,7 @@ public class GroupPane extends BorderPane { slideShowPane.disposeContent(); } slideShowPane = null; - this.scrollToFileID(globalSelectionModel.lastSelectedProperty().get()); + this.scrollToFileID(selectionModel.lastSelectedProperty().get()); } public DrawableGroup getGroup() { @@ -311,7 +312,7 @@ public class GroupPane extends BorderPane { } private void selectAllFiles() { - globalSelectionModel.clearAndSelectAll(getGroup().fileIds()); + selectionModel.clearAndSelectAll(getGroup().fileIds()); } /** @@ -363,7 +364,7 @@ public class GroupPane extends BorderPane { if (slideShowPane != null) { slideShowPane.getFileID().ifPresent(fileID -> { if (newValue) { - FileIDSelectionModel.getInstance().clearAndSelect(fileID); + selectionModel.clearAndSelect(fileID); new CategorizeAction(controller).addTag(controller.getTagsManager().getTagName(cat), ""); } }); @@ -428,10 +429,10 @@ public class GroupPane extends BorderPane { HBox.setHgrow(spacer, Priority.ALWAYS); spacer.setMinWidth(Region.USE_PREF_SIZE); - FileIDSelectionModel.getInstance().getSelected().addListener((Observable o) -> { + selectionModel.getSelected().addListener((Observable o) -> { Platform.runLater(() -> { - catSelectedSplitMenu.setDisable(FileIDSelectionModel.getInstance().getSelected().isEmpty()); - tagSelectedSplitMenu.setDisable(FileIDSelectionModel.getInstance().getSelected().isEmpty()); + catSelectedSplitMenu.setDisable(selectionModel.getSelected().isEmpty()); + tagSelectedSplitMenu.setDisable(selectionModel.getSelected().isEmpty()); }); }); @@ -491,7 +492,7 @@ public class GroupPane extends BorderPane { //listen to toggles and update view state slideShowToggle.setOnAction((ActionEvent t) -> { - activateSlideShowViewer(globalSelectionModel.lastSelectedProperty().get()); + activateSlideShowViewer(selectionModel.lastSelectedProperty().get()); }); tileToggle.setOnAction((ActionEvent t) -> { @@ -543,7 +544,7 @@ public class GroupPane extends BorderPane { switch (t.getButton()) { case PRIMARY: if (t.getClickCount() == 1) { - globalSelectionModel.clearSelection(); + selectionModel.clearSelection(); if (contextMenu != null) { contextMenu.hide(); } @@ -554,7 +555,7 @@ public class GroupPane extends BorderPane { if (t.getClickCount() == 1) { selectAllFiles(); } - if (globalSelectionModel.getSelected().isEmpty() == false) { + if (selectionModel.getSelected().isEmpty() == false) { if (contextMenu == null) { contextMenu = buildContextMenu(); } @@ -590,7 +591,7 @@ public class GroupPane extends BorderPane { //listen to tile selection and make sure it is visible in scroll area //TODO: make sure we are testing complete visability not just bounds intersection - globalSelectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> { + selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> { if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW) { slideShowPane.setFile(newFileId); } else { @@ -736,11 +737,11 @@ public class GroupPane extends BorderPane { endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt(); List subList = grouping.get().fileIds().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().fileIds().size()) + 1); - globalSelectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()])); - globalSelectionModel.select(newFileID); + selectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()])); + selectionModel.select(newFileID); } else { selectionAnchorIndex = null; - globalSelectionModel.clearAndSelect(newFileID); + selectionModel.clearAndSelect(newFileID); } } @@ -796,7 +797,7 @@ public class GroupPane extends BorderPane { switch (t.getCode()) { case SHIFT: if (selectionAnchorIndex == null) { - selectionAnchorIndex = grouping.get().fileIds().indexOf(globalSelectionModel.lastSelectedProperty().get()); + selectionAnchorIndex = grouping.get().fileIds().indexOf(selectionModel.lastSelectedProperty().get()); } t.consume(); break; @@ -827,7 +828,7 @@ public class GroupPane extends BorderPane { break; case SPACE: if (groupViewMode.get() == GroupViewMode.TILE) { - activateSlideShowViewer(globalSelectionModel.lastSelectedProperty().get()); + activateSlideShowViewer(selectionModel.lastSelectedProperty().get()); } else { activateTileViewer(); } @@ -839,7 +840,7 @@ public class GroupPane extends BorderPane { selectAllFiles(); t.consume(); } - if (globalSelectionModel.getSelected().isEmpty() == false) { + if (selectionModel.getSelected().isEmpty() == false) { switch (t.getCode()) { case NUMPAD0: case DIGIT0: @@ -871,7 +872,7 @@ public class GroupPane extends BorderPane { } private void handleArrows(KeyEvent t) { - Long lastSelectFileId = globalSelectionModel.lastSelectedProperty().get(); + Long lastSelectFileId = selectionModel.lastSelectedProperty().get(); int lastSelectedIndex = lastSelectFileId != null ? grouping.get().fileIds().indexOf(lastSelectFileId) diff --git a/docs/doxygen-user/images/InstallZkJanitor.PNG b/docs/doxygen-user/images/InstallZkJanitor.PNG deleted file mode 100755 index b0eacf07ec..0000000000 Binary files a/docs/doxygen-user/images/InstallZkJanitor.PNG and /dev/null differ diff --git a/docs/doxygen-user/images/autoPurge.PNG b/docs/doxygen-user/images/autoPurge.PNG deleted file mode 100755 index a88c4811ee..0000000000 Binary files a/docs/doxygen-user/images/autoPurge.PNG and /dev/null differ diff --git a/docs/doxygen-user/images/dataDir.PNG b/docs/doxygen-user/images/dataDir.PNG deleted file mode 100755 index 8ec0c50921..0000000000 Binary files a/docs/doxygen-user/images/dataDir.PNG and /dev/null differ diff --git a/docs/doxygen-user/images/tickTime.PNG b/docs/doxygen-user/images/tickTime.PNG deleted file mode 100755 index 1c322155af..0000000000 Binary files a/docs/doxygen-user/images/tickTime.PNG and /dev/null differ diff --git a/docs/doxygen-user/images/updatedServiceInstall.PNG b/docs/doxygen-user/images/updatedServiceInstall.PNG index f3f1622e27..33045ec282 100755 Binary files a/docs/doxygen-user/images/updatedServiceInstall.PNG and b/docs/doxygen-user/images/updatedServiceInstall.PNG differ diff --git a/docs/doxygen-user/installPostgres.dox b/docs/doxygen-user/installPostgres.dox index b71ce6f1b7..686df9c0e4 100755 --- a/docs/doxygen-user/installPostgres.dox +++ b/docs/doxygen-user/installPostgres.dox @@ -89,7 +89,7 @@ To this: Note the removal of the leading number symbol-this uncomments that entry.

-8. Still in "C:\Program Files\PostgreSQL\9.4\data\postgresql.conf", find the entry named _max_connections_ and set it to the number of suggested connections for your configuration. A rule of thumb is add 100 connections for each Automated Ingest Node and 100 connections for each Reviewer node you plan to have in the network. More information is available at 5.1.1. See the screenshot below. +8. Still in "C:\Program Files\PostgreSQL\9.4\data\postgresql.conf", find the entry named _max_connections_ and set it to the number of suggested connections for your configuration. A rule of thumb is add 100 connections for each Automated Ingest Node and 100 connections for each Reviewer node you plan to have in the network. More information is available at 5.1.1. See the screenshot below.

\image html maxConnections.PNG

diff --git a/docs/doxygen-user/installSolr.dox b/docs/doxygen-user/installSolr.dox index a3ef2a712f..3bd2f6ab96 100755 --- a/docs/doxygen-user/installSolr.dox +++ b/docs/doxygen-user/installSolr.dox @@ -35,12 +35,11 @@ The following steps will configure Solr to run using an account that will have a 5. When the installation completes, clear the "Launch Bitnami Apache Solr Stack Now?" checkbox and click _Finish_. \subsection install_solr_config Solr Configuration -1. Stop _solrApache_ and _solrJetty_ services by pressing _Start_, typing _services.msc_, pressing _Enter_, and locating the _solrApache_ and _solrJetty_ Windows services. Select the services one at a time, and press _Stop the service_ once for each of them. If the service is already stopped and there is no _Stop the service_ available, this is okay. +1. Stop the _solrJetty_ service by pressing _Start_, typing _services.msc_, pressing _Enter_, and locating the _solrJetty_ Windows service. Select the service and press _Stop the service_. If the service is already stopped and there is no _Stop the service_ available, this is okay. 2. Edit the C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\scripts\\serviceinstall.bat script. You need administrator permission to change this file. The easiest way around this is to save a copy on the Desktop, edit the Desktop version, and copy the new one back over the top of the old. Windows will ask for permission to overwrite the old file; allow it. You should make the following changes to this file:

- Add the following options in the _JvmOptions_ section of the line that begins with "C:\Bitnami\solr-4.10.3-0/apache-solr\scripts\prunsrv.exe" : - + ++JvmOptions=-DzkRun + ++JvmOptions=-Dcollection.configName=AutopsyConfig + ++JvmOptions=-Dbootstrap_confdir="C:\Bitnami\solr-4.10.3-0\apache-solr\solr\configsets\AutopsyConfig\conf" - Replace the path to JavaHome with the path to your 64-bit version of the JRE. If you do not know the path, the correct _JavaHome_ path can be obtained by running the command "where java" from the Windows command line. An example is shown below. The text in yellow is what we are interested in. Do not include the "bin" folder in the path you place into the _JavaHome_ variable. A correct example of the final result will look something like this:   --JavaHome="C:\Program Files\Java\jre1.8.0_45" @@ -66,24 +65,7 @@ The added part is highlighted in yellow below. Ensure that it is inside the \
\image html transientcache.PNG

-4. Edit the file "C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg" to increase the _tickTime_ value to 15000 as shown in the screenshot below. -

-\image html tickTime.PNG -

-5. Create a folder on your local hard drive named _C:/Bitnami/zookeeper_ -6. Edit the file "C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg" to set the value dataDir=C:/Bitnami/zookeeper as shown in the screenshot below. -

-\image html dataDir.PNG -

-7. Edit the file "C:\Bitnami\solr-4.10.3-0\apache-solr\solr\zoo.cfg" to add the lines autopurge.snapRetainCount=3 and autopurge.purgeInterval=1 as shown in the screenshot below. -

-\image html autoPurge.PNG -

-8. Install _ZkJanitor_ by right clicking on _InstallJanitor.bat_, and selecting "Run as administrator". You should see a confirmation that ZkJanitor is installed, as shown in the screenshot below. -

-\image html InstallZkJanitor.PNG -

-9. Edit "C:\Bitnami\solr-4.10.3-0\apache-solr\resources/log4j.properties" to configure Solr log settings: +4. Edit "C:\Bitnami\solr-4.10.3-0\apache-solr\resources/log4j.properties" to configure Solr log settings: - Increase the log rotation size threshold (_log4j\.appender\.file\.MaxFileSize_) from 4MB to 100MB. - Remove the _CONSOLE_ appender from the _log4j\.rootLogger_ line.

@@ -93,9 +75,9 @@ The log file should end up looking like this (modified lines are highlighted in

\image html log4j.PNG

-10. From an Autopsy installation, copy the folder "C:\Program Files\Autopsy-4.0\autopsy\solr\solr\configsets" to "C:\Bitnami\solr-4.10.3-0\apache-solr\solr". -11. From an Autopsy installation, copy the folder "C:\Program Files\Autopsy-4.0\autopsy\solr\solr\lib" to "C:\Bitnami\solr-4.10.3-0\apache-solr\solr". -12. Start a Windows command prompt as administrator by pressing _Start_, typing _command_, right clicking on _Command Prompt_, and clicking on _Run as administrator_. Then run the following command to install the _solrJetty_ service: +5. From an Autopsy installation, copy the folder "C:\Program Files\Autopsy-4.0\autopsy\solr\solr\configsets" to "C:\Bitnami\solr-4.10.3-0\apache-solr\solr". +6. From an Autopsy installation, copy the folder "C:\Program Files\Autopsy-4.0\autopsy\solr\solr\lib" to "C:\Bitnami\solr-4.10.3-0\apache-solr\solr". +7. Start a Windows command prompt as administrator by pressing _Start_, typing _command_, right clicking on _Command Prompt_, and clicking on _Run as administrator_. Then run the following command to install the _solrJetty_ service:

cmd /c C:\\Bitnami\\solr-4.10.3-0\\apache-solr\\scripts\\serviceinstall.bat INSTALL

@@ -103,13 +85,13 @@ The log file should end up looking like this (modified lines are highlighted in

\image html solrinstall1.PNG

-13. Press _Start_, type _services.msc_, and press _Enter_. Find _solrJetty_. If the service is running, press _Stop the service_, then double click it, and switch to the _Log On_ tab to change the logon credentials to a user who will have access to read and write the primary shared drive. If the machine is on a domain, the Account Name will be in the form of _DOMAINNAME\\username_ as shown in the example below. Note that in the screenshot below, the domain name is _DOMAIN_ and the user name is _username_. These are just examples, not real values. +8. Press _Start_, type _services.msc_, and press _Enter_. Find _solrJetty_. If the service is running, press _Stop the service_, then double click it, and switch to the _Log On_ tab to change the logon credentials to a user who will have access to read and write the primary shared drive. If the machine is on a domain, the Account Name will be in the form of _DOMAINNAME\\username_ as shown in the example below. Note that in the screenshot below, the domain name is _DOMAIN_ and the user name is _username_. These are just examples, not real values.

\image html solrinstall2.PNG
If the machine is on a domain, **make sure** to select the domain with the mouse by going to the _Log On_ tab, clicking _Browse_, then clicking _Locations_ and selecting the domain of interest. Then enter the user name desired and press _Check Names_. When that completes, press _OK_, type in the password once for each box and press _OK_. You may see "The user has been granted the log on as a service right." -14. You should be able to see the Solr service in a web browser via the URL http://localhost:8983/solr/#/ as shown in the screenshot below. +9. You should be able to see the Solr service in a web browser via the URL http://localhost:8983/solr/#/ as shown in the screenshot below.

\image html solrinstall3.PNG

diff --git a/test/script/config.xml b/test/script/config.xml index 6c7bf951a1..d11f973649 100644 --- a/test/script/config.xml +++ b/test/script/config.xml @@ -2,11 +2,24 @@ diff --git a/test/script/regression.py b/test/script/regression.py index 893a0d4fdf..1c7f5e7d1b 100755 --- a/test/script/regression.py +++ b/test/script/regression.py @@ -84,15 +84,16 @@ Day = 0 def usage(): - print ("-f PATH single file") - print ("-r rebuild") - print ("-l PATH path to config file") - print ("-u Ignore unallocated space") - print ("-k Do not delete SOLR index") - print ("-o PATH path to output folder for Diff files") - print ("-v verbose mode") - print ("-e ARG Enable exception mode with given string") - print ("-h help") + print ("-f PATH single file") + print ("-r rebuild") + print ("-b run both compare and rebuild") + print ("-l PATH path to config file") + print ("-u Ignore unallocated space") + print ("-k Do not delete SOLR index") + print ("-o PATH path to output folder for Diff files") + print ("-v verbose mode") + print ("-e ARG Enable exception mode with given string") + print ("-h help") #----------------------# # Main # @@ -103,10 +104,12 @@ def main(): parse_result = args.parse() # The arguments were given wrong: if not parse_result: - return + Errors.print_error("The arguments were given wrong") + exit(1) test_config = TestConfiguration(args) TestRunner.run_tests(test_config) + exit(0) class TestRunner(object): @@ -125,6 +128,7 @@ class TestRunner(object): Reports.html_add_images(test_config.html_log, test_config.images) # Test each image + gold_exists = False logres =[] for test_data in test_data_list: Errors.clear_print_logs() @@ -133,6 +137,11 @@ class TestRunner(object): Errors.print_error(msg) Errors.print_error(test_data.gold_archive) continue + + # At least one test has gold + gold_exists = True + + # Analyze the given image TestRunner._run_autopsy_ingest(test_data) @@ -140,9 +149,12 @@ class TestRunner(object): # Generate HTML report Reports.write_html_foot(test_config.html_log) - # Either copy the data or compare the data + # Either copy the data or compare the data or both if test_config.args.rebuild: TestRunner.rebuild(test_data) + elif test_config.args.both: + logres.append(TestRunner._compare_results(test_data)) + TestRunner.rebuild(test_data) else: logres.append(TestRunner._compare_results(test_data)) @@ -152,9 +164,11 @@ class TestRunner(object): time.sleep(10) TestRunner._cleanup(test_data) - if all([ test_data.overall_passed for test_data in test_data_list ]): - pass - else: + if not gold_exists: + Errors.print_error("No image had any gold; Regression did not run") + exit(1) + + if not all([ test_data.overall_passed for test_data in test_data_list ]): html = open(test_config.html_log) Errors.add_errors_out(html.name) html.close() @@ -179,13 +193,17 @@ class TestRunner(object): TestRunner._run_ant(test_data) time.sleep(2) # Give everything a second to process + # exit if .db was not created + if not file_exists(test_data.get_db_path(DBType.OUTPUT)): + Errors.print_error("Autopsy did not run properly; No .db file was created") + sys.exit(1) + try: # Dump the database before we diff or use it for rebuild TskDbDiff.dump_output_db(test_data.get_db_path(DBType.OUTPUT), test_data.get_db_dump_path(DBType.OUTPUT), test_data.get_sorted_data_path(DBType.OUTPUT)) except sqlite3.OperationalError as e: - print("Ingest did not run properly.", - "Make sure no other instances of Autopsy are open and try again.") + Errors.print_error("Ingest did not run properly.\nMake sure no other instances of Autopsy are open and try again.") sys.exit(1) # merges logs into a single log for later diff / rebuild @@ -396,8 +414,7 @@ class TestRunner(object): test_data.ant.append("-Dkeyword_path=" + test_config.keyword_path) test_data.ant.append("-Dnsrl_path=" + test_config.nsrl_path) test_data.ant.append("-Dgold_path=" + test_config.gold) - test_data.ant.append("-Dout_path=" + - make_local_path(test_data.output_path)) + test_data.ant.append("-Dout_path=" + make_local_path(test_data.output_path)) if test_config.jenkins: test_data.ant.append("-Ddiff_dir="+ test_config.diff_dir) test_data.ant.append("-Dignore_unalloc=" + "%s" % test_config.args.unallocated) @@ -657,6 +674,7 @@ class TestConfiguration(object): """ self.args = args # Paths: + self.output_parent_dir = make_path("..", "output", "results") self.output_dir = "" self.input_dir = make_local_path("..","input") self.gold = make_path("..", "output", "gold") @@ -703,17 +721,19 @@ class TestConfiguration(object): counts = {} if parsed_config.getElementsByTagName("indir"): self.input_dir = parsed_config.getElementsByTagName("indir")[0].getAttribute("value").encode().decode("utf_8") + if parsed_config.getElementsByTagName("outdir"): + self.output_parent_dir = parsed_config.getElementsByTagName("outdir")[0].getAttribute("value").encode().decode("utf_8") if parsed_config.getElementsByTagName("global_csv"): self.global_csv = parsed_config.getElementsByTagName("global_csv")[0].getAttribute("value").encode().decode("utf_8") self.global_csv = make_local_path(self.global_csv) if parsed_config.getElementsByTagName("golddir"): self.gold = parsed_config.getElementsByTagName("golddir")[0].getAttribute("value").encode().decode("utf_8") if parsed_config.getElementsByTagName("jenkins"): - self.jenkins = True - if parsed_config.getElementsByTagName("diffdir"): + self.jenkins = parsed_config.getElementsByTagName("jenkins")[0].getAttribute("value").encode().decode("utf_8") + if self.jenkins and parsed_config.getElementsByTagName("diffdir"): self.diff_dir = parsed_config.getElementsByTagName("diffdir")[0].getAttribute("value").encode().decode("utf_8") - else: - self.jenkins = False + else: + self.jenkins = False if parsed_config.getElementsByTagName("timing"): self.timing = parsed_config.getElementsByTagName("timing")[0].getAttribute("value").encode().decode("utf_8") self._init_imgs(parsed_config) @@ -727,9 +747,9 @@ class TestConfiguration(object): def _init_logs(self): """Setup output folder, logs, and reporting infrastructure.""" - if(not dir_exists(make_path("..", "output", "results"))): - os.makedirs(make_path("..", "output", "results",)) - self.output_dir = make_path("..", "output", "results", time.strftime("%Y.%m.%d-%H.%M.%S")) + if not dir_exists(self.output_parent_dir): + os.makedirs(self.output_parent_dir) + self.output_dir = make_path(self.output_parent_dir, time.strftime("%Y.%m.%d-%H.%M.%S")) os.makedirs(self.output_dir) self.csv = make_local_path(self.output_dir, "CSV.txt") self.html_log = make_path(self.output_dir, "AutopsyTestCase.html") @@ -757,6 +777,9 @@ class TestConfiguration(object): image_count = len(self.images) # Sanity check to see if there are obvious gold images that we are not testing + if not dir_exists(self.gold): + Errors.print_error("Gold folder does not exist") + sys.exit(1) gold_count = 0 for file in os.listdir(self.gold): if not(file == 'tmp'): @@ -885,7 +908,7 @@ class TestResultsDiffer(object): (subprocess.check_output(["diff", '-r', '-N', '-x', '*.png', '-x', '*.ico', '--ignore-matching-lines', 'HTML Report Generated on \|Autopsy Report for case \|Case:\|Case Number:' '\|Examiner:', gold_report_path, output_report_path])) - print_report("", "REPORT COMPARISON", "The test reports matched the gold reports") + print_report("", "REPORT COMPARISON", "The test html reports matched the gold reports") return True except subprocess.CalledProcessError as e: if e.returncode == 1: @@ -1644,6 +1667,7 @@ class Args(object): self.single = False self.single_file = "" self.rebuild = False + self.both = False self.list = False self.config_file = "" self.unallocated = False @@ -1672,6 +1696,9 @@ class Args(object): elif(arg == "-r" or arg == "--rebuild"): print("Running in rebuild mode.\n") self.rebuild = True + elif(arg == "-b" or arg == "--both"): + print("Comparing then creating gold") + self.both = True elif(arg == "-l" or arg == "--list"): try: arg = sys.argv.pop(0) @@ -1709,7 +1736,7 @@ class Args(object): elif arg == "-o" or arg == "--output": try: arg = sys.argv.pop(0) - if not os.path.exists(arg): + if not dir_exists(arg): print("Invalid output folder given.\n") return False nxtproc.append(arg) @@ -1995,3 +2022,4 @@ if __name__ == "__main__": main() else: print("We only support Windows and Cygwin at this time.") + sys.exit(1) diff --git a/test/script/tskdbdiff.py b/test/script/tskdbdiff.py index d19ca30810..c4f2e3c4ab 100755 --- a/test/script/tskdbdiff.py +++ b/test/script/tskdbdiff.py @@ -44,8 +44,6 @@ class TskDbDiff(object): self.output_dir = output_dir self.gold_bb_dump = gold_bb_dump self.gold_dump = gold_dump - self._generate_gold_dump = True - self._generate_gold_bb_dump = True self._bb_dump_diff = "" self._dump_diff = "" self._bb_dump = "" @@ -61,12 +59,6 @@ class TskDbDiff(object): self._init_diff() - # generate the gold database dumps if necessary - if self._generate_gold_dump: - TskDbDiff._dump_output_db_nonbb(self.gold_db_file, self.gold_dump) - if self._generate_gold_bb_dump: - TskDbDiff._dump_output_db_bb(self.gold_db_file, self.gold_bb_dump) - # generate the output database dumps (both DB and BB) TskDbDiff._dump_output_db_nonbb(self.output_db_file, self._dump) TskDbDiff._dump_output_db_bb(self.output_db_file, self._bb_dump)