Merge branch 'develop' of https://github.com/sleuthkit/autopsy into develop

This commit is contained in:
Raman 2018-10-09 10:49:53 -04:00
commit 8b59f526a8
9 changed files with 245 additions and 176 deletions

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2013-16 Basis Technology Corp. * Copyright 2013-18 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -37,6 +37,7 @@ public class ImageGalleryPreferences {
*/ */
private static final String ENABLED_BY_DEFAULT = "enabled_by_default"; //NON-NLS private static final String ENABLED_BY_DEFAULT = "enabled_by_default"; //NON-NLS
private static final String GROUP_CATEGORIZATION_WARNING_DISABLED = "group_categorization_warning_disabled"; //NON-NLS private static final String GROUP_CATEGORIZATION_WARNING_DISABLED = "group_categorization_warning_disabled"; //NON-NLS
private static final String MULTI_USER_CASE_INFO_DIALOG_DISABLED = "multi_user_case_info_dialog_disabled"; //NON-NLS
/** /**
* Return setting of whether Image Analyzer should be automatically enabled * Return setting of whether Image Analyzer should be automatically enabled
@ -46,7 +47,7 @@ public class ImageGalleryPreferences {
* @return true if new cases should have image analyzer enabled. * @return true if new cases should have image analyzer enabled.
*/ */
public static boolean isEnabledByDefault() { public static boolean isEnabledByDefault() {
return preferences.getBoolean(ENABLED_BY_DEFAULT, true); return preferences.getBoolean(ENABLED_BY_DEFAULT, true);
} }
public static void setEnabledByDefault(boolean b) { public static void setEnabledByDefault(boolean b) {
@ -68,6 +69,21 @@ public class ImageGalleryPreferences {
preferences.putBoolean(GROUP_CATEGORIZATION_WARNING_DISABLED, b); preferences.putBoolean(GROUP_CATEGORIZATION_WARNING_DISABLED, b);
} }
/**
* Return whether the dialog describing multi user case updating is
* disabled.
*
* @return true if the dialog is disabled.
*/
public static boolean isMultiUserCaseInfoDialogDisabled() {
final boolean aBoolean = preferences.getBoolean(MULTI_USER_CASE_INFO_DIALOG_DISABLED, false);
return aBoolean;
}
public static void setMultiUserCaseInfoDialogDisabled(boolean b) {
preferences.putBoolean(MULTI_USER_CASE_INFO_DIALOG_DISABLED, b);
}
static void addChangeListener(PreferenceChangeListener l) { static void addChangeListener(PreferenceChangeListener l) {
preferences.addPreferenceChangeListener(l); preferences.addPreferenceChangeListener(l);
} }

View File

@ -18,14 +18,25 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.actions; package org.sleuthkit.autopsy.imagegallery.actions;
import com.google.common.util.concurrent.ListenableFuture;
import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator;
import java.awt.Component; import java.awt.Component;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javax.annotation.concurrent.ThreadSafe;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.JOptionPane; import javax.swing.SwingUtilities;
import org.openide.awt.ActionID; import org.openide.awt.ActionID;
import org.openide.awt.ActionReference; import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences; import org.openide.awt.ActionReferences;
@ -34,15 +45,19 @@ import org.openide.util.HelpCtx;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.openide.util.actions.CallableSystemAction; import org.openide.util.actions.CallableSystemAction;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.Installer; import org.sleuthkit.autopsy.core.Installer;
import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController; import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule; import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryPreferences;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent; import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.imagegallery.OpenAction") @ActionID(category = "Tools", id = "org.sleuthkit.autopsy.imagegallery.OpenAction")
@ -119,9 +134,13 @@ public final class OpenAction extends CallableSystemAction {
} }
@Override @Override
@NbBundle.Messages({"OpenAction.dialogTitle=Image Gallery"}) @NbBundle.Messages({"OpenAction.dialogTitle=Image Gallery",
"OpenAction.multiUserDialog.Header=Multi-user Image Gallery",
"OpenAction.multiUserDialog.ContentText=The Image Gallery updates itself differently for multi-user cases than single user cases. Notably:\n\n"
+ "If your computer is analyzing a data source, then you will get real-time Image Gallery updates as files are analyzed (hashed, EXIF, etc.). This is the same behavior as a single-user case.\n\n"
+ "If another computer in your multi-user cluster is analyzing a data source, you will get updates about files on that data source only when you launch Image Gallery, which will cause the local database to be rebuilt based on results from other nodes.",
"OpenAction.multiUserDialog.checkBox.text=Don't show this message again."})
public void performAction() { public void performAction() {
//check case //check case
final Case currentCase; final Case currentCase;
try { try {
@ -130,57 +149,103 @@ public final class OpenAction extends CallableSystemAction {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); logger.log(Level.SEVERE, "Exception while getting open case.", ex);
return; return;
} }
ImageGalleryController controller;
try { try {
ImageGalleryController controller = ImageGalleryModule.getController(); controller = ImageGalleryModule.getController();
if (controller.isDataSourcesTableStale()) { } catch (TskCoreException | NoCurrentCaseException ex) {
//drawable db is stale, ask what to do logger.log(Level.SEVERE, "Exception while getting ImageGalleryController for current case.", ex);
int answer = JOptionPane.showConfirmDialog( return;
WindowManager.getDefault().getMainWindow(),
Bundle.OpenAction_stale_confDlg_msg(),
Bundle.OpenAction_stale_confDlg_title(),
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE);
switch (answer) {
case JOptionPane.YES_OPTION:
/* For a single-user case, we favor user experience, and
* rebuild the database as soon as Image Gallery is
* enabled for the case. For a multi-user case, we favor
* overall performance and user experience, not every
* user may want to review images, so we rebuild the
* database only when a user launches Image Gallery.
*/
if (currentCase.getCaseType() == Case.CaseType.SINGLE_USER_CASE) {
/*
* Turning listening off is necessary in order to
* invoke the listener that will call
* controller.rebuildDB();
*/
controller.setListeningEnabled(false);
controller.setListeningEnabled(true);
} else {
controller.rebuildDB();
}
ImageGalleryTopComponent.openTopComponent();
break;
case JOptionPane.NO_OPTION: {
ImageGalleryTopComponent.openTopComponent();
}
break;
case JOptionPane.CANCEL_OPTION:
break; //do nothing
}
} else {
//drawable db is not stale, just open it
ImageGalleryTopComponent.openTopComponent();
}
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS
} }
Platform.runLater(() -> {
if (currentCase.getCaseType() == Case.CaseType.MULTI_USER_CASE
&& ImageGalleryPreferences.isMultiUserCaseInfoDialogDisabled() == false) {
Alert dialog = new Alert(Alert.AlertType.INFORMATION);
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setResizable(true);
dialog.setTitle(Bundle.OpenAction_dialogTitle());
dialog.setHeaderText(Bundle.OpenAction_multiUserDialog_Header());
Label label = new Label(Bundle.OpenAction_multiUserDialog_ContentText());
label.setMaxWidth(450);
label.setWrapText(true);
CheckBox dontShowAgainCheckBox = new CheckBox(Bundle.OpenAction_multiUserDialog_checkBox_text());
dialog.getDialogPane().setContent(new VBox(10, label, dontShowAgainCheckBox));
GuiUtils.setDialogIcons(dialog);
dialog.showAndWait();
if (dialog.getResult() == ButtonType.OK && dontShowAgainCheckBox.isSelected()) {
ImageGalleryPreferences.setMultiUserCaseInfoDialogDisabled(true);
}
}
checkDBStale(controller);
});
}
private void checkDBStale(ImageGalleryController controller) {
//check if db is stale on throw away bg thread and then react back on jfx thread.
ListenableFuture<Boolean> staleFuture = TaskUtils.getExecutorForClass(OpenAction.class)
.submit(controller::isDataSourcesTableStale);
addFXCallback(staleFuture,
dbIsStale -> {
//back on fx thread.
if (false == dbIsStale) {
//drawable db is not stale, just open it
openTopComponent();
} else {
//drawable db is stale, ask what to do
Alert alert = new Alert(Alert.AlertType.WARNING,
Bundle.OpenAction_stale_confDlg_msg(),
ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
alert.initModality(Modality.APPLICATION_MODAL);
alert.setTitle(Bundle.OpenAction_stale_confDlg_title());
GuiUtils.setDialogIcons(alert);
ButtonType answer = alert.showAndWait().orElse(ButtonType.CANCEL);
if (answer == ButtonType.CANCEL) {
//just do nothing
} else if (answer == ButtonType.NO) {
openTopComponent();
} else if (answer == ButtonType.YES) {
if (controller.getAutopsyCase().getCaseType() == Case.CaseType.SINGLE_USER_CASE) {
/* For a single-user case, we favor user
* experience, and rebuild the database as soon
* as Image Gallery is enabled for the case.
*
* Turning listening off is necessary in order
* to invoke the listener that will call
* controller.rebuildDB();
*/
controller.setListeningEnabled(false);
controller.setListeningEnabled(true);
} else {
/*
* For a multi-user case, we favor overall
* performance and user experience, not every
* user may want to review images, so we rebuild
* the database only when a user launches Image
* Gallery.
*/
controller.rebuildDB();
}
openTopComponent();
}
}
},
throwable -> logger.log(Level.SEVERE, "Error checking if drawable db is stale.", throwable)//NON-NLS
);
}
private void openTopComponent() {
SwingUtilities.invokeLater(() -> {
try {
ImageGalleryTopComponent.openTopComponent();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex);//NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error getting ImageGalleryController.", ex); //NON-NLS}
}
});
} }
@Override @Override
@ -195,6 +260,6 @@ public final class OpenAction extends CallableSystemAction {
@Override @Override
public boolean asynchronous() { public boolean asynchronous() {
return false; // run on edt return true; // run off edt
} }
} }

View File

@ -99,7 +99,7 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
if (!Objects.equals(this.attr, other.attr)) { if (!Objects.equals(this.attr, other.attr)) {
return false; return false;
} }
return Objects.equals(this.dataSource, other.dataSource); return this.dataSource.getId() == other.dataSource.getId();
} }
@Override @Override

View File

@ -82,7 +82,7 @@ public class SummaryTablePane extends AnchorPane {
//register for category events //register for category events
controller.getCategoryManager().registerListener(this); controller.getCategoryManager().registerListener(this);
handleCategoryChanged(null); new Thread(() -> handleCategoryChanged(null)).start();
} }
public SummaryTablePane(ImageGalleryController controller) { public SummaryTablePane(ImageGalleryController controller) {

View File

@ -74,6 +74,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.DataSource;
/** /**
@ -255,6 +256,7 @@ public class Toolbar extends ToolBar {
evt -> { evt -> {
Platform.runLater(() -> { Platform.runLater(() -> {
Optional<DataSource> selectedItem = dataSourceSelectionModel.getSelectedItem(); Optional<DataSource> selectedItem = dataSourceSelectionModel.getSelectedItem();
//restore selection once the sync is done.
syncDataSources().addListener(() -> dataSourceSelectionModel.select(selectedItem), Platform::runLater); syncDataSources().addListener(() -> dataSourceSelectionModel.select(selectedItem), Platform::runLater);
}); });
}); });
@ -269,33 +271,30 @@ public class Toolbar extends ToolBar {
} }
private void initTagMenuButton() { private void initTagMenuButton() {
ListenableFuture<TagGroupAction> future = exec.submit(() -> new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller)); addFXCallback(exec.submit(() -> new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller)),
Futures.addCallback(future, new FutureCallback<TagGroupAction>() { followUpGroupAction -> {
@Override //on fx thread
public void onSuccess(TagGroupAction followUpGroupAction) { tagGroupMenuButton.setOnAction(followUpGroupAction);
tagGroupMenuButton.setOnAction(followUpGroupAction); tagGroupMenuButton.setText(followUpGroupAction.getText());
tagGroupMenuButton.setText(followUpGroupAction.getText()); tagGroupMenuButton.setGraphic(followUpGroupAction.getGraphic());
tagGroupMenuButton.setGraphic(followUpGroupAction.getGraphic()); },
} throwable -> {
/*
@Override * The problem appears to be a timing issue where a case is
public void onFailure(Throwable throwable) { * closed before this initialization is completed, which It
/* * appears to be harmless, so we are temporarily changing
* The problem appears to be a timing issue where a case is * this log message to a WARNING.
* closed before this initialization is completed, which It *
* appears to be harmless, so we are temporarily changing this * TODO (JIRA-3010): SEVERE error logged by image Gallery UI
* log message to a WARNING. */
* if (Case.isCaseOpen()) {
* TODO (JIRA-3010): SEVERE error logged by image Gallery UI logger.log(Level.WARNING, "Could not create Follow Up tag menu item", throwable); //NON-NLS
*/ } else {
if (Case.isCaseOpen()) { // don't add stack trace to log because it makes looking for real errors harder
logger.log(Level.WARNING, "Could not create Follow Up tag menu item", throwable); //NON-NLS logger.log(Level.INFO, "Unable to get tag name. Case is closed."); //NON-NLS
} else { }
// don't add stack trace to log because it makes looking for real errors harder
logger.log(Level.INFO, "Unable to get tag name. Case is closed."); //NON-NLS
} }
} );
}, Platform::runLater);
tagGroupMenuButton.showingProperty().addListener(showing -> { tagGroupMenuButton.showingProperty().addListener(showing -> {
if (tagGroupMenuButton.isShowing()) { if (tagGroupMenuButton.isShowing()) {
@ -303,24 +302,18 @@ public class Toolbar extends ToolBar {
return Lists.transform(controller.getTagsManager().getNonCategoryTagNames(), return Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
tagName -> GuiUtils.createAutoAssigningMenuItem(tagGroupMenuButton, new TagGroupAction(tagName, controller))); tagName -> GuiUtils.createAutoAssigningMenuItem(tagGroupMenuButton, new TagGroupAction(tagName, controller)));
}); });
Futures.addCallback(getTagsFuture, new FutureCallback<List<MenuItem>>() {
@Override
public void onSuccess(List<MenuItem> result) {
tagGroupMenuButton.getItems().setAll(result);
}
@Override addFXCallback(getTagsFuture,
public void onFailure(Throwable t) { menuItems -> tagGroupMenuButton.getItems().setAll(menuItems),
logger.log(Level.SEVERE, "Error getting non-gategory tag names.", t); throwable -> logger.log(Level.SEVERE, "Error getting non-gategory tag names.", throwable)
} );
}, Platform::runLater);
} }
}); });
} }
@ThreadConfined(type = ThreadConfined.ThreadType.ANY) @ThreadConfined(type = ThreadConfined.ThreadType.ANY)
private ListenableFuture<List<DataSource>> syncDataSources() { private ListenableFuture<List<DataSource>> syncDataSources() {
ListenableFuture<List<DataSource>> future = exec.submit(() -> { ListenableFuture<List<DataSource>> dataSourcesFuture = exec.submit(() -> {
List<DataSource> dataSourcesInCase = controller.getSleuthKitCase().getDataSources(); List<DataSource> dataSourcesInCase = controller.getSleuthKitCase().getDataSources();
synchronized (dataSourcesViewable) { synchronized (dataSourcesViewable) {
dataSourcesViewable.clear(); dataSourcesViewable.clear();
@ -331,22 +324,18 @@ public class Toolbar extends ToolBar {
} }
return dataSourcesInCase; return dataSourcesInCase;
}); });
Futures.addCallback(future, new FutureCallback<List<DataSource>>() { addFXCallback(dataSourcesFuture,
@Override result -> {
public void onSuccess(List<DataSource> result) { //on fx thread
List<Optional<DataSource>> newDataSources = new ArrayList<>(); List<Optional<DataSource>> newDataSources = new ArrayList<>(Lists.transform(result, Optional::of));
newDataSources.add(Optional.empty()); newDataSources.add(0, Optional.empty());
result.forEach(dataSource -> newDataSources.add(Optional.of(dataSource))); dataSources.setAll(newDataSources);
dataSources.setAll(newDataSources); },
} throwable -> logger.log(Level.SEVERE, "Unable to get datasources for current case.", throwable) //NON-NLS
@Override );
public void onFailure(Throwable t) {
logger.log(Level.SEVERE, "Unable to get datasources for current case.", t); //NON-NLS
}
}, Platform::runLater);
return future; return dataSourcesFuture;
} }
/** /**
@ -384,8 +373,7 @@ public class Toolbar extends ToolBar {
* selection. * selection.
*/ */
private void syncGroupControlsEnabledState(GroupViewState newViewState) { private void syncGroupControlsEnabledState(GroupViewState newViewState) {
boolean noGroupSelected = (null == newViewState) boolean noGroupSelected = (null == newViewState) || (null == newViewState.getGroup());
|| (null == newViewState.getGroup());
Platform.runLater(() -> { Platform.runLater(() -> {
tagGroupMenuButton.setDisable(noGroupSelected); tagGroupMenuButton.setDisable(noGroupSelected);
catGroupMenuButton.setDisable(noGroupSelected); catGroupMenuButton.setDisable(noGroupSelected);
@ -402,7 +390,5 @@ public class Toolbar extends ToolBar {
public Toolbar(ImageGalleryController controller) { public Toolbar(ImageGalleryController controller) {
this.controller = controller; this.controller = controller;
FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS
} }
} }

View File

@ -21,12 +21,12 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.util.concurrent.FutureCallback; import static com.google.common.collect.Lists.transform;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import static java.util.Arrays.asList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -35,6 +35,7 @@ import java.util.Map;
import static java.util.Objects.isNull; import static java.util.Objects.isNull;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import javafx.animation.Interpolator; import javafx.animation.Interpolator;
@ -135,7 +136,10 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils; import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
import static org.sleuthkit.autopsy.imagegallery.gui.GuiUtils.createAutoAssigningMenuItem;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
@ -254,7 +258,7 @@ public class GroupPane extends BorderPane {
private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>(); private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>();
/** /**
* map from fileIDs to their assigned cells in the tile view. This is used * Map from fileIDs to their assigned cells in the tile view. This is used
* to determine whether fileIDs are visible or are offscreen. No entry * to determine whether fileIDs are visible or are offscreen. No entry
* indicates the given fileID is not displayed on screen. DrawableCells are * indicates the given fileID is not displayed on screen. DrawableCells are
* responsible for adding and removing themselves from this map. * responsible for adding and removing themselves from this map.
@ -371,7 +375,7 @@ public class GroupPane extends BorderPane {
case FIVE: case FIVE:
return cat5Toggle; return cat5Toggle;
default: default:
throw new IllegalArgumentException(category.name()); throw new UnsupportedOperationException("Unknown category: " + category.name());
} }
} }
@ -425,51 +429,41 @@ public class GroupPane extends BorderPane {
DoubleBinding cellSize = controller.thumbnailSizeProperty().add(75); DoubleBinding cellSize = controller.thumbnailSizeProperty().add(75);
gridView.cellHeightProperty().bind(cellSize); gridView.cellHeightProperty().bind(cellSize);
gridView.cellWidthProperty().bind(cellSize); gridView.cellWidthProperty().bind(cellSize);
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell()); gridView.setCellFactory(param -> new DrawableCell());
BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected()); BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected());
catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
TagSelectedFilesAction followUpSelectedAction = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); //NON-NLS addFXCallback(exec.submit(() -> controller.getTagsManager().getFollowUpTagName()),
Platform.runLater(() -> { followUpTagName -> {
tagSelectedSplitMenu.setText(followUpSelectedAction.getText()); //on fx thread
tagSelectedSplitMenu.setGraphic(followUpSelectedAction.getGraphic()); TagSelectedFilesAction followUpSelectedAction = new TagSelectedFilesAction(followUpTagName, controller);
tagSelectedSplitMenu.setOnAction(followUpSelectedAction); tagSelectedSplitMenu.setText(followUpSelectedAction.getText());
tagSelectedSplitMenu.showingProperty().addListener(showing -> { tagSelectedSplitMenu.setGraphic(followUpSelectedAction.getGraphic());
if (tagSelectedSplitMenu.isShowing()) { tagSelectedSplitMenu.setOnAction(followUpSelectedAction);
},
ListenableFuture<List<MenuItem>> getTagsFuture = exec.submit(() throwable -> logger.log(Level.SEVERE, "Error getting tag names.", throwable));
-> Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
tagName -> GuiUtils.createAutoAssigningMenuItem(tagSelectedSplitMenu, new TagSelectedFilesAction(tagName, controller))));
Futures.addCallback(getTagsFuture, new FutureCallback<List<MenuItem>>() {
@Override
public void onSuccess(List<MenuItem> result) {
tagSelectedSplitMenu.getItems().setAll(result);
}
@Override
public void onFailure(Throwable throwable) {
logger.log(Level.SEVERE, "Error getting tag names.", throwable);
}
}, Platform::runLater);
}
});
});
addFXCallback(exec.submit(() -> controller.getTagsManager().getNonCategoryTagNames()),
tagNames -> {
//on fx thread
List<MenuItem> menuItems = transform(tagNames,
tagName -> createAutoAssigningMenuItem(tagSelectedSplitMenu, new TagSelectedFilesAction(tagName, controller)));
tagSelectedSplitMenu.getItems().setAll(menuItems);
},
throwable -> logger.log(Level.SEVERE, "Error getting tag names.", throwable)//NON-NLS
);
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller); CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller);
catSelectedSplitMenu.setOnAction(cat5SelectedAction); catSelectedSplitMenu.setOnAction(cat5SelectedAction);
catSelectedSplitMenu.setText(cat5SelectedAction.getText()); catSelectedSplitMenu.setText(cat5SelectedAction.getText());
catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic()); catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic());
catSelectedSplitMenu.showingProperty().addListener(showing -> {
if (catSelectedSplitMenu.isShowing()) { List<MenuItem> categoryMenues = transform(asList(DhsImageCategory.values()),
List<MenuItem> categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()), cat -> createAutoAssigningMenuItem(catSelectedSplitMenu, new CategorizeSelectedFilesAction(cat, controller)));
cat -> GuiUtils.createAutoAssigningMenuItem(catSelectedSplitMenu, new CategorizeSelectedFilesAction(cat, controller))); catSelectedSplitMenu.getItems().setAll(categoryMenues);
catSelectedSplitMenu.getItems().setAll(categoryMenues);
}
});
slideShowToggle.getStyleClass().remove("radio-button"); slideShowToggle.getStyleClass().remove("radio-button");
slideShowToggle.getStyleClass().add("toggle-button"); slideShowToggle.getStyleClass().add("toggle-button");
@ -681,7 +675,6 @@ public class GroupPane extends BorderPane {
} }
void makeSelection(Boolean shiftDown, Long newFileID) { void makeSelection(Boolean shiftDown, Long newFileID) {
if (shiftDown) { if (shiftDown) {
//TODO: do more hear to implement slicker multiselect //TODO: do more hear to implement slicker multiselect
int endIndex = grouping.get().getFileIDs().indexOf(newFileID); int endIndex = grouping.get().getFileIDs().indexOf(newFileID);
@ -694,7 +687,6 @@ public class GroupPane extends BorderPane {
} else { } else {
selectionAnchorIndex = null; selectionAnchorIndex = null;
selectionModel.clearAndSelect(newFileID); selectionModel.clearAndSelect(newFileID);
} }
} }

View File

@ -22,6 +22,7 @@ import com.google.common.eventbus.Subscribe;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import static java.util.Collections.singletonMap;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import static java.util.Objects.isNull; import static java.util.Objects.isNull;
@ -112,9 +113,7 @@ public class MetaDataPane extends DrawableUIBase {
}); });
copyMenuItem.setAccelerator(COPY_KEY_COMBINATION); copyMenuItem.setAccelerator(COPY_KEY_COMBINATION);
copyMenuItem.setOnAction(actionEvent -> { copyMenuItem.setOnAction(actionEvent -> copyValueToClipBoard());
copyValueToClipBoard();
});
tableView.setContextMenu(contextMenu); tableView.setContextMenu(contextMenu);
tableView.setOnKeyPressed((KeyEvent event) -> { tableView.setOnKeyPressed((KeyEvent event) -> {
@ -220,9 +219,6 @@ public class MetaDataPane extends DrawableUIBase {
return imageBorder; return imageBorder;
} }
/**
* {@inheritDoc }
*/
@Subscribe @Subscribe
@Override @Override
public void handleCategoryChanged(CategoryManager.CategoryChangeEvent evt) { public void handleCategoryChanged(CategoryManager.CategoryChangeEvent evt) {
@ -256,9 +252,9 @@ public class MetaDataPane extends DrawableUIBase {
private void copyValueToClipBoard() { private void copyValueToClipBoard() {
Pair<DrawableAttribute<?>, Collection<?>> selectedItem = tableView.getSelectionModel().getSelectedItem(); Pair<DrawableAttribute<?>, Collection<?>> selectedItem = tableView.getSelectionModel().getSelectedItem();
if (nonNull(selectedItem)) { if (nonNull(selectedItem)) {
Clipboard.getSystemClipboard().setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, Clipboard.getSystemClipboard().setContent(
getValueDisplayString(selectedItem))); singletonMap(DataFormat.PLAIN_TEXT, getValueDisplayString(selectedItem))
);
} }
} }
} }

View File

@ -131,8 +131,8 @@ public class SlideShowView extends DrawableTileBase {
getGroupPane().grouping().addListener(observable -> { getGroupPane().grouping().addListener(observable -> {
syncButtonVisibility(); syncButtonVisibility();
if (getGroupPane().getGroup() != null) { if (getGroupPane().getGroup() != null) {
getGroupPane().getGroup().getFileIDs().addListener((Observable observable1) -> getGroupPane().getGroup().getFileIDs().addListener((Observable observable1)
syncButtonVisibility()); -> syncButtonVisibility());
} }
}); });
} }
@ -215,9 +215,7 @@ public class SlideShowView extends DrawableTileBase {
mediaTask = null; mediaTask = null;
} }
}); });
myTask.setOnCancelled(cancelled -> { myTask.setOnCancelled(cancelled -> disposeContent());
disposeContent();
});
exec.execute(myTask); exec.execute(myTask);
return progressNode; return progressNode;
@ -245,7 +243,6 @@ public class SlideShowView extends DrawableTileBase {
/** /**
* *
* @param file the value of file
* @param imageTask the value of imageTask * @param imageTask the value of imageTask
*/ */
@Override @Override
@ -259,9 +256,6 @@ public class SlideShowView extends DrawableTileBase {
return maskerPane; return maskerPane;
} }
/**
* {@inheritDoc }
*/
@Override @Override
protected String getTextForLabel() { protected String getTextForLabel() {
return getFile().map(DrawableFile::getName).orElse("") + " " + getSupplementalText(); return getFile().map(DrawableFile::getName).orElse("") + " " + getSupplementalText();
@ -292,8 +286,8 @@ public class SlideShowView extends DrawableTileBase {
* of y" * of y"
*/ */
@NbBundle.Messages({"# {0} - file id number", @NbBundle.Messages({"# {0} - file id number",
"# {1} - number of file ids", "# {1} - number of file ids",
"SlideShowView.supplementalText={0} of {1} in group"}) "SlideShowView.supplementalText={0} of {1} in group"})
private String getSupplementalText() { private String getSupplementalText() {
final ObservableList<Long> fileIds = getGroupPane().getGroup().getFileIDs(); final ObservableList<Long> fileIds = getGroupPane().getGroup().getFileIDs();
return getFileID().map(fileID -> " ( " + Bundle.SlideShowView_supplementalText(fileIds.indexOf(fileID) + 1, fileIds.size()) + " )") return getFileID().map(fileID -> " ( " + Bundle.SlideShowView_supplementalText(fileIds.indexOf(fileID) + 1, fileIds.size()) + " )")
@ -301,9 +295,6 @@ public class SlideShowView extends DrawableTileBase {
} }
/**
* {@inheritDoc }
*/
@Override @Override
@ThreadConfined(type = ThreadType.ANY) @ThreadConfined(type = ThreadType.ANY)
public DhsImageCategory updateCategory() { public DhsImageCategory updateCategory() {

View File

@ -18,11 +18,16 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.utils; package org.sleuthkit.autopsy.imagegallery.utils;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.function.Consumer;
import javafx.application.Platform;
import javafx.concurrent.Task; import javafx.concurrent.Task;
/** /**
@ -42,8 +47,26 @@ public final class TaskUtils {
}; };
} }
public static ListeningExecutorService getExecutorForClass(Class<?> clazz) { public static ListeningExecutorService getExecutorForClass(Class<?> clazz) {
return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder().setNameFormat("Image Gallery " + clazz.getSimpleName() + " BG Thread").build())); new ThreadFactoryBuilder().setNameFormat("Image Gallery " + clazz.getSimpleName() + " BG Thread").build()));
} }
public static <X> void addFXCallback(ListenableFuture<X> future, Consumer<X> onSuccess, Consumer<Throwable> onFailure) {
Futures.addCallback(future, makeFutureCallBack(onSuccess, onFailure), Platform::runLater);
}
public static <X> FutureCallback< X> makeFutureCallBack(Consumer<X> onSuccess, Consumer<Throwable> onFailure) {
return new FutureCallback<X>() {
@Override
public void onSuccess(X result) {
onSuccess.accept(result);
}
@Override
public void onFailure(Throwable t) {
onFailure.accept(t);
}
};
}
} }