mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 07:56:16 +00:00
Merge branch 'develop' of https://github.com/sleuthkit/autopsy into 4167-UpdateCommonAttributeGUI
This commit is contained in:
commit
a2a0970e48
@ -329,6 +329,7 @@
|
||||
<package>org.sleuthkit.autopsy.guiutils</package>
|
||||
<package>org.sleuthkit.autopsy.healthmonitor</package>
|
||||
<package>org.sleuthkit.autopsy.ingest</package>
|
||||
<package>org.sleuthkit.autopsy.ingest.events</package>
|
||||
<package>org.sleuthkit.autopsy.keywordsearchservice</package>
|
||||
<package>org.sleuthkit.autopsy.menuactions</package>
|
||||
<package>org.sleuthkit.autopsy.modules.encryptiondetection</package>
|
||||
|
@ -107,7 +107,7 @@ public class ImageUtils {
|
||||
* NOTE: Must be cleared when the case is changed.
|
||||
*/
|
||||
@Messages({"ImageUtils.ffmpegLoadedError.title=OpenCV FFMpeg",
|
||||
"ImageUtils.ffmpegLoadedError.msg=OpenCV FFMpeg library failed to load, see log for more details"})
|
||||
"ImageUtils.ffmpegLoadedError.msg=OpenCV FFMpeg library failed to load, see log for more details"})
|
||||
private static final ConcurrentHashMap<Long, File> cacheFileMap = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
@ -218,7 +218,7 @@ public class ImageUtils {
|
||||
}
|
||||
|
||||
return VideoUtils.isVideoThumbnailSupported(file)
|
||||
|| isImageThumbnailSupported(file);
|
||||
|| isImageThumbnailSupported(file);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -413,7 +413,7 @@ public class ImageUtils {
|
||||
String cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
|
||||
return Paths.get(cacheDirectory, "thumbnails", fileID + ".png").toFile(); //NON-NLS
|
||||
} catch (NoCurrentCaseException e) {
|
||||
LOGGER.log(Level.WARNING, "Could not get cached thumbnail location. No case is open."); //NON-NLS
|
||||
LOGGER.log(Level.INFO, "Could not get cached thumbnail location. No case is open."); //NON-NLS
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
@ -23,11 +23,13 @@ import org.openide.util.NbBundle;
|
||||
import static org.sleuthkit.autopsy.directorytree.Bundle.*;
|
||||
|
||||
@NbBundle.Messages({"SelectionContext.dataSources=Data Sources",
|
||||
"SelectionContext.dataSourceFiles=Data Source Files",
|
||||
"SelectionContext.views=Views"})
|
||||
enum SelectionContext {
|
||||
DATA_SOURCES(SelectionContext_dataSources()),
|
||||
VIEWS(SelectionContext_views()),
|
||||
OTHER(""); // Subnode of another node.
|
||||
OTHER(""), // Subnode of another node.
|
||||
DATA_SOURCE_FILES(SelectionContext_dataSourceFiles());
|
||||
|
||||
private final String displayName;
|
||||
|
||||
@ -36,7 +38,7 @@ enum SelectionContext {
|
||||
}
|
||||
|
||||
public static SelectionContext getContextFromName(String name) {
|
||||
if (name.equals(DATA_SOURCES.getName())) {
|
||||
if (name.equals(DATA_SOURCES.getName()) || name.equals(DATA_SOURCE_FILES.getName())) {
|
||||
return DATA_SOURCES;
|
||||
} else if (name.equals(VIEWS.getName())) {
|
||||
return VIEWS;
|
||||
@ -64,6 +66,16 @@ enum SelectionContext {
|
||||
// One level below root node. Should be one of DataSources, Views, or Results
|
||||
return SelectionContext.getContextFromName(n.getDisplayName());
|
||||
} else {
|
||||
// In Group by Data Source mode, the node under root is the data source name, and
|
||||
// under that is Data Source Files, Views, or Results. Before moving up the tree, check
|
||||
// if one of those applies.
|
||||
if (n.getParentNode().getParentNode().getParentNode() == null) {
|
||||
SelectionContext context = SelectionContext.getContextFromName(n.getDisplayName());
|
||||
if (context != SelectionContext.OTHER) {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
return getSelectionContext(n.getParentNode());
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import org.sleuthkit.datamodel.Content;
|
||||
* Event published when analysis (ingest) of a data source included in an ingest
|
||||
* job is completed.
|
||||
*/
|
||||
public class DataSourceAnalysisCompletedEvent extends DataSourceAnalysisEvent implements Serializable {
|
||||
public final class DataSourceAnalysisCompletedEvent extends DataSourceAnalysisEvent implements Serializable {
|
||||
|
||||
/**
|
||||
* The reason why the analysis of the data source completed.
|
||||
|
@ -26,7 +26,7 @@ import org.sleuthkit.datamodel.Content;
|
||||
* Event published when analysis (ingest) of a data source included in an ingest
|
||||
* job is started.
|
||||
*/
|
||||
public class DataSourceAnalysisStartedEvent extends DataSourceAnalysisEvent implements Serializable {
|
||||
public final class DataSourceAnalysisStartedEvent extends DataSourceAnalysisEvent implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015-17 Basis Technology Corp.
|
||||
* Copyright 2015-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -56,9 +56,7 @@ public final class PromptDialogManager {
|
||||
@NbBundle.Messages("PrompDialogManager.buttonType.update=Update DB")
|
||||
private static final ButtonType UPDATE = new ButtonType(Bundle.PrompDialogManager_buttonType_update(), ButtonBar.ButtonData.OK_DONE);
|
||||
|
||||
/**
|
||||
* Image to use as title bar icon in dialogs
|
||||
*/
|
||||
/** Image to use as title bar icon in dialogs */
|
||||
private static final Image AUTOPSY_ICON;
|
||||
|
||||
static {
|
||||
@ -222,7 +220,7 @@ public final class PromptDialogManager {
|
||||
dialog.setHeaderText(Bundle.PromptDialogManager_showTooManyFiles_headerText());
|
||||
dialog.showAndWait();
|
||||
}
|
||||
|
||||
|
||||
@NbBundle.Messages({
|
||||
"PromptDialogManager.showTimeLineDisabledMessage.contentText="
|
||||
+ "Timeline functionality is not available yet."
|
||||
|
@ -230,6 +230,7 @@
|
||||
<package>org.apache.commons.codec.digest</package>
|
||||
<package>org.apache.commons.codec.language</package>
|
||||
<package>org.apache.commons.codec.net</package>
|
||||
<package>org.apache.commons.collections4</package>
|
||||
<package>org.apache.commons.csv</package>
|
||||
<package>org.apache.commons.io</package>
|
||||
<package>org.apache.commons.io.comparator</package>
|
||||
|
@ -21,21 +21,24 @@ package org.sleuthkit.autopsy.imagegallery;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||
import javafx.beans.property.ReadOnlyLongWrapper;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
@ -43,25 +46,14 @@ import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.annotation.Nonnull;
|
||||
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
||||
import org.netbeans.api.progress.ProgressHandle;
|
||||
import org.openide.util.Cancellable;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.Case.CaseType;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.core.RuntimeProperties;
|
||||
import org.sleuthkit.autopsy.coreutils.History;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
@ -69,18 +61,18 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.imagegallery.actions.UndoRedoManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB.DrawableDbBuildStatusEnum;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.HashSetManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
@ -90,8 +82,7 @@ import org.sleuthkit.datamodel.TskData;
|
||||
*/
|
||||
public final class ImageGalleryController {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(ImageGalleryController.class.getName());
|
||||
private static ImageGalleryController instance;
|
||||
private static final Logger logger = Logger.getLogger(ImageGalleryController.class.getName());
|
||||
|
||||
/**
|
||||
* true if Image Gallery should listen to ingest events, false if it should
|
||||
@ -103,7 +94,7 @@ public final class ImageGalleryController {
|
||||
private final ReadOnlyBooleanWrapper stale = new ReadOnlyBooleanWrapper(false);
|
||||
|
||||
private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false);
|
||||
private final ReadOnlyDoubleWrapper thumbnailSize = new ReadOnlyDoubleWrapper(100);
|
||||
private final SimpleDoubleProperty thumbnailSizeProp = new SimpleDoubleProperty(100);
|
||||
private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false);
|
||||
private final ReadOnlyIntegerWrapper dbTaskQueueSize = new ReadOnlyIntegerWrapper(0);
|
||||
|
||||
@ -111,36 +102,23 @@ public final class ImageGalleryController {
|
||||
|
||||
private final History<GroupViewState> historyManager = new History<>();
|
||||
private final UndoRedoManager undoManager = new UndoRedoManager();
|
||||
private final GroupManager groupManager = new GroupManager(this);
|
||||
private final HashSetManager hashSetManager = new HashSetManager();
|
||||
private final CategoryManager categoryManager = new CategoryManager(this);
|
||||
private final DrawableTagsManager tagsManager = new DrawableTagsManager(null);
|
||||
|
||||
private Runnable showTree;
|
||||
private Toolbar toolbar;
|
||||
private StackPane fullUIStackPane;
|
||||
private StackPane centralStackPane;
|
||||
private Node infoOverlay;
|
||||
private final Region infoOverLayBackground = new Region() {
|
||||
{
|
||||
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
setOpacity(.4);
|
||||
}
|
||||
};
|
||||
private final ThumbnailCache thumbnailCache = new ThumbnailCache(this);
|
||||
private final GroupManager groupManager;
|
||||
private final HashSetManager hashSetManager;
|
||||
private final CategoryManager categoryManager;
|
||||
private final DrawableTagsManager tagsManager;
|
||||
|
||||
private ListeningExecutorService dbExecutor;
|
||||
|
||||
private SleuthkitCase sleuthKitCase;
|
||||
private DrawableDB db;
|
||||
private final Case autopsyCase;
|
||||
private final SleuthkitCase sleuthKitCase;
|
||||
private final DrawableDB drawableDB;
|
||||
|
||||
public static synchronized ImageGalleryController getDefault() {
|
||||
if (instance == null) {
|
||||
instance = new ImageGalleryController();
|
||||
}
|
||||
return instance;
|
||||
public Case getAutopsyCase() {
|
||||
return autopsyCase;
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty getMetaDataCollapsed() {
|
||||
public ReadOnlyBooleanProperty metaDataCollapsedProperty() {
|
||||
return metaDataCollapsed.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@ -148,19 +126,19 @@ public final class ImageGalleryController {
|
||||
this.metaDataCollapsed.set(metaDataCollapsed);
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleProperty thumbnailSizeProperty() {
|
||||
return thumbnailSize.getReadOnlyProperty();
|
||||
public DoubleProperty thumbnailSizeProperty() {
|
||||
return thumbnailSizeProp;
|
||||
}
|
||||
|
||||
private GroupViewState getViewState() {
|
||||
public GroupViewState getViewState() {
|
||||
return historyManager.getCurrentState();
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty regroupDisabled() {
|
||||
public ReadOnlyBooleanProperty regroupDisabledProperty() {
|
||||
return regroupDisabled.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<GroupViewState> viewState() {
|
||||
public ReadOnlyObjectProperty<GroupViewState> viewStateProperty() {
|
||||
return historyManager.currentState();
|
||||
}
|
||||
|
||||
@ -172,8 +150,8 @@ public final class ImageGalleryController {
|
||||
return groupManager;
|
||||
}
|
||||
|
||||
synchronized public DrawableDB getDatabase() {
|
||||
return db;
|
||||
public DrawableDB getDatabase() {
|
||||
return drawableDB;
|
||||
}
|
||||
|
||||
public void setListeningEnabled(boolean enabled) {
|
||||
@ -193,14 +171,9 @@ public final class ImageGalleryController {
|
||||
Platform.runLater(() -> {
|
||||
stale.set(b);
|
||||
});
|
||||
try {
|
||||
new PerCaseProperties(Case.getCurrentCaseThrows()).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.STALE, b.toString());
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
Logger.getLogger(ImageGalleryController.class.getName()).log(Level.WARNING, "Exception while getting open case."); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty stale() {
|
||||
public ReadOnlyBooleanProperty staleProperty() {
|
||||
return stale.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@ -209,50 +182,57 @@ public final class ImageGalleryController {
|
||||
return stale.get();
|
||||
}
|
||||
|
||||
private ImageGalleryController() {
|
||||
ImageGalleryController(@Nonnull Case newCase) throws TskCoreException {
|
||||
|
||||
listeningEnabled.addListener((observable, oldValue, newValue) -> {
|
||||
this.autopsyCase = Objects.requireNonNull(newCase);
|
||||
this.sleuthKitCase = newCase.getSleuthkitCase();
|
||||
|
||||
setListeningEnabled(ImageGalleryModule.isEnabledforCase(newCase));
|
||||
|
||||
groupManager = new GroupManager(this);
|
||||
this.drawableDB = DrawableDB.getDrawableDB(this);
|
||||
categoryManager = new CategoryManager(this);
|
||||
tagsManager = new DrawableTagsManager(this);
|
||||
tagsManager.registerListener(groupManager);
|
||||
tagsManager.registerListener(categoryManager);
|
||||
|
||||
hashSetManager = new HashSetManager(drawableDB);
|
||||
setStale(isDataSourcesTableStale());
|
||||
|
||||
dbExecutor = getNewDBExecutor();
|
||||
|
||||
// listener for the boolean property about when IG is listening / enabled
|
||||
listeningEnabled.addListener((observable, wasPreviouslyEnabled, isEnabled) -> {
|
||||
try {
|
||||
//if we just turned on listening and a case is open and that case is not up to date
|
||||
if (newValue && !oldValue && ImageGalleryModule.isDrawableDBStale(Case.getCurrentCaseThrows())) {
|
||||
// if we just turned on listening and a single-user case is open and that case is not up to date, then rebuild it
|
||||
// For multiuser cases, we defer DB rebuild till the user actually opens Image Gallery
|
||||
if (isEnabled && !wasPreviouslyEnabled
|
||||
&& isDataSourcesTableStale()
|
||||
&& (Case.getCurrentCaseThrows().getCaseType() == CaseType.SINGLE_USER_CASE)) {
|
||||
//populate the db
|
||||
queueDBTask(new CopyAnalyzedFiles(instance, db, sleuthKitCase));
|
||||
this.rebuildDB();
|
||||
}
|
||||
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
LOGGER.log(Level.WARNING, "Exception while getting open case.", ex);
|
||||
logger.log(Level.WARNING, "Exception while getting open case.", ex);
|
||||
}
|
||||
});
|
||||
|
||||
groupManager.getAnalyzedGroups().addListener((Observable o) -> {
|
||||
//analyzed groups is confined to JFX thread
|
||||
if (Case.isCaseOpen()) {
|
||||
checkForGroups();
|
||||
}
|
||||
});
|
||||
|
||||
groupManager.getUnSeenGroups().addListener((Observable observable) -> {
|
||||
//if there are unseen groups and none being viewed
|
||||
if (groupManager.getUnSeenGroups().isEmpty() == false && (getViewState() == null || getViewState().getGroup() == null)) {
|
||||
advance(GroupViewState.tile(groupManager.getUnSeenGroups().get(0)), true);
|
||||
}
|
||||
});
|
||||
|
||||
viewState().addListener((Observable observable) -> {
|
||||
viewStateProperty().addListener((Observable observable) -> {
|
||||
//when the viewed group changes, clear the selection and the undo/redo history
|
||||
selectionModel.clearSelection();
|
||||
undoManager.clear();
|
||||
});
|
||||
|
||||
regroupDisabled.addListener(observable -> checkForGroups());
|
||||
|
||||
IngestManager ingestManager = IngestManager.getInstance();
|
||||
PropertyChangeListener ingestEventHandler =
|
||||
propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled);
|
||||
PropertyChangeListener ingestEventHandler
|
||||
= propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled);
|
||||
|
||||
ingestManager.addIngestModuleEventListener(ingestEventHandler);
|
||||
ingestManager.addIngestJobEventListener(ingestEventHandler);
|
||||
|
||||
dbTaskQueueSize.addListener(obs -> this.updateRegroupDisabled());
|
||||
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty getCanAdvance() {
|
||||
@ -264,10 +244,7 @@ public final class ImageGalleryController {
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
||||
public void advance(GroupViewState newState, boolean forceShowTree) {
|
||||
if (forceShowTree && showTree != null) {
|
||||
showTree.run();
|
||||
}
|
||||
public void advance(GroupViewState newState) {
|
||||
historyManager.advance(newState);
|
||||
}
|
||||
|
||||
@ -284,131 +261,92 @@ public final class ImageGalleryController {
|
||||
regroupDisabled.set((dbTaskQueueSize.get() > 0) || IngestManager.getInstance().isIngestRunning());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any fully analyzed groups available from the
|
||||
* GroupManager and remove blocking progress spinners if there are. If there
|
||||
* aren't, add a blocking progress spinner with appropriate message.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
@NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. "
|
||||
+ " No groups will be available until ingest is finished and listening is re-enabled.",
|
||||
"ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.",
|
||||
"ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.",
|
||||
"ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. "
|
||||
+ " No groups will be available until ingest is finished and listening is re-enabled.",
|
||||
"ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.",
|
||||
"ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:"
|
||||
+ " the current Group By setting resulted in no groups, "
|
||||
+ "or no groups are fully analyzed but ingest is not running."})
|
||||
synchronized private void checkForGroups() {
|
||||
if (groupManager.getAnalyzedGroups().isEmpty()) {
|
||||
if (IngestManager.getInstance().isIngestRunning()) {
|
||||
if (listeningEnabled.not().get()) {
|
||||
replaceNotification(fullUIStackPane,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1()));
|
||||
} else {
|
||||
replaceNotification(fullUIStackPane,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(),
|
||||
new ProgressIndicator()));
|
||||
}
|
||||
|
||||
} else if (dbTaskQueueSize.get() > 0) {
|
||||
replaceNotification(fullUIStackPane,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(),
|
||||
new ProgressIndicator()));
|
||||
} else if (db != null && db.countAllFiles() <= 0) { // there are no files in db
|
||||
if (listeningEnabled.not().get()) {
|
||||
replaceNotification(fullUIStackPane,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4()));
|
||||
} else {
|
||||
replaceNotification(fullUIStackPane,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5()));
|
||||
}
|
||||
|
||||
} else if (!groupManager.isRegrouping()) {
|
||||
replaceNotification(centralStackPane,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6()));
|
||||
}
|
||||
|
||||
} else {
|
||||
clearNotification();
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private void clearNotification() {
|
||||
//remove the ingest spinner
|
||||
if (fullUIStackPane != null) {
|
||||
fullUIStackPane.getChildren().remove(infoOverlay);
|
||||
}
|
||||
//remove the ingest spinner
|
||||
if (centralStackPane != null) {
|
||||
centralStackPane.getChildren().remove(infoOverlay);
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private void replaceNotification(StackPane stackPane, Node newNode) {
|
||||
clearNotification();
|
||||
|
||||
infoOverlay = new StackPane(infoOverLayBackground, newNode);
|
||||
if (stackPane != null) {
|
||||
stackPane.getChildren().add(infoOverlay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* configure the controller for a specific case.
|
||||
*
|
||||
* @param theNewCase the case to configure the controller for
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public synchronized void setCase(Case theNewCase) {
|
||||
if (null == theNewCase) {
|
||||
reset();
|
||||
} else {
|
||||
this.sleuthKitCase = theNewCase.getSleuthkitCase();
|
||||
this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), this);
|
||||
|
||||
setListeningEnabled(ImageGalleryModule.isEnabledforCase(theNewCase));
|
||||
setStale(ImageGalleryModule.isDrawableDBStale(theNewCase));
|
||||
|
||||
// if we add this line icons are made as files are analyzed rather than on demand.
|
||||
// db.addUpdatedFileListener(IconCache.getDefault());
|
||||
historyManager.clear();
|
||||
groupManager.setDB(db);
|
||||
hashSetManager.setDb(db);
|
||||
categoryManager.setDb(db);
|
||||
tagsManager.setAutopsyTagsManager(theNewCase.getServices().getTagsManager());
|
||||
tagsManager.registerListener(groupManager);
|
||||
tagsManager.registerListener(categoryManager);
|
||||
shutDownDBExecutor();
|
||||
dbExecutor = getNewDBExecutor();
|
||||
}
|
||||
/**
|
||||
* Rebuilds the DrawableDB database.
|
||||
*
|
||||
*/
|
||||
public void rebuildDB() {
|
||||
// queue a rebuild task for each stale data source
|
||||
getStaleDataSourceIds().forEach(dataSourceObjId -> queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* reset the state of the controller (eg if the case is closed)
|
||||
*/
|
||||
public synchronized void reset() {
|
||||
LOGGER.info("resetting ImageGalleryControler to initial state."); //NON-NLS
|
||||
logger.info("Closing ImageGalleryControler for case."); //NON-NLS
|
||||
|
||||
selectionModel.clearSelection();
|
||||
setListeningEnabled(false);
|
||||
ThumbnailCache.getDefault().clearCache();
|
||||
thumbnailCache.clearCache();
|
||||
historyManager.clear();
|
||||
groupManager.clear();
|
||||
tagsManager.clearFollowUpTagName();
|
||||
tagsManager.unregisterListener(groupManager);
|
||||
tagsManager.unregisterListener(categoryManager);
|
||||
groupManager.reset();
|
||||
|
||||
shutDownDBExecutor();
|
||||
dbExecutor = getNewDBExecutor();
|
||||
}
|
||||
|
||||
if (toolbar != null) {
|
||||
toolbar.reset();
|
||||
/**
|
||||
* Checks if the datasources table in drawable DB is stale.
|
||||
*
|
||||
* @return true if datasources table is stale
|
||||
*/
|
||||
public boolean isDataSourcesTableStale() {
|
||||
return isNotEmpty(getStaleDataSourceIds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of data source object ids that are stale.
|
||||
*
|
||||
* This includes any data sources already in the table, that are not in
|
||||
* COMPLETE status, or any data sources that might have been added to the
|
||||
* case, but are not in the datasources table.
|
||||
*
|
||||
* @return list of data source object ids that are stale.
|
||||
*/
|
||||
Set<Long> getStaleDataSourceIds() {
|
||||
|
||||
Set<Long> staleDataSourceIds = new HashSet<>();
|
||||
|
||||
// no current case open to check
|
||||
if ((null == getDatabase()) || (null == getSleuthKitCase())) {
|
||||
return staleDataSourceIds;
|
||||
}
|
||||
|
||||
if (db != null) {
|
||||
db.closeDBCon();
|
||||
try {
|
||||
Map<Long, DrawableDbBuildStatusEnum> knownDataSourceIds = getDatabase().getDataSourceDbBuildStatus();
|
||||
|
||||
List<DataSource> dataSources = getSleuthKitCase().getDataSources();
|
||||
Set<Long> caseDataSourceIds = new HashSet<>();
|
||||
dataSources.stream().map(DataSource::getId).forEach(caseDataSourceIds::add);
|
||||
|
||||
// collect all data sources already in the table, that are not yet COMPLETE
|
||||
knownDataSourceIds.entrySet().stream().forEach((Map.Entry<Long, DrawableDbBuildStatusEnum> t) -> {
|
||||
DrawableDbBuildStatusEnum status = t.getValue();
|
||||
if (DrawableDbBuildStatusEnum.COMPLETE != status) {
|
||||
staleDataSourceIds.add(t.getKey());
|
||||
}
|
||||
});
|
||||
|
||||
// collect any new data sources in the case.
|
||||
caseDataSourceIds.forEach((Long id) -> {
|
||||
if (!knownDataSourceIds.containsKey(id)) {
|
||||
staleDataSourceIds.add(id);
|
||||
}
|
||||
});
|
||||
|
||||
return staleDataSourceIds;
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Image Gallery failed to check if datasources table is stale.", ex);
|
||||
return staleDataSourceIds;
|
||||
}
|
||||
db = null;
|
||||
|
||||
}
|
||||
|
||||
synchronized private void shutDownDBExecutor() {
|
||||
@ -417,7 +355,7 @@ public final class ImageGalleryController {
|
||||
try {
|
||||
dbExecutor.awaitTermination(30, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException ex) {
|
||||
LOGGER.log(Level.WARNING, "Image Gallery failed to shutdown DB Task Executor in a timely fashion.", ex);
|
||||
logger.log(Level.WARNING, "Image Gallery failed to shutdown DB Task Executor in a timely fashion.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -449,46 +387,14 @@ public final class ImageGalleryController {
|
||||
Platform.runLater(() -> dbTaskQueueSize.set(dbTaskQueueSize.get() - 1));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
synchronized public DrawableFile getFileFromId(Long fileID) throws TskCoreException {
|
||||
if (Objects.isNull(db)) {
|
||||
LOGGER.log(Level.WARNING, "Could not get file from id, no DB set. The case is probably closed."); //NON-NLS
|
||||
return null;
|
||||
}
|
||||
return db.getFileFromID(fileID);
|
||||
}
|
||||
|
||||
public void setStacks(StackPane fullUIStack, StackPane centralStack) {
|
||||
fullUIStackPane = fullUIStack;
|
||||
this.centralStackPane = centralStack;
|
||||
Platform.runLater(this::checkForGroups);
|
||||
}
|
||||
|
||||
public synchronized void setToolbar(Toolbar toolbar) {
|
||||
if (this.toolbar != null) {
|
||||
throw new IllegalStateException("Can not set the toolbar a second time!");
|
||||
}
|
||||
this.toolbar = toolbar;
|
||||
thumbnailSize.bind(toolbar.thumbnailSizeProperty());
|
||||
public DrawableFile getFileFromID(Long fileID) throws TskCoreException {
|
||||
return drawableDB.getFileFromID(fileID);
|
||||
}
|
||||
|
||||
public ReadOnlyDoubleProperty regroupProgress() {
|
||||
return groupManager.regroupProgress();
|
||||
}
|
||||
|
||||
/**
|
||||
* invoked by {@link OnStart} to make sure that the ImageGallery listeners
|
||||
* get setup as early as possible, and do other setup stuff.
|
||||
*/
|
||||
void onStart() {
|
||||
Platform.setImplicitExit(false);
|
||||
LOGGER.info("setting up ImageGallery listeners"); //NON-NLS
|
||||
//TODO can we do anything usefull in an InjestJobEventListener?
|
||||
//IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> {});
|
||||
IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener());
|
||||
Case.addPropertyChangeListener(new CaseEventListener());
|
||||
}
|
||||
|
||||
public HashSetManager getHashSetManager() {
|
||||
return hashSetManager;
|
||||
}
|
||||
@ -501,10 +407,6 @@ public final class ImageGalleryController {
|
||||
return tagsManager;
|
||||
}
|
||||
|
||||
public void setShowTree(Runnable showTree) {
|
||||
this.showTree = showTree;
|
||||
}
|
||||
|
||||
public UndoRedoManager getUndoManager() {
|
||||
return undoManager;
|
||||
}
|
||||
@ -515,6 +417,12 @@ public final class ImageGalleryController {
|
||||
|
||||
public synchronized SleuthkitCase getSleuthKitCase() {
|
||||
return sleuthKitCase;
|
||||
|
||||
}
|
||||
|
||||
public ThumbnailCache getThumbsCache() {
|
||||
return thumbnailCache;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -594,7 +502,7 @@ public final class ImageGalleryController {
|
||||
return file;
|
||||
}
|
||||
|
||||
public FileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
FileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
super();
|
||||
this.file = f;
|
||||
this.taskDB = taskDB;
|
||||
@ -604,7 +512,7 @@ public final class ImageGalleryController {
|
||||
/**
|
||||
* task that updates one file in database with results from ingest
|
||||
*/
|
||||
static private class UpdateFileTask extends FileTask {
|
||||
static class UpdateFileTask extends FileTask {
|
||||
|
||||
UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
super(f, taskDB);
|
||||
@ -631,7 +539,7 @@ public final class ImageGalleryController {
|
||||
/**
|
||||
* task that updates one file in database with results from ingest
|
||||
*/
|
||||
static private class RemoveFileTask extends FileTask {
|
||||
static class RemoveFileTask extends FileTask {
|
||||
|
||||
RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||
super(f, taskDB);
|
||||
@ -654,51 +562,74 @@ public final class ImageGalleryController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base abstract class for various methods of copying image files data, for
|
||||
* a given data source, into the Image gallery DB.
|
||||
*/
|
||||
@NbBundle.Messages({"BulkTask.committingDb.status=committing image/video database",
|
||||
"BulkTask.stopCopy.status=Stopping copy to drawable db task.",
|
||||
"BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."})
|
||||
/* Base abstract class for various methods of copying data into the Image gallery DB */
|
||||
abstract static private class BulkTransferTask extends BackgroundTask {
|
||||
abstract static class BulkTransferTask extends BackgroundTask {
|
||||
|
||||
static private final String FILE_EXTENSION_CLAUSE =
|
||||
"(name LIKE '%." //NON-NLS
|
||||
+ String.join("' OR name LIKE '%.", FileTypeUtils.getAllSupportedExtensions()) //NON-NLS
|
||||
+ "')";
|
||||
static private final String FILE_EXTENSION_CLAUSE
|
||||
= "(extension LIKE '" //NON-NLS
|
||||
+ String.join("' OR extension LIKE '", FileTypeUtils.getAllSupportedExtensions()) //NON-NLS
|
||||
+ "') ";
|
||||
|
||||
static private final String MIMETYPE_CLAUSE =
|
||||
"(mime_type LIKE '" //NON-NLS
|
||||
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
|
||||
+ "') ";
|
||||
static private final String MIMETYPE_CLAUSE
|
||||
= "(mime_type LIKE '" //NON-NLS
|
||||
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
|
||||
+ "') ";
|
||||
|
||||
static final String DRAWABLE_QUERY =
|
||||
//grab files with supported extension
|
||||
"(" + FILE_EXTENSION_CLAUSE
|
||||
//grab files with supported mime-types
|
||||
+ " OR " + MIMETYPE_CLAUSE //NON-NLS
|
||||
//grab files with image or video mime-types even if we don't officially support them
|
||||
+ " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS
|
||||
private final String DRAWABLE_QUERY;
|
||||
private final String DATASOURCE_CLAUSE;
|
||||
|
||||
final ImageGalleryController controller;
|
||||
final DrawableDB taskDB;
|
||||
final SleuthkitCase tskCase;
|
||||
protected final ImageGalleryController controller;
|
||||
protected final DrawableDB taskDB;
|
||||
protected final SleuthkitCase tskCase;
|
||||
protected final long dataSourceObjId;
|
||||
|
||||
ProgressHandle progressHandle;
|
||||
private ProgressHandle progressHandle;
|
||||
private boolean taskCompletionStatus;
|
||||
|
||||
BulkTransferTask(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||
BulkTransferTask(long dataSourceObjId, ImageGalleryController controller) {
|
||||
this.controller = controller;
|
||||
this.taskDB = taskDB;
|
||||
this.tskCase = tskCase;
|
||||
this.taskDB = controller.getDatabase();
|
||||
this.tskCase = controller.getSleuthKitCase();
|
||||
this.dataSourceObjId = dataSourceObjId;
|
||||
|
||||
DATASOURCE_CLAUSE = " (data_source_obj_id = " + dataSourceObjId + ") ";
|
||||
|
||||
DRAWABLE_QUERY
|
||||
= DATASOURCE_CLAUSE
|
||||
+ " AND ( "
|
||||
+ //grab files with supported extension
|
||||
FILE_EXTENSION_CLAUSE
|
||||
//grab files with supported mime-types
|
||||
+ " OR " + MIMETYPE_CLAUSE //NON-NLS
|
||||
//grab files with image or video mime-types even if we don't officially support them
|
||||
+ " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Do any cleanup for this task.
|
||||
*
|
||||
* @param success true if the transfer was successful
|
||||
*/
|
||||
abstract void cleanup(boolean success);
|
||||
|
||||
abstract List<AbstractFile> getFiles() throws TskCoreException;
|
||||
abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) throws TskCoreException;
|
||||
|
||||
abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) throws TskCoreException;
|
||||
/**
|
||||
* Gets a list of files to process.
|
||||
*
|
||||
* @return list of files to process
|
||||
*
|
||||
* @throws TskCoreException
|
||||
*/
|
||||
List<AbstractFile> getFiles() throws TskCoreException {
|
||||
return tskCase.findAllFilesWhere(DRAWABLE_QUERY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
@ -706,24 +637,32 @@ public final class ImageGalleryController {
|
||||
progressHandle.start();
|
||||
updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status());
|
||||
|
||||
DrawableDB.DrawableTransaction drawableDbTransaction = null;
|
||||
CaseDbTransaction caseDbTransaction = null;
|
||||
try {
|
||||
//grab all files with supported extension or detected mime types
|
||||
final List<AbstractFile> files = getFiles();
|
||||
progressHandle.switchToDeterminate(files.size());
|
||||
|
||||
taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS);
|
||||
|
||||
updateProgress(0.0);
|
||||
taskCompletionStatus = true;
|
||||
int workDone = 0;
|
||||
|
||||
//do in transaction
|
||||
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
|
||||
int workDone = 0;
|
||||
drawableDbTransaction = taskDB.beginTransaction();
|
||||
caseDbTransaction = tskCase.beginTransaction();
|
||||
for (final AbstractFile f : files) {
|
||||
if (isCancelled() || Thread.interrupted()) {
|
||||
LOGGER.log(Level.WARNING, "Task cancelled: not all contents may be transfered to drawable database."); //NON-NLS
|
||||
logger.log(Level.WARNING, "Task cancelled or interrupted: not all contents may be transfered to drawable database."); //NON-NLS
|
||||
taskCompletionStatus = false;
|
||||
progressHandle.finish();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
processFile(f, tr);
|
||||
processFile(f, drawableDbTransaction, caseDbTransaction);
|
||||
|
||||
workDone++;
|
||||
progressHandle.progress(f.getName(), workDone);
|
||||
@ -737,23 +676,42 @@ public final class ImageGalleryController {
|
||||
updateProgress(1.0);
|
||||
|
||||
progressHandle.start();
|
||||
taskDB.commitTransaction(tr, true);
|
||||
caseDbTransaction.commit();
|
||||
taskDB.commitTransaction(drawableDbTransaction, true);
|
||||
|
||||
} catch (TskCoreException ex) {
|
||||
if (null != drawableDbTransaction) {
|
||||
taskDB.rollbackTransaction(drawableDbTransaction);
|
||||
}
|
||||
if (null != caseDbTransaction) {
|
||||
try {
|
||||
caseDbTransaction.rollback();
|
||||
} catch (TskCoreException ex2) {
|
||||
logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
progressHandle.progress(Bundle.BulkTask_stopCopy_status());
|
||||
LOGGER.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS
|
||||
logger.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.warn(Bundle.BulkTask_errPopulating_errMsg(), ex.getMessage());
|
||||
cleanup(false);
|
||||
return;
|
||||
} finally {
|
||||
progressHandle.finish();
|
||||
if (taskCompletionStatus) {
|
||||
taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.COMPLETE);
|
||||
}
|
||||
updateMessage("");
|
||||
updateProgress(-1.0);
|
||||
}
|
||||
cleanup(true);
|
||||
cleanup(taskCompletionStatus);
|
||||
}
|
||||
|
||||
abstract ProgressHandle getInitialProgressHandle();
|
||||
|
||||
protected void setTaskCompletionStatus(boolean status) {
|
||||
taskCompletionStatus = status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -766,24 +724,21 @@ public final class ImageGalleryController {
|
||||
@NbBundle.Messages({"CopyAnalyzedFiles.committingDb.status=committing image/video database",
|
||||
"CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.",
|
||||
"CopyAnalyzedFiles.errPopulating.errMsg=There was an error populating Image Gallery database."})
|
||||
static private class CopyAnalyzedFiles extends BulkTransferTask {
|
||||
static class CopyAnalyzedFiles extends BulkTransferTask {
|
||||
|
||||
CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||
super(controller, taskDB, tskCase);
|
||||
CopyAnalyzedFiles(long dataSourceObjId, ImageGalleryController controller) {
|
||||
super(dataSourceObjId, controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(boolean success) {
|
||||
controller.setStale(!success);
|
||||
// at the end of the task, set the stale status based on the
|
||||
// cumulative status of all data sources
|
||||
controller.setStale(controller.isDataSourcesTableStale());
|
||||
}
|
||||
|
||||
@Override
|
||||
List<AbstractFile> getFiles() throws TskCoreException {
|
||||
return tskCase.findAllFilesWhere(DRAWABLE_QUERY);
|
||||
}
|
||||
|
||||
@Override
|
||||
void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr) {
|
||||
void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDbTransaction) throws TskCoreException {
|
||||
final boolean known = f.getKnown() == TskData.FileKnown.KNOWN;
|
||||
|
||||
if (known) {
|
||||
@ -791,13 +746,21 @@ public final class ImageGalleryController {
|
||||
} else {
|
||||
|
||||
try {
|
||||
if (FileTypeUtils.hasDrawableMIMEType(f)) { //supported mimetype => analyzed
|
||||
taskDB.updateFile(DrawableFile.create(f, true, false), tr);
|
||||
} else { //unsupported mimtype => analyzed but shouldn't include
|
||||
taskDB.removeFile(f.getId(), tr);
|
||||
//supported mimetype => analyzed
|
||||
if (null != f.getMIMEType() && FileTypeUtils.hasDrawableMIMEType(f)) {
|
||||
taskDB.updateFile(DrawableFile.create(f, true, false), tr, caseDbTransaction);
|
||||
} else {
|
||||
// if mimetype of the file hasn't been ascertained, ingest might not have completed yet.
|
||||
if (null == f.getMIMEType()) {
|
||||
// set to false to force the DB to be marked as stale
|
||||
this.setTaskCompletionStatus(false);
|
||||
} else {
|
||||
//unsupported mimtype => analyzed but shouldn't include
|
||||
taskDB.removeFile(f.getId(), tr);
|
||||
}
|
||||
}
|
||||
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
throw new TskCoreException("Failed to initialize FileTypeDetector.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -813,24 +776,17 @@ public final class ImageGalleryController {
|
||||
* Copy files from a newly added data source into the DB. Get all "drawable"
|
||||
* files, based on extension and mime-type. After ingest we use file type id
|
||||
* module and if necessary jpeg/png signature matching to add/remove files
|
||||
*
|
||||
* TODO: create methods to simplify progress value/text updates to both
|
||||
* netbeans and ImageGallery progress/status
|
||||
*/
|
||||
@NbBundle.Messages({"PrePopulateDataSourceFiles.committingDb.status=committing image/video database"})
|
||||
static private class PrePopulateDataSourceFiles extends BulkTransferTask {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(PrePopulateDataSourceFiles.class.getName());
|
||||
|
||||
private final Content dataSource;
|
||||
static class PrePopulateDataSourceFiles extends BulkTransferTask {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dataSourceId Data source object ID
|
||||
* @param dataSourceObjId The object ID of the DataSource that is being
|
||||
* pre-populated into the DrawableDB.
|
||||
* @param controller The controller for this task.
|
||||
*/
|
||||
PrePopulateDataSourceFiles(Content dataSource, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
||||
super(controller, taskDB, tskCase);
|
||||
this.dataSource = dataSource;
|
||||
PrePopulateDataSourceFiles(long dataSourceObjId, ImageGalleryController controller) {
|
||||
super(dataSourceObjId, controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -838,14 +794,8 @@ public final class ImageGalleryController {
|
||||
}
|
||||
|
||||
@Override
|
||||
void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) {
|
||||
taskDB.insertFile(DrawableFile.create(f, false, false), tr);
|
||||
}
|
||||
|
||||
@Override
|
||||
List<AbstractFile> getFiles() throws TskCoreException {
|
||||
long datasourceID = dataSource.getDataSource().getId();
|
||||
return tskCase.findAllFilesWhere("data_source_obj_id = " + datasourceID + " AND " + DRAWABLE_QUERY);
|
||||
void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) {
|
||||
taskDB.insertFile(DrawableFile.create(f, false, false), tr, caseDBTransaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -854,116 +804,4 @@ public final class ImageGalleryController {
|
||||
return ProgressHandle.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this);
|
||||
}
|
||||
}
|
||||
|
||||
private class IngestModuleEventListener implements PropertyChangeListener {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if (RuntimeProperties.runningWithGUI() == false) {
|
||||
/*
|
||||
* Running in "headless" mode, no need to process any events.
|
||||
* This cannot be done earlier because the switch to core
|
||||
* components inactive may not have been made at start up.
|
||||
*/
|
||||
IngestManager.getInstance().removeIngestModuleEventListener(this);
|
||||
return;
|
||||
}
|
||||
switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
|
||||
case CONTENT_CHANGED:
|
||||
//TODO: do we need to do anything here? -jm
|
||||
case DATA_ADDED:
|
||||
/*
|
||||
* we could listen to DATA events and progressivly update
|
||||
* files, and get data from DataSource ingest modules, but
|
||||
* given that most modules don't post new artifacts in the
|
||||
* events and we would have to query for them, without
|
||||
* knowing which are the new ones, we just ignore these
|
||||
* events for now. The relevant data should all be captured
|
||||
* by file done event, anyways -jm
|
||||
*/
|
||||
break;
|
||||
case FILE_DONE:
|
||||
/**
|
||||
* getOldValue has fileID getNewValue has
|
||||
* {@link Abstractfile}
|
||||
*/
|
||||
|
||||
AbstractFile file = (AbstractFile) evt.getNewValue();
|
||||
|
||||
if (isListeningEnabled()) {
|
||||
if (file.isFile()) {
|
||||
try {
|
||||
synchronized (ImageGalleryController.this) {
|
||||
if (ImageGalleryModule.isDrawableAndNotKnown(file)) {
|
||||
//this file should be included and we don't already know about it from hash sets (NSRL)
|
||||
queueDBTask(new UpdateFileTask(file, db));
|
||||
} else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) {
|
||||
//doing this check results in fewer tasks queued up, and faster completion of db update
|
||||
//this file would have gotten scooped up in initial grab, but actually we don't need it
|
||||
queueDBTask(new RemoveFileTask(file, db));
|
||||
}
|
||||
}
|
||||
} catch (TskCoreException | FileTypeDetector.FileTypeDetectorInitException ex) {
|
||||
//TODO: What to do here?
|
||||
LOGGER.log(Level.SEVERE, "Unable to determine if file is drawable and not known. Not making any changes to DB", ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error("Image Gallery Error",
|
||||
"Unable to determine if file is drawable and not known. Not making any changes to DB. See the logs for details.");
|
||||
}
|
||||
}
|
||||
} else { //TODO: keep track of what we missed for later
|
||||
setStale(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CaseEventListener implements PropertyChangeListener {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if (RuntimeProperties.runningWithGUI() == false) {
|
||||
/*
|
||||
* Running in "headless" mode, no need to process any events.
|
||||
* This cannot be done earlier because the switch to core
|
||||
* components inactive may not have been made at start up.
|
||||
*/
|
||||
Case.removePropertyChangeListener(this);
|
||||
return;
|
||||
}
|
||||
switch (Case.Events.valueOf(evt.getPropertyName())) {
|
||||
case CURRENT_CASE:
|
||||
Case newCase = (Case) evt.getNewValue();
|
||||
if (newCase == null) { // case is closing
|
||||
//close window, reset everything
|
||||
SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent);
|
||||
reset();
|
||||
} else { // a new case has been opened
|
||||
setCase(newCase); //connect db, groupmanager, start worker thread
|
||||
}
|
||||
break;
|
||||
case DATA_SOURCE_ADDED:
|
||||
//copy all file data to drawable databse
|
||||
Content newDataSource = (Content) evt.getNewValue();
|
||||
if (isListeningEnabled()) {
|
||||
queueDBTask(new PrePopulateDataSourceFiles(newDataSource, ImageGalleryController.this, getDatabase(), getSleuthKitCase()));
|
||||
} else {//TODO: keep track of what we missed for later
|
||||
setStale(true);
|
||||
}
|
||||
break;
|
||||
case CONTENT_TAG_ADDED:
|
||||
final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt;
|
||||
if (getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) {
|
||||
getTagsManager().fireTagAddedEvent(tagAddedEvent);
|
||||
}
|
||||
break;
|
||||
case CONTENT_TAG_DELETED:
|
||||
final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) evt;
|
||||
if (getDatabase().isInDB(tagDeletedEvent.getDeletedTagInfo().getContentID())) {
|
||||
getTagsManager().fireTagDeletedEvent(tagDeletedEvent);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Copyright 2013-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,32 +18,77 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.SwingUtilities;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.core.RuntimeProperties;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||
import org.sleuthkit.autopsy.events.AutopsyEvent;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager.IngestJobEvent;
|
||||
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.FILE_DONE;
|
||||
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/** static definitions and utilities for the ImageGallery module */
|
||||
/** static definitions, utilities, and listeners for the ImageGallery module */
|
||||
@NbBundle.Messages({"ImageGalleryModule.moduleName=Image Gallery"})
|
||||
public class ImageGalleryModule {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(ImageGalleryModule.class.getName());
|
||||
private static final Logger logger = Logger.getLogger(ImageGalleryModule.class.getName());
|
||||
|
||||
private static final String MODULE_NAME = Bundle.ImageGalleryModule_moduleName();
|
||||
|
||||
private static final Object controllerLock = new Object();
|
||||
private static ImageGalleryController controller;
|
||||
|
||||
public static ImageGalleryController getController() throws NoCurrentCaseException {
|
||||
synchronized (controllerLock) {
|
||||
if (controller == null) {
|
||||
try {
|
||||
controller = new ImageGalleryController(Case.getCurrentCaseThrows());
|
||||
} catch (NoCurrentCaseException | TskCoreException ex) {
|
||||
throw new NoCurrentCaseException("Error getting ImageGalleryController for the current case.", ex);
|
||||
}
|
||||
}
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* This method is invoked by virtue of the OnStart annotation on the OnStart
|
||||
* class class
|
||||
*/
|
||||
static void onStart() {
|
||||
Platform.setImplicitExit(false);
|
||||
logger.info("Setting up ImageGallery listeners"); //NON-NLS
|
||||
|
||||
IngestManager.getInstance().addIngestJobEventListener(new IngestJobEventListener());
|
||||
IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener());
|
||||
Case.addPropertyChangeListener(new CaseEventListener());
|
||||
}
|
||||
|
||||
static String getModuleName() {
|
||||
return MODULE_NAME;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get the Path to the Case's ImageGallery ModuleOutput subfolder; ie
|
||||
* ".../[CaseName]/ModuleOutput/Image Gallery/"
|
||||
@ -53,7 +98,7 @@ public class ImageGalleryModule {
|
||||
*
|
||||
* @return the Path to the ModuleOuput subfolder for Image Gallery
|
||||
*/
|
||||
static Path getModuleOutputDir(Case theCase) {
|
||||
public static Path getModuleOutputDir(Case theCase) {
|
||||
return Paths.get(theCase.getModuleDirectory(), getModuleName());
|
||||
}
|
||||
|
||||
@ -83,19 +128,10 @@ public class ImageGalleryModule {
|
||||
* @return true if the drawable db is out of date for the given case, false
|
||||
* otherwise
|
||||
*/
|
||||
public static boolean isDrawableDBStale(Case c) {
|
||||
if (c != null) {
|
||||
String stale = new PerCaseProperties(c).getConfigSetting(ImageGalleryModule.MODULE_NAME, PerCaseProperties.STALE);
|
||||
return StringUtils.isNotBlank(stale) ? Boolean.valueOf(stale) : true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
public static boolean isDrawableDBStale(Case c) throws TskCoreException {
|
||||
return new ImageGalleryController(c).isDataSourcesTableStale();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Is the given file 'supported' and not 'known'(nsrl hash hit). If so we
|
||||
* should include it in {@link DrawableDB} and UI
|
||||
@ -105,7 +141,187 @@ public class ImageGalleryModule {
|
||||
* @return true if the given {@link AbstractFile} is "drawable" and not
|
||||
* 'known', else false
|
||||
*/
|
||||
public static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws TskCoreException, FileTypeDetector.FileTypeDetectorInitException {
|
||||
public static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws FileTypeDetector.FileTypeDetectorInitException {
|
||||
return (abstractFile.getKnown() != TskData.FileKnown.KNOWN) && FileTypeUtils.isDrawable(abstractFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for IngestModuleEvents
|
||||
*/
|
||||
static private class IngestModuleEventListener implements PropertyChangeListener {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if (RuntimeProperties.runningWithGUI() == false) {
|
||||
/*
|
||||
* Running in "headless" mode, no need to process any events.
|
||||
* This cannot be done earlier because the switch to core
|
||||
* components inactive may not have been made at start up.
|
||||
*/
|
||||
IngestManager.getInstance().removeIngestModuleEventListener(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName()) != FILE_DONE) {
|
||||
return;
|
||||
}
|
||||
// getOldValue has fileID getNewValue has Abstractfile
|
||||
AbstractFile file = (AbstractFile) evt.getNewValue();
|
||||
if (false == file.isFile()) {
|
||||
return;
|
||||
}
|
||||
/* only process individual files in realtime on the node that is
|
||||
* running the ingest. on a remote node, image files are processed
|
||||
* enblock when ingest is complete */
|
||||
if (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ImageGalleryController con = getController();
|
||||
if (con.isListeningEnabled()) {
|
||||
try {
|
||||
if (isDrawableAndNotKnown(file)) {
|
||||
//this file should be included and we don't already know about it from hash sets (NSRL)
|
||||
con.queueDBTask(new ImageGalleryController.UpdateFileTask(file, controller.getDatabase()));
|
||||
} else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) {
|
||||
/* Doing this check results in fewer tasks queued
|
||||
* up, and faster completion of db update. This file
|
||||
* would have gotten scooped up in initial grab, but
|
||||
* actually we don't need it */
|
||||
con.queueDBTask(new ImageGalleryController.RemoveFileTask(file, controller.getDatabase()));
|
||||
}
|
||||
|
||||
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
|
||||
logger.log(Level.SEVERE, "Unable to determine if file is drawable and not known. Not making any changes to DB", ex); //NON-NLS
|
||||
MessageNotifyUtil.Notify.error("Image Gallery Error",
|
||||
"Unable to determine if file is drawable and not known. Not making any changes to DB. See the logs for details.");
|
||||
}
|
||||
}
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for case events.
|
||||
*/
|
||||
static private class CaseEventListener implements PropertyChangeListener {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if (RuntimeProperties.runningWithGUI() == false) {
|
||||
/*
|
||||
* Running in "headless" mode, no need to process any events.
|
||||
* This cannot be done earlier because the switch to core
|
||||
* components inactive may not have been made at start up.
|
||||
*/
|
||||
Case.removePropertyChangeListener(this);
|
||||
return;
|
||||
}
|
||||
ImageGalleryController con;
|
||||
try {
|
||||
con = getController();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
switch (Case.Events.valueOf(evt.getPropertyName())) {
|
||||
case CURRENT_CASE:
|
||||
synchronized (controllerLock) {
|
||||
// case has changes: close window, reset everything
|
||||
SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent);
|
||||
if (controller != null) {
|
||||
controller.reset();
|
||||
}
|
||||
controller = null;
|
||||
|
||||
Case newCase = (Case) evt.getNewValue();
|
||||
if (newCase != null) {
|
||||
// a new case has been opened: connect db, groupmanager, start worker thread
|
||||
try {
|
||||
controller = new ImageGalleryController(newCase);
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error changing case in ImageGallery.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DATA_SOURCE_ADDED:
|
||||
//For a data source added on the local node, prepopulate all file data to drawable database
|
||||
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
|
||||
Content newDataSource = (Content) evt.getNewValue();
|
||||
if (con.isListeningEnabled()) {
|
||||
con.queueDBTask(new ImageGalleryController.PrePopulateDataSourceFiles(newDataSource.getId(), controller));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CONTENT_TAG_ADDED:
|
||||
final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt;
|
||||
if (con.getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) {
|
||||
con.getTagsManager().fireTagAddedEvent(tagAddedEvent);
|
||||
}
|
||||
break;
|
||||
case CONTENT_TAG_DELETED:
|
||||
final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) evt;
|
||||
if (con.getDatabase().isInDB(tagDeletedEvent.getDeletedTagInfo().getContentID())) {
|
||||
con.getTagsManager().fireTagDeletedEvent(tagDeletedEvent);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//we don't need to do anything for other events.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for Ingest Job events.
|
||||
*/
|
||||
static private class IngestJobEventListener implements PropertyChangeListener {
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ImageGalleryController.dataSourceAnalyzed.confDlg.msg= A new data source was added and finished ingest.\n"
|
||||
+ "The image / video database may be out of date. "
|
||||
+ "Do you want to update the database with ingest results?\n",
|
||||
"ImageGalleryController.dataSourceAnalyzed.confDlg.title=Image Gallery"
|
||||
})
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
IngestJobEvent eventType = IngestJobEvent.valueOf(evt.getPropertyName());
|
||||
if (eventType != IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED
|
||||
|| ((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.REMOTE) {
|
||||
return;
|
||||
}
|
||||
// A remote node added a new data source and just finished ingest on it.
|
||||
//drawable db is stale, and if ImageGallery is open, ask user what to do
|
||||
ImageGalleryController con;
|
||||
try {
|
||||
con = getController();
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
|
||||
return;
|
||||
}
|
||||
con.setStale(true);
|
||||
if (con.isListeningEnabled() && ImageGalleryTopComponent.isImageGalleryOpen()) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
int showAnswer = JOptionPane.showConfirmDialog(ImageGalleryTopComponent.getTopComponent(),
|
||||
Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_msg(),
|
||||
Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_title(),
|
||||
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||
|
||||
switch (showAnswer) {
|
||||
case JOptionPane.YES_OPTION:
|
||||
con.rebuildDB();
|
||||
break;
|
||||
case JOptionPane.NO_OPTION:
|
||||
case JOptionPane.CANCEL_OPTION:
|
||||
default:
|
||||
break; //do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,13 +18,10 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.logging.Level;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
/**
|
||||
* The Image/Video Gallery panel in the NetBeans provided Options Dialogs
|
||||
@ -45,13 +42,8 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
enabledForCaseBox.setEnabled(Case.isCaseOpen() && IngestManager.getInstance().isIngestRunning() == false);
|
||||
});
|
||||
|
||||
enabledByDefaultBox.addActionListener((ActionEvent e) -> {
|
||||
controller.changed();
|
||||
});
|
||||
|
||||
enabledForCaseBox.addActionListener((ActionEvent e) -> {
|
||||
controller.changed();
|
||||
});
|
||||
enabledByDefaultBox.addActionListener(actionEvent -> controller.changed());
|
||||
enabledForCaseBox.addActionListener(actionEvent -> controller.changed());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -204,19 +196,18 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
||||
|
||||
void store() {
|
||||
ImageGalleryPreferences.setEnabledByDefault(enabledByDefaultBox.isSelected());
|
||||
ImageGalleryController.getDefault().setListeningEnabled(enabledForCaseBox.isSelected());
|
||||
|
||||
ImageGalleryPreferences.setGroupCategorizationWarningDisabled(groupCategorizationWarningBox.isSelected());
|
||||
|
||||
// If a case is open, save the per case setting
|
||||
try {
|
||||
Case openCase = Case.getCurrentCaseThrows();
|
||||
ImageGalleryModule.getController().setListeningEnabled(enabledForCaseBox.isSelected());
|
||||
new PerCaseProperties(openCase).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.ENABLED, Boolean.toString(enabledForCaseBox.isSelected()));
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
// It's not an error if there's no case open
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,8 +46,7 @@ public class ImageGalleryPreferences {
|
||||
* @return true if new cases should have image analyzer enabled.
|
||||
*/
|
||||
public static boolean isEnabledByDefault() {
|
||||
final boolean aBoolean = preferences.getBoolean(ENABLED_BY_DEFAULT, true);
|
||||
return aBoolean;
|
||||
return preferences.getBoolean(ENABLED_BY_DEFAULT, true);
|
||||
}
|
||||
|
||||
public static void setEnabledByDefault(boolean b) {
|
||||
|
@ -18,27 +18,52 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ChoiceDialog;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundFill;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Modality;
|
||||
import javax.swing.SwingUtilities;
|
||||
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
||||
import static org.apache.commons.lang3.ObjectUtils.notEqual;
|
||||
import org.openide.explorer.ExplorerManager;
|
||||
import org.openide.explorer.ExplorerUtils;
|
||||
import org.openide.util.Lookup;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.windows.Mode;
|
||||
import org.openide.windows.RetainLocation;
|
||||
import org.openide.windows.TopComponent;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.StatusBar;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.SummaryTablePane;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
|
||||
@ -46,6 +71,9 @@ import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.GroupPane;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.MetaDataPane;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.navpanel.GroupTree;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.navpanel.HashHitGroupList;
|
||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Top component which displays ImageGallery interface.
|
||||
@ -69,17 +97,17 @@ import org.sleuthkit.autopsy.imagegallery.gui.navpanel.HashHitGroupList;
|
||||
public final class ImageGalleryTopComponent extends TopComponent implements ExplorerManager.Provider, Lookup.Provider {
|
||||
|
||||
public final static String PREFERRED_ID = "ImageGalleryTopComponent"; // NON-NLS
|
||||
private static final Logger LOGGER = Logger.getLogger(ImageGalleryTopComponent.class.getName());
|
||||
private static final Logger logger = Logger.getLogger(ImageGalleryTopComponent.class.getName());
|
||||
private static volatile boolean topComponentInitialized = false;
|
||||
|
||||
private final ExplorerManager em = new ExplorerManager();
|
||||
private final Lookup lookup = (ExplorerUtils.createLookup(em, getActionMap()));
|
||||
|
||||
private final ImageGalleryController controller = ImageGalleryController.getDefault();
|
||||
private ImageGalleryController controller;
|
||||
|
||||
private SplitPane splitPane;
|
||||
private StackPane centralStack;
|
||||
private BorderPane borderPane = new BorderPane();
|
||||
private final BorderPane borderPane = new BorderPane();
|
||||
private StackPane fullUIStack;
|
||||
private MetaDataPane metaDataTable;
|
||||
private GroupPane groupPane;
|
||||
@ -88,24 +116,97 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
||||
private VBox leftPane;
|
||||
private Scene myScene;
|
||||
|
||||
public static void openTopComponent() {
|
||||
//TODO:eventually move to this model, throwing away everything and rebuilding controller groupmanager etc for each case.
|
||||
// synchronized (OpenTimelineAction.class) {
|
||||
// if (timeLineController == null) {
|
||||
// timeLineController = new TimeLineController();
|
||||
// LOGGER.log(Level.WARNING, "Failed to get TimeLineController from lookup. Instantiating one directly.S");
|
||||
// }
|
||||
// }
|
||||
// timeLineController.openTimeLine();
|
||||
final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
|
||||
if (tc != null) {
|
||||
topComponentInitialized = true;
|
||||
if (tc.isOpened() == false) {
|
||||
tc.open();
|
||||
}
|
||||
tc.toFront();
|
||||
tc.requestActive();
|
||||
private Node infoOverlay;
|
||||
private final Region infoOverLayBackground = new TranslucentRegion();
|
||||
|
||||
/**
|
||||
* Returns whether the ImageGallery window is open or not.
|
||||
*
|
||||
* @return true, if Image gallery is opened, false otherwise
|
||||
*/
|
||||
public static boolean isImageGalleryOpen() {
|
||||
|
||||
final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
|
||||
if (topComponent != null) {
|
||||
return topComponent.isOpened();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the top component window.
|
||||
*
|
||||
* @return Image gallery top component window, null if it's not open
|
||||
*/
|
||||
public static TopComponent getTopComponent() {
|
||||
return WindowManager.getDefault().findTopComponent(PREFERRED_ID);
|
||||
}
|
||||
|
||||
@Messages({
|
||||
"ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.headerText=Choose a data source to view.",
|
||||
"ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.contentText=Data source:",
|
||||
"ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.all=All",
|
||||
"ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.titleText=Image Gallery",})
|
||||
public static void openTopComponent() throws NoCurrentCaseException {
|
||||
|
||||
final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
|
||||
if (topComponent == null) {
|
||||
return;
|
||||
}
|
||||
topComponentInitialized = true;
|
||||
if (topComponent.isOpened()) {
|
||||
showTopComponent(topComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
List<DataSource> dataSources = Collections.emptyList();
|
||||
ImageGalleryController controller = ImageGalleryModule.getController();
|
||||
((ImageGalleryTopComponent) topComponent).setController(controller);
|
||||
try {
|
||||
dataSources = controller.getSleuthKitCase().getDataSources();
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException);
|
||||
}
|
||||
GroupManager groupManager = controller.getGroupManager();
|
||||
synchronized (groupManager) {
|
||||
if (dataSources.size() <= 1
|
||||
|| groupManager.getGroupBy() != DrawableAttribute.PATH) {
|
||||
/* if there is only one datasource or the grouping is already
|
||||
* set to something other than path , don't both to ask for
|
||||
* datasource */
|
||||
groupManager.regroup(null, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true);
|
||||
|
||||
showTopComponent(topComponent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, DataSource> dataSourceNames = new HashMap<>();
|
||||
dataSourceNames.put("All", null);
|
||||
dataSources.forEach(dataSource -> dataSourceNames.put(dataSource.getName(), dataSource));
|
||||
|
||||
Platform.runLater(() -> {
|
||||
ChoiceDialog<String> datasourceDialog = new ChoiceDialog<>(null, dataSourceNames.keySet());
|
||||
datasourceDialog.setTitle(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_titleText());
|
||||
datasourceDialog.setHeaderText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_headerText());
|
||||
datasourceDialog.setContentText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_contentText());
|
||||
datasourceDialog.initModality(Modality.APPLICATION_MODAL);
|
||||
GuiUtils.setDialogIcons(datasourceDialog);
|
||||
|
||||
Optional<String> dataSourceName = datasourceDialog.showAndWait();
|
||||
DataSource dataSource = dataSourceName.map(dataSourceNames::get).orElse(null);
|
||||
synchronized (groupManager) {
|
||||
groupManager.regroup(dataSource, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true);
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> showTopComponent(topComponent));
|
||||
});
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
|
||||
public static void showTopComponent(TopComponent topComponent) {
|
||||
topComponent.open();
|
||||
topComponent.toFront();
|
||||
topComponent.requestActive();
|
||||
}
|
||||
|
||||
public static void closeTopComponent() {
|
||||
@ -115,48 +216,57 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
||||
try {
|
||||
etc.close();
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "failed to close " + PREFERRED_ID, e); // NON-NLS
|
||||
logger.log(Level.SEVERE, "failed to close " + PREFERRED_ID, e); // NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ImageGalleryTopComponent() {
|
||||
public ImageGalleryTopComponent() throws NoCurrentCaseException {
|
||||
setName(Bundle.CTL_ImageGalleryTopComponent());
|
||||
initComponents();
|
||||
setController(ImageGalleryModule.getController());
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {//initialize jfx ui
|
||||
fullUIStack = new StackPane(); //this is passed into controller
|
||||
myScene = new Scene(fullUIStack);
|
||||
jfxPanel.setScene(myScene);
|
||||
groupPane = new GroupPane(controller);
|
||||
centralStack = new StackPane(groupPane); //this is passed into controller
|
||||
fullUIStack.getChildren().add(borderPane);
|
||||
splitPane = new SplitPane();
|
||||
borderPane.setCenter(splitPane);
|
||||
Toolbar toolbar = new Toolbar(controller);
|
||||
borderPane.setTop(toolbar);
|
||||
borderPane.setBottom(new StatusBar(controller));
|
||||
synchronized private void setController(ImageGalleryController controller) {
|
||||
if (this.controller != null && notEqual(this.controller, controller)) {
|
||||
this.controller.reset();
|
||||
}
|
||||
this.controller = controller;
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//initialize jfx ui
|
||||
fullUIStack = new StackPane(); //this is passed into controller
|
||||
myScene = new Scene(fullUIStack);
|
||||
jfxPanel.setScene(myScene);
|
||||
groupPane = new GroupPane(controller);
|
||||
centralStack = new StackPane(groupPane); //this is passed into controller
|
||||
fullUIStack.getChildren().add(borderPane);
|
||||
splitPane = new SplitPane();
|
||||
borderPane.setCenter(splitPane);
|
||||
Toolbar toolbar = new Toolbar(controller);
|
||||
borderPane.setTop(toolbar);
|
||||
borderPane.setBottom(new StatusBar(controller));
|
||||
metaDataTable = new MetaDataPane(controller);
|
||||
groupTree = new GroupTree(controller);
|
||||
hashHitList = new HashHitGroupList(controller);
|
||||
TabPane tabPane = new TabPane(groupTree, hashHitList);
|
||||
tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE);
|
||||
tabPane.setMinWidth(TabPane.USE_PREF_SIZE);
|
||||
VBox.setVgrow(tabPane, Priority.ALWAYS);
|
||||
leftPane = new VBox(tabPane, new SummaryTablePane(controller));
|
||||
SplitPane.setResizableWithParent(leftPane, Boolean.FALSE);
|
||||
SplitPane.setResizableWithParent(groupPane, Boolean.TRUE);
|
||||
SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE);
|
||||
splitPane.getItems().addAll(leftPane, centralStack, metaDataTable);
|
||||
splitPane.setDividerPositions(0.1, 1.0);
|
||||
|
||||
metaDataTable = new MetaDataPane(controller);
|
||||
controller.regroupDisabledProperty().addListener((Observable observable) -> checkForGroups());
|
||||
controller.getGroupManager().getAnalyzedGroups().addListener((Observable observable) -> Platform.runLater(() -> checkForGroups()));
|
||||
|
||||
groupTree = new GroupTree(controller);
|
||||
hashHitList = new HashHitGroupList(controller);
|
||||
|
||||
TabPane tabPane = new TabPane(groupTree, hashHitList);
|
||||
tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE);
|
||||
tabPane.setMinWidth(TabPane.USE_PREF_SIZE);
|
||||
VBox.setVgrow(tabPane, Priority.ALWAYS);
|
||||
leftPane = new VBox(tabPane, new SummaryTablePane(controller));
|
||||
SplitPane.setResizableWithParent(leftPane, Boolean.FALSE);
|
||||
SplitPane.setResizableWithParent(groupPane, Boolean.TRUE);
|
||||
SplitPane.setResizableWithParent(metaDataTable, Boolean.FALSE);
|
||||
splitPane.getItems().addAll(leftPane, centralStack, metaDataTable);
|
||||
splitPane.setDividerPositions(0.1, 1.0);
|
||||
|
||||
ImageGalleryController.getDefault().setStacks(fullUIStack, centralStack);
|
||||
ImageGalleryController.getDefault().setToolbar(toolbar);
|
||||
ImageGalleryController.getDefault().setShowTree(() -> tabPane.getSelectionModel().select(groupTree));
|
||||
Platform.runLater(() -> checkForGroups());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -212,4 +322,100 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
||||
public Lookup getLookup() {
|
||||
return lookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any fully analyzed groups available from the
|
||||
* GroupManager and remove blocking progress spinners if there are. If there
|
||||
* aren't, add a blocking progress spinner with appropriate message.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
@NbBundle.Messages({
|
||||
"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. "
|
||||
+ " No groups will be available until ingest is finished and listening is re-enabled.",
|
||||
"ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.",
|
||||
"ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.",
|
||||
"ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. "
|
||||
+ " No groups will be available until ingest is finished and listening is re-enabled.",
|
||||
"ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.",
|
||||
"ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:"
|
||||
+ " the current Group By setting resulted in no groups, "
|
||||
+ "or no groups are fully analyzed but ingest is not running."})
|
||||
private void checkForGroups() {
|
||||
GroupManager groupManager = controller.getGroupManager();
|
||||
synchronized (groupManager) {
|
||||
if (isNotEmpty(groupManager.getAnalyzedGroups())) {
|
||||
clearNotification();
|
||||
return;
|
||||
}
|
||||
|
||||
if (IngestManager.getInstance().isIngestRunning()) {
|
||||
if (controller.isListeningEnabled()) {
|
||||
replaceNotification(centralStack,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(),
|
||||
new ProgressIndicator()));
|
||||
} else {
|
||||
replaceNotification(fullUIStack,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (controller.getDBTasksQueueSizeProperty().get() > 0) {
|
||||
replaceNotification(fullUIStack,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(),
|
||||
new ProgressIndicator()));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (controller.getDatabase().countAllFiles() <= 0) {
|
||||
// there are no files in db
|
||||
if (controller.isListeningEnabled()) {
|
||||
replaceNotification(fullUIStack,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5()));
|
||||
} else {
|
||||
replaceNotification(fullUIStack,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
logger.log(Level.SEVERE, "Error counting files in the database.", tskCoreException);
|
||||
}
|
||||
|
||||
if (false == groupManager.isRegrouping()) {
|
||||
replaceNotification(centralStack,
|
||||
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private void replaceNotification(StackPane stackPane, Node newNode) {
|
||||
clearNotification();
|
||||
infoOverlay = new StackPane(infoOverLayBackground, newNode);
|
||||
if (stackPane != null) {
|
||||
stackPane.getChildren().add(infoOverlay);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private void clearNotification() {
|
||||
//remove the ingest spinner
|
||||
fullUIStack.getChildren().remove(infoOverlay);
|
||||
//remove the ingest spinner
|
||||
centralStack.getChildren().remove(infoOverlay);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Region with partialy opacity used to block out parts of the UI behind a
|
||||
* pseudo dialog.
|
||||
*/
|
||||
static final private class TranslucentRegion extends Region {
|
||||
|
||||
TranslucentRegion() {
|
||||
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
|
||||
setOpacity(.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013 Basis Technology Corp.
|
||||
* Copyright 2013-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,26 +18,19 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery;
|
||||
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* The {@link org.openide.modules.OnStart} annotation tells NetBeans to invoke
|
||||
* this class's {@link OnStart#run()} method
|
||||
* The org.openide.modules.OnStart annotation tells NetBeans to invoke this
|
||||
* class's run method.
|
||||
*/
|
||||
@org.openide.modules.OnStart
|
||||
public class OnStart implements Runnable {
|
||||
|
||||
static private final Logger LOGGER = Logger.getLogger(OnStart.class.getName());
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* This method is invoked by virtue of the {@link OnStart} annotation on the
|
||||
* {@link ImageGalleryModule} class
|
||||
* This method is invoked by virtue of the OnStart annotation on the this
|
||||
* class
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
ImageGalleryController.getDefault().onStart();
|
||||
ImageGalleryModule.onStart();
|
||||
}
|
||||
}
|
||||
|
@ -40,8 +40,6 @@ class PerCaseProperties {
|
||||
|
||||
public static final String ENABLED = "enabled"; //NON-NLS
|
||||
|
||||
public static final String STALE = "stale"; //NON-NLS
|
||||
|
||||
private final Case theCase;
|
||||
|
||||
PerCaseProperties(Case c) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Copyright 2013-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -54,9 +54,13 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* TODO: this was only a singleton for convenience, convert this to
|
||||
* non-singleton class -jm?
|
||||
*/
|
||||
public enum ThumbnailCache {
|
||||
public class ThumbnailCache {
|
||||
|
||||
instance;
|
||||
private final ImageGalleryController controller;
|
||||
|
||||
public ThumbnailCache(ImageGalleryController controller) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
private static final int MAX_THUMBNAIL_SIZE = 300;
|
||||
|
||||
@ -71,10 +75,6 @@ public enum ThumbnailCache {
|
||||
.softValues()
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES).build();
|
||||
|
||||
public static ThumbnailCache getDefault() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* currently desired icon size. is bound in {@link Toolbar}
|
||||
*/
|
||||
@ -109,7 +109,7 @@ public enum ThumbnailCache {
|
||||
@Nullable
|
||||
public Image get(Long fileID) {
|
||||
try {
|
||||
return get(ImageGalleryController.getDefault().getFileFromId(fileID));
|
||||
return get(controller.getFileFromID(fileID));
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.WARNING, "Failed to load thumbnail for file: " + fileID, ex.getCause()); //NON-NLS
|
||||
return null;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2017 Basis Technology Corp.
|
||||
* Copyright 2013-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -36,7 +36,6 @@ import javax.swing.SwingWorker;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.openide.util.NbBundle.Messages;
|
||||
import org.openide.windows.TopComponent;
|
||||
import org.openide.windows.WindowManager;
|
||||
import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog;
|
||||
@ -50,8 +49,8 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.datamodel.TskData;
|
||||
|
||||
/**
|
||||
* Instances of this Action allow users to apply tags to content.
|
||||
@ -75,14 +74,14 @@ public class AddTagAction extends Action {
|
||||
setEventHandler(actionEvent -> addTagWithComment(""));
|
||||
}
|
||||
|
||||
static public Menu getTagMenu(ImageGalleryController controller) {
|
||||
static public Menu getTagMenu(ImageGalleryController controller) throws TskCoreException {
|
||||
return new TagMenu(controller);
|
||||
}
|
||||
|
||||
private void addTagWithComment(String comment) {
|
||||
addTagsToFiles(tagName, comment, selectedFileIDs);
|
||||
}
|
||||
|
||||
|
||||
@NbBundle.Messages({"# {0} - fileID",
|
||||
"AddDrawableTagAction.addTagsToFiles.alert=Unable to tag file {0}."})
|
||||
private void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles) {
|
||||
@ -94,7 +93,7 @@ public class AddTagAction extends Action {
|
||||
DrawableTagsManager tagsManager = controller.getTagsManager();
|
||||
for (Long fileID : selectedFiles) {
|
||||
try {
|
||||
final DrawableFile file = controller.getFileFromId(fileID);
|
||||
final DrawableFile file = controller.getFileFromID(fileID);
|
||||
LOGGER.log(Level.INFO, "tagging {0} with {1} and comment {2}", new Object[]{file.getName(), tagName.getDisplayName(), comment}); //NON-NLS
|
||||
|
||||
List<ContentTag> contentTags = tagsManager.getContentTags(file);
|
||||
@ -141,7 +140,7 @@ public class AddTagAction extends Action {
|
||||
"AddDrawableTagAction.displayName.singular=Tag File"})
|
||||
private static class TagMenu extends Menu {
|
||||
|
||||
TagMenu(ImageGalleryController controller) {
|
||||
TagMenu(ImageGalleryController controller) throws TskCoreException {
|
||||
setGraphic(new ImageView(DrawableAttribute.TAGS.getIcon()));
|
||||
ObservableSet<Long> selectedFileIDs = controller.getSelectionModel().getSelected();
|
||||
setText(selectedFileIDs.size() > 1
|
||||
@ -163,11 +162,10 @@ public class AddTagAction extends Action {
|
||||
empty.setDisable(true);
|
||||
quickTagMenu.getItems().add(empty);
|
||||
} else {
|
||||
for (final TagName tagName : tagNames) {
|
||||
AddTagAction addDrawableTagAction = new AddTagAction(controller, tagName, selectedFileIDs);
|
||||
MenuItem tagNameItem = ActionUtils.createMenuItem(addDrawableTagAction);
|
||||
quickTagMenu.getItems().add(tagNameItem);
|
||||
}
|
||||
tagNames.stream()
|
||||
.map(tagName -> new AddTagAction(controller, tagName, selectedFileIDs))
|
||||
.map(ActionUtils::createMenuItem)
|
||||
.forEachOrdered(quickTagMenu.getItems()::add);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -138,7 +138,7 @@ public class CategorizeAction extends Action {
|
||||
TagName catZeroTagName = categoryManager.getTagName(DhsImageCategory.ZERO);
|
||||
for (long fileID : fileIDs) {
|
||||
try {
|
||||
DrawableFile file = controller.getFileFromId(fileID); //drawable db access
|
||||
DrawableFile file = controller.getFileFromID(fileID); //drawable db access
|
||||
if (createUndo) {
|
||||
DhsImageCategory oldCat = file.getCategory(); //drawable db access
|
||||
TagName oldCatTagName = categoryManager.getTagName(oldCat);
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015-16 Basis Technology Corp.
|
||||
* Copyright 2015-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -34,11 +34,12 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import static org.apache.commons.lang.ObjectUtils.notEqual;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryPreferences;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
@ -52,32 +53,35 @@ public class CategorizeGroupAction extends CategorizeAction {
|
||||
public CategorizeGroupAction(DhsImageCategory newCat, ImageGalleryController controller) {
|
||||
super(controller, newCat, null);
|
||||
setEventHandler(actionEvent -> {
|
||||
ObservableList<Long> fileIDs = controller.viewState().get().getGroup().getFileIDs();
|
||||
controller.getViewState().getGroup().ifPresent(group -> {
|
||||
ObservableList<Long> fileIDs = group.getFileIDs();
|
||||
|
||||
if (ImageGalleryPreferences.isGroupCategorizationWarningDisabled()) {
|
||||
//if they have preveiously disabled the warning, just go ahead and apply categories.
|
||||
addCatToFiles(ImmutableSet.copyOf(fileIDs));
|
||||
} else {
|
||||
final Map<DhsImageCategory, Long> catCountMap = new HashMap<>();
|
||||
|
||||
for (Long fileID : fileIDs) {
|
||||
try {
|
||||
DhsImageCategory category = controller.getFileFromId(fileID).getCategory();
|
||||
if (false == DhsImageCategory.ZERO.equals(category) && newCat.equals(category) == false) {
|
||||
catCountMap.merge(category, 1L, Long::sum);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to categorize files.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (catCountMap.isEmpty()) {
|
||||
//if there are not going to be any categories overwritten, skip the warning.
|
||||
if (ImageGalleryPreferences.isGroupCategorizationWarningDisabled()) {
|
||||
//if they have preveiously disabled the warning, just go ahead and apply categories.
|
||||
addCatToFiles(ImmutableSet.copyOf(fileIDs));
|
||||
} else {
|
||||
showConfirmationDialog(catCountMap, newCat, fileIDs);
|
||||
final Map<DhsImageCategory, Long> catCountMap = new HashMap<>();
|
||||
|
||||
for (Long fileID : fileIDs) {
|
||||
try {
|
||||
DhsImageCategory category = controller.getFileFromID(fileID).getCategory();
|
||||
if (false == DhsImageCategory.ZERO.equals(category) && newCat.equals(category) == false) {
|
||||
catCountMap.merge(category, 1L, Long::sum);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to categorize files.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (catCountMap.isEmpty()) {
|
||||
//if there are not going to be any categories overwritten, skip the warning.
|
||||
addCatToFiles(ImmutableSet.copyOf(fileIDs));
|
||||
} else {
|
||||
showConfirmationDialog(catCountMap, newCat, fileIDs);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@ -88,19 +92,18 @@ public class CategorizeGroupAction extends CategorizeAction {
|
||||
"CategorizeGroupAction.fileCountHeader=Files in the following categories will have their categories overwritten: "})
|
||||
private void showConfirmationDialog(final Map<DhsImageCategory, Long> catCountMap, DhsImageCategory newCat, ObservableList<Long> fileIDs) {
|
||||
|
||||
ButtonType categorizeButtonType =
|
||||
new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY);
|
||||
ButtonType categorizeButtonType
|
||||
= new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY);
|
||||
|
||||
VBox textFlow = new VBox();
|
||||
|
||||
for (Map.Entry<DhsImageCategory, Long> entry : catCountMap.entrySet()) {
|
||||
if (entry.getKey().equals(newCat) == false) {
|
||||
if (entry.getValue() > 0) {
|
||||
Label label = new Label(Bundle.CategorizeGroupAction_fileCountMessage(entry.getValue(), entry.getKey().getDisplayName()),
|
||||
entry.getKey().getGraphic());
|
||||
label.setContentDisplay(ContentDisplay.RIGHT);
|
||||
textFlow.getChildren().add(label);
|
||||
}
|
||||
if (entry.getValue() > 0
|
||||
&& notEqual(entry.getKey(), newCat)) {
|
||||
Label label = new Label(Bundle.CategorizeGroupAction_fileCountMessage(entry.getValue(), entry.getKey().getDisplayName()),
|
||||
entry.getKey().getGraphic());
|
||||
label.setContentDisplay(ContentDisplay.RIGHT);
|
||||
textFlow.getChildren().add(label);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2011-17 Basis Technology Corp.
|
||||
* Copyright 2011-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,15 +18,16 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import java.util.Optional;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.ObjectExpression;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
|
||||
@ -36,58 +37,87 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
||||
* Marks the currently displayed group as "seen" and advances to the next unseen
|
||||
* group
|
||||
*/
|
||||
@NbBundle.Messages({"NextUnseenGroup.markGroupSeen=Mark Group Seen",
|
||||
"NextUnseenGroup.nextUnseenGroup=Next Unseen group"})
|
||||
@NbBundle.Messages({
|
||||
"NextUnseenGroup.markGroupSeen=Mark Group Seen",
|
||||
"NextUnseenGroup.nextUnseenGroup=Next Unseen group",
|
||||
"NextUnseenGroup.allGroupsSeen=All Groups Have Been Seen"})
|
||||
public class NextUnseenGroup extends Action {
|
||||
|
||||
private static final Image END =
|
||||
new Image(NextUnseenGroup.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/control-stop.png")); //NON-NLS
|
||||
private static final Image ADVANCE =
|
||||
new Image(NextUnseenGroup.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/control-double.png")); //NON-NLS
|
||||
private static final String IMAGE_PATH = "/org/sleuthkit/autopsy/imagegallery/images/"; //NON-NLS
|
||||
private static final Image END_IMAGE = new Image(NextUnseenGroup.class.getResourceAsStream(
|
||||
IMAGE_PATH + "control-stop.png")); //NON-NLS
|
||||
private static final Image ADVANCE_IMAGE = new Image(NextUnseenGroup.class.getResourceAsStream(
|
||||
IMAGE_PATH + "control-double.png")); //NON-NLS
|
||||
|
||||
private static final String MARK_GROUP_SEEN = Bundle.NextUnseenGroup_markGroupSeen();
|
||||
private static final String NEXT_UNSEEN_GROUP = Bundle.NextUnseenGroup_nextUnseenGroup();
|
||||
private static final String ALL_GROUPS_SEEN = Bundle.NextUnseenGroup_allGroupsSeen();
|
||||
|
||||
private final GroupManager groupManager;
|
||||
private final ImageGalleryController controller;
|
||||
private final ObservableList<DrawableGroup> unSeenGroups;
|
||||
private final ObservableList<DrawableGroup> analyzedGroups;
|
||||
private final GroupManager groupManager;
|
||||
|
||||
public NextUnseenGroup(ImageGalleryController controller) {
|
||||
super(NEXT_UNSEEN_GROUP);
|
||||
setGraphic(new ImageView(ADVANCE_IMAGE));
|
||||
|
||||
this.controller = controller;
|
||||
groupManager = controller.getGroupManager();
|
||||
unSeenGroups = groupManager.getUnSeenGroups();
|
||||
analyzedGroups = groupManager.getAnalyzedGroups();
|
||||
setGraphic(new ImageView(ADVANCE));
|
||||
unSeenGroups.addListener((Observable observable) -> updateButton());
|
||||
controller.viewStateProperty().addListener((Observable observable) -> updateButton());
|
||||
|
||||
//TODO: do we need both these listeners?
|
||||
analyzedGroups.addListener((Observable o) -> this.updateButton());
|
||||
unSeenGroups.addListener((Observable o) -> this.updateButton());
|
||||
|
||||
setEventHandler(event -> {
|
||||
//fx-thread
|
||||
setEventHandler(event -> { //on fx-thread
|
||||
//if there is a group assigned to the view, mark it as seen
|
||||
Optional.ofNullable(controller.viewState())
|
||||
.map(ObjectExpression<GroupViewState>::getValue)
|
||||
.map(GroupViewState::getGroup)
|
||||
.ifPresent(group -> groupManager.markGroupSeen(group, true));
|
||||
|
||||
if (unSeenGroups.isEmpty() == false) {
|
||||
controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true);
|
||||
updateButton();
|
||||
}
|
||||
Optional.ofNullable(controller.getViewState())
|
||||
.flatMap(GroupViewState::getGroup)
|
||||
.ifPresent(group -> {
|
||||
groupManager.markGroupSeen(group, true)
|
||||
.addListener(this::advanceToNextUnseenGroup, MoreExecutors.newDirectExecutorService());
|
||||
});
|
||||
});
|
||||
updateButton();
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private void advanceToNextUnseenGroup() {
|
||||
synchronized (groupManager) {
|
||||
if (CollectionUtils.isNotEmpty(unSeenGroups)) {
|
||||
controller.advance(GroupViewState.tile(unSeenGroups.get(0)));
|
||||
}
|
||||
|
||||
updateButton();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateButton() {
|
||||
setDisabled(unSeenGroups.isEmpty());
|
||||
if (unSeenGroups.size() <= 1) {
|
||||
setText(MARK_GROUP_SEEN);
|
||||
setGraphic(new ImageView(END));
|
||||
int size = unSeenGroups.size();
|
||||
|
||||
if (size < 1) {
|
||||
//there are no unseen groups.
|
||||
Platform.runLater(() -> {
|
||||
setDisabled(true);
|
||||
setText(ALL_GROUPS_SEEN);
|
||||
setGraphic(null);
|
||||
});
|
||||
} else {
|
||||
setText(NEXT_UNSEEN_GROUP);
|
||||
setGraphic(new ImageView(ADVANCE));
|
||||
DrawableGroup get = unSeenGroups.get(0);
|
||||
DrawableGroup orElse = Optional.ofNullable(controller.getViewState()).flatMap(GroupViewState::getGroup).orElse(null);
|
||||
boolean equals = get.equals(orElse);
|
||||
if (size == 1 & equals) {
|
||||
//The only unseen group is the one that is being viewed.
|
||||
Platform.runLater(() -> {
|
||||
setDisabled(false);
|
||||
setText(MARK_GROUP_SEEN);
|
||||
setGraphic(new ImageView(END_IMAGE));
|
||||
});
|
||||
} else {
|
||||
//there are more unseen groups.
|
||||
Platform.runLater(() -> {
|
||||
setDisabled(false);
|
||||
setText(NEXT_UNSEEN_GROUP);
|
||||
setGraphic(new ImageView(ADVANCE_IMAGE));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,16 +21,11 @@ package org.sleuthkit.autopsy.imagegallery.actions;
|
||||
import java.awt.Component;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.Dialog;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JMenuItem;
|
||||
@ -49,42 +44,27 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.core.Installer;
|
||||
import org.sleuthkit.autopsy.core.RuntimeProperties;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.imagegallery.OpenAction")
|
||||
@ActionReferences(value = {
|
||||
@ActionReference(path = "Menu/Tools", position = 101),
|
||||
@ActionReference(path = "Menu/Tools", position = 101)
|
||||
,
|
||||
@ActionReference(path = "Toolbars/Case", position = 101)})
|
||||
@ActionRegistration(displayName = "#CTL_OpenAction", lazy = false)
|
||||
@Messages({"CTL_OpenAction=Images/Videos",
|
||||
"OpenAction.stale.confDlg.msg=The image / video database may be out of date. " +
|
||||
"Do you want to update and listen for further ingest results?\n" +
|
||||
"Choosing 'yes' will update the database and enable listening to future ingests.",
|
||||
"OpenAction.stale.confDlg.msg=The image / video database may be out of date. "
|
||||
+ "Do you want to update and listen for further ingest results?\n"
|
||||
+ "Choosing 'yes' will update the database and enable listening to future ingests.",
|
||||
"OpenAction.stale.confDlg.title=Image Gallery"})
|
||||
public final class OpenAction extends CallableSystemAction {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(OpenAction.class.getName());
|
||||
private static final String VIEW_IMAGES_VIDEOS = Bundle.CTL_OpenAction();
|
||||
|
||||
/**
|
||||
* Image to use as title bar icon in dialogs
|
||||
*/
|
||||
private static final Image AUTOPSY_ICON;
|
||||
|
||||
static {
|
||||
Image tempImg = null;
|
||||
try {
|
||||
tempImg = new Image(new URL("nbresloc:/org/netbeans/core/startup/frame.gif").openStream()); //NON-NLS
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Failed to load branded icon for progress dialog.", ex); //NON-NLS
|
||||
}
|
||||
AUTOPSY_ICON = tempImg;
|
||||
}
|
||||
|
||||
private static final long FILE_LIMIT = 6_000_000;
|
||||
|
||||
private final PropertyChangeListener pcl;
|
||||
@ -145,10 +125,7 @@ public final class OpenAction extends CallableSystemAction {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("fallthrough")
|
||||
@NbBundle.Messages({
|
||||
"OpenAction.dialogTitle=Image Gallery"
|
||||
})
|
||||
@NbBundle.Messages({"OpenAction.dialogTitle=Image Gallery"})
|
||||
public void performAction() {
|
||||
|
||||
//check case
|
||||
@ -165,24 +142,49 @@ public final class OpenAction extends CallableSystemAction {
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
if (ImageGalleryModule.isDrawableDBStale(currentCase)) {
|
||||
//drawable db is stale, ask what to do
|
||||
int answer = JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), Bundle.OpenAction_stale_confDlg_msg(),
|
||||
Bundle.OpenAction_stale_confDlg_title(), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||
try {
|
||||
ImageGalleryController controller = ImageGalleryModule.getController();
|
||||
if (controller.isDataSourcesTableStale()) {
|
||||
//drawable db is stale, ask what to do
|
||||
int answer = JOptionPane.showConfirmDialog(
|
||||
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:
|
||||
ImageGalleryController.getDefault().setListeningEnabled(true);
|
||||
//fall through
|
||||
case JOptionPane.NO_OPTION:
|
||||
ImageGalleryTopComponent.openTopComponent();
|
||||
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) {
|
||||
controller.setListeningEnabled(true);
|
||||
} else {
|
||||
controller.rebuildDB();
|
||||
}
|
||||
ImageGalleryTopComponent.openTopComponent();
|
||||
break;
|
||||
|
||||
case JOptionPane.NO_OPTION: {
|
||||
ImageGalleryTopComponent.openTopComponent();
|
||||
}
|
||||
break;
|
||||
case JOptionPane.CANCEL_OPTION:
|
||||
break; //do nothing
|
||||
case JOptionPane.CANCEL_OPTION:
|
||||
break; //do nothing
|
||||
}
|
||||
} else {
|
||||
//drawable db is not stale, just open it
|
||||
ImageGalleryTopComponent.openTopComponent();
|
||||
}
|
||||
} else {
|
||||
//drawable db is not stale, just open it
|
||||
ImageGalleryTopComponent.openTopComponent();
|
||||
} catch (NoCurrentCaseException noCurrentCaseException) {
|
||||
logger.log(Level.WARNING, "There was no case open when Image Gallery was opened.", noCurrentCaseException);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,16 +200,6 @@ public final class OpenAction extends CallableSystemAction {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the title bar icon for the given Dialog to be the Autopsy logo icon.
|
||||
*
|
||||
* @param dialog The dialog to set the title bar icon for.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private static void setDialogIcons(Dialog<?> dialog) {
|
||||
((Stage) dialog.getDialogPane().getScene().getWindow()).getIcons().setAll(AUTOPSY_ICON);
|
||||
}
|
||||
|
||||
@NbBundle.Messages({
|
||||
"ImageGallery.showTooManyFiles.contentText="
|
||||
+ "There are too many files in the DB to ensure reasonable performance."
|
||||
@ -218,7 +210,7 @@ public final class OpenAction extends CallableSystemAction {
|
||||
Bundle.ImageGallery_showTooManyFiles_contentText(), ButtonType.OK);
|
||||
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||
dialog.setTitle(Bundle.OpenAction_dialogTitle());
|
||||
setDialogIcons(dialog);
|
||||
GuiUtils.setDialogIcons(dialog);
|
||||
dialog.setHeaderText(Bundle.ImageGallery_showTooManyFiles_headerText());
|
||||
dialog.showAndWait();
|
||||
}
|
||||
|
@ -29,9 +29,10 @@ public class TagGroupAction extends AddTagAction {
|
||||
|
||||
public TagGroupAction(final TagName tagName, ImageGalleryController controller) {
|
||||
super(controller, tagName, null);
|
||||
setEventHandler(actionEvent ->
|
||||
new AddTagAction(controller, tagName, ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs())).
|
||||
handle(actionEvent)
|
||||
);
|
||||
setEventHandler(actionEvent -> {
|
||||
controller.getViewState().getGroup().ifPresent(group -> {
|
||||
new AddTagAction(controller, tagName, ImmutableSet.copyOf(group.getFileIDs())).handle(actionEvent);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015-16 Basis Technology Corp.
|
||||
* Copyright 2015-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -34,20 +34,19 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
|
||||
/**
|
||||
* Provides a cached view of the number of files per category, and fires
|
||||
* {@link CategoryChangeEvent}s when files are categorized.
|
||||
* CategoryChangeEvents when files are categorized.
|
||||
*
|
||||
* To receive CategoryChangeEvents, a listener must register itself, and
|
||||
* implement a public method annotated with {@link Subscribe} that accepts one
|
||||
* argument of type CategoryChangeEvent
|
||||
* implement a public method annotated with Subscribe that accepts one argument
|
||||
* of type CategoryChangeEvent
|
||||
*
|
||||
* TODO: currently these two functions (cached counts and events) are separate
|
||||
* although they are related. Can they be integrated more?
|
||||
@ -64,14 +63,14 @@ public class CategoryManager {
|
||||
* initialized from this, and the counting of CAT-0 is always delegated to
|
||||
* this db.
|
||||
*/
|
||||
private DrawableDB db;
|
||||
private final DrawableDB drawableDb;
|
||||
|
||||
/**
|
||||
* Used to distribute {@link CategoryChangeEvent}s
|
||||
* Used to distribute CategoryChangeEvents
|
||||
*/
|
||||
private final EventBus categoryEventBus = new AsyncEventBus(Executors.newSingleThreadExecutor(
|
||||
new BasicThreadFactory.Builder().namingPattern("Category Event Bus").uncaughtExceptionHandler((Thread t, Throwable e) -> { //NON-NLS
|
||||
LOGGER.log(Level.SEVERE, "Uncaught exception in category event bus handler", e); //NON-NLS
|
||||
new BasicThreadFactory.Builder().namingPattern("Category Event Bus").uncaughtExceptionHandler((Thread thread, Throwable throwable) -> { //NON-NLS
|
||||
LOGGER.log(Level.SEVERE, "Uncaught exception in category event bus handler", throwable); //NON-NLS
|
||||
}).build()
|
||||
));
|
||||
|
||||
@ -80,38 +79,29 @@ public class CategoryManager {
|
||||
* the count related methods go through this cache, which loads initial
|
||||
* values from the database if needed.
|
||||
*/
|
||||
private final LoadingCache<DhsImageCategory, LongAdder> categoryCounts =
|
||||
CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper));
|
||||
private final LoadingCache<DhsImageCategory, LongAdder> categoryCounts
|
||||
= CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper));
|
||||
/**
|
||||
* cached TagNames corresponding to Categories, looked up from
|
||||
* autopsyTagManager at initial request or if invalidated by case change.
|
||||
*/
|
||||
private final LoadingCache<DhsImageCategory, TagName> catTagNameMap =
|
||||
CacheBuilder.newBuilder().build(CacheLoader.from(
|
||||
cat -> getController().getTagsManager().getTagName(cat)
|
||||
));
|
||||
private final LoadingCache<DhsImageCategory, TagName> catTagNameMap
|
||||
= CacheBuilder.newBuilder().build(new CacheLoader<DhsImageCategory, TagName>() {
|
||||
@Override
|
||||
public TagName load(DhsImageCategory cat) throws TskCoreException {
|
||||
return getController().getTagsManager().getTagName(cat);
|
||||
}
|
||||
});
|
||||
|
||||
public CategoryManager(ImageGalleryController controller) {
|
||||
this.controller = controller;
|
||||
this.drawableDb = controller.getDatabase();
|
||||
}
|
||||
|
||||
private ImageGalleryController getController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* assign a new db. the counts cache is invalidated and all subsequent db
|
||||
* lookups go to the new db.
|
||||
*
|
||||
* Also clears the Category TagNames
|
||||
*
|
||||
* @param db
|
||||
*/
|
||||
synchronized public void setDb(DrawableDB db) {
|
||||
this.db = db;
|
||||
invalidateCaches();
|
||||
}
|
||||
|
||||
synchronized public void invalidateCaches() {
|
||||
categoryCounts.invalidateAll();
|
||||
catTagNameMap.invalidateAll();
|
||||
@ -131,7 +121,7 @@ public class CategoryManager {
|
||||
// is going on, so always use the list of file IDs we already have along with the
|
||||
// other category counts instead of trying to track it separately.
|
||||
long allOtherCatCount = getCategoryCount(DhsImageCategory.ONE) + getCategoryCount(DhsImageCategory.TWO) + getCategoryCount(DhsImageCategory.THREE) + getCategoryCount(DhsImageCategory.FOUR) + getCategoryCount(DhsImageCategory.FIVE);
|
||||
return db.getNumberOfImageFilesInList() - allOtherCatCount;
|
||||
return drawableDb.getNumberOfImageFilesInList() - allOtherCatCount;
|
||||
} else {
|
||||
return categoryCounts.getUnchecked(cat).sum();
|
||||
}
|
||||
@ -151,7 +141,7 @@ public class CategoryManager {
|
||||
|
||||
/**
|
||||
* decrement the cached value for the number of files with the given
|
||||
* {@link DhsImageCategory}
|
||||
* DhsImageCategory
|
||||
*
|
||||
* @param cat the Category to decrement
|
||||
*/
|
||||
@ -175,7 +165,7 @@ public class CategoryManager {
|
||||
LongAdder longAdder = new LongAdder();
|
||||
longAdder.decrement();
|
||||
try {
|
||||
longAdder.add(db.getCategoryCount(cat));
|
||||
longAdder.add(drawableDb.getCategoryCount(cat));
|
||||
longAdder.increment();
|
||||
} catch (IllegalStateException ex) {
|
||||
LOGGER.log(Level.WARNING, "Case closed while getting files"); //NON-NLS
|
||||
@ -212,15 +202,14 @@ public class CategoryManager {
|
||||
try {
|
||||
categoryEventBus.unregister(listener);
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (e.getMessage().contains("missing event subscriber for an annotated method. Is " + listener + " registered?")) { //NON-NLS
|
||||
/*
|
||||
* We don't fully understand why we are getting this exception
|
||||
* when the groups should all be registered. To avoid cluttering
|
||||
* the logs we have disabled recording this exception. This
|
||||
* documented in issues 738 and 802.
|
||||
*/
|
||||
//LOGGER.log(Level.WARNING, "Attempted to unregister {0} for category change events, but it was not registered.", listener.toString()); //NON-NLS
|
||||
} else {
|
||||
/*
|
||||
* We don't fully understand why we are getting this exception when
|
||||
* the groups should all be registered. To avoid cluttering the logs
|
||||
* we have disabled recording this exception. This documented in
|
||||
* issues 738 and 802.
|
||||
*/
|
||||
|
||||
if (!e.getMessage().contains("missing event subscriber for an annotated method. Is " + listener + " registered?")) { //NON-NLS
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@ -258,7 +247,7 @@ public class CategoryManager {
|
||||
//remove old category tag(s) if necessary
|
||||
for (ContentTag ct : tagsManager.getContentTags(addedTag.getContent())) {
|
||||
if (ct.getId() != addedTag.getId()
|
||||
&& CategoryManager.isCategoryTagName(ct.getName())) {
|
||||
&& CategoryManager.isCategoryTagName(ct.getName())) {
|
||||
try {
|
||||
tagsManager.deleteContentTag(ct);
|
||||
} catch (TskCoreException tskException) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,6 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
@ -32,6 +31,7 @@ import java.util.stream.Collectors;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.util.Pair;
|
||||
import javax.annotation.Nonnull;
|
||||
@ -40,8 +40,8 @@ import org.apache.commons.lang3.text.WordUtils;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
|
||||
import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
|
||||
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
|
||||
import org.sleuthkit.datamodel.AbstractFile;
|
||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||
@ -67,15 +67,21 @@ public abstract class DrawableFile {
|
||||
|
||||
/**
|
||||
* Skip the database query if we have already determined the file type.
|
||||
*
|
||||
* @param file The underlying AbstractFile.
|
||||
* @param analyzed Is the file analyzed.
|
||||
* @param isVideo Is the file a video.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static DrawableFile create(AbstractFile abstractFileById, boolean analyzed, boolean isVideo) {
|
||||
public static DrawableFile create(AbstractFile file, boolean analyzed, boolean isVideo) {
|
||||
return isVideo
|
||||
? new VideoFile(abstractFileById, analyzed)
|
||||
: new ImageFile(abstractFileById, analyzed);
|
||||
? new VideoFile(file, analyzed)
|
||||
: new ImageFile(file, analyzed);
|
||||
}
|
||||
|
||||
public static DrawableFile create(Long id, boolean analyzed) throws TskCoreException, NoCurrentCaseException {
|
||||
return create(Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(id), analyzed);
|
||||
public static DrawableFile create(Long fileID, boolean analyzed) throws TskCoreException, NoCurrentCaseException {
|
||||
return create(Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(fileID), analyzed);
|
||||
}
|
||||
|
||||
private SoftReference<Image> imageRef;
|
||||
@ -149,8 +155,8 @@ public abstract class DrawableFile {
|
||||
return file.getSleuthkitCase();
|
||||
}
|
||||
|
||||
private Pair<DrawableAttribute<?>, Collection<?>> makeAttributeValuePair(DrawableAttribute<?> t) {
|
||||
return new Pair<>(t, t.getValue(DrawableFile.this));
|
||||
private Pair<DrawableAttribute<?>, Collection<?>> makeAttributeValuePair(DrawableAttribute<?> attribute) {
|
||||
return new Pair<>(attribute, attribute.getValue(this));
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
@ -254,42 +260,17 @@ public abstract class DrawableFile {
|
||||
return getSleuthkitCase().getContentTagsByContent(file);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Image getThumbnail() {
|
||||
try {
|
||||
return getThumbnailTask().get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Task<Image> getThumbnailTask() {
|
||||
return ThumbnailCache.getDefault().getThumbnailTask(this);
|
||||
}
|
||||
|
||||
@Deprecated //use non-blocking getReadFullSizeImageTask instead for most cases
|
||||
public Image getFullSizeImage() {
|
||||
try {
|
||||
return getReadFullSizeImageTask().get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<Image> getReadFullSizeImageTask() {
|
||||
Image image = (imageRef != null) ? imageRef.get() : null;
|
||||
if (image == null || image.isError()) {
|
||||
Task<Image> readImageTask = getReadFullSizeImageTaskHelper();
|
||||
readImageTask.stateProperty().addListener(stateProperty -> {
|
||||
switch (readImageTask.getState()) {
|
||||
case SUCCEEDED:
|
||||
try {
|
||||
imageRef = new SoftReference<>(readImageTask.get());
|
||||
} catch (InterruptedException | ExecutionException exception) {
|
||||
LOGGER.log(Level.WARNING, getMessageTemplate(exception), getContentPathSafe());
|
||||
}
|
||||
break;
|
||||
if (readImageTask.getState() == Worker.State.SUCCEEDED) {
|
||||
try {
|
||||
imageRef = new SoftReference<>(readImageTask.get());
|
||||
} catch (InterruptedException | ExecutionException exception) {
|
||||
LOGGER.log(Level.WARNING, getMessageTemplate(exception), getContentPathSafe());
|
||||
}
|
||||
}
|
||||
});
|
||||
return readImageTask;
|
||||
@ -316,14 +297,14 @@ public abstract class DrawableFile {
|
||||
|
||||
/**
|
||||
* Get the width of the visual content.
|
||||
*
|
||||
*
|
||||
* @return The width.
|
||||
*/
|
||||
abstract Double getWidth();
|
||||
|
||||
/**
|
||||
* Get the height of the visual content.
|
||||
*
|
||||
*
|
||||
* @return The height.
|
||||
*/
|
||||
abstract Double getHeight();
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Copyright 2013-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,25 +18,23 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import com.google.common.eventbus.AsyncEventBus;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javax.annotation.Nonnull;
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.ContentTag;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
@ -44,39 +42,38 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Manages Tags, Tagging, and the relationship between Categories and Tags in
|
||||
* the autopsy Db. Delegates some work to the backing {@link TagsManager}.
|
||||
* the autopsy Db. Delegates some work to the backing autopsy TagsManager.
|
||||
*/
|
||||
@NbBundle.Messages({"DrawableTagsManager.followUp=Follow Up",
|
||||
"DrawableTagsManager.bookMark=Bookmark"})
|
||||
public class DrawableTagsManager {
|
||||
public final class DrawableTagsManager {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DrawableTagsManager.class.getName());
|
||||
private static final Logger logger = Logger.getLogger(DrawableTagsManager.class.getName());
|
||||
|
||||
private static Image FOLLOW_UP_IMAGE;
|
||||
private static Image BOOKMARK_IMAGE;
|
||||
private static final Image FOLLOW_UP_IMAGE = new Image("/org/sleuthkit/autopsy/imagegallery/images/flag_red.png");
|
||||
private static final Image BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png");
|
||||
|
||||
final private Object autopsyTagsManagerLock = new Object();
|
||||
private TagsManager autopsyTagsManager;
|
||||
private final TagsManager autopsyTagsManager;
|
||||
/** The tag name corresponding to the "built-in" tag "Follow Up" */
|
||||
private final TagName followUpTagName;
|
||||
private final TagName bookmarkTagName;
|
||||
|
||||
/**
|
||||
* Used to distribute {@link TagsChangeEvent}s
|
||||
* Used to distribute TagsChangeEvents
|
||||
*/
|
||||
private final EventBus tagsEventBus = new AsyncEventBus(
|
||||
Executors.newSingleThreadExecutor(
|
||||
new BasicThreadFactory.Builder().namingPattern("Tags Event Bus").uncaughtExceptionHandler((Thread t, Throwable e) -> { //NON-NLS
|
||||
LOGGER.log(Level.SEVERE, "uncaught exception in event bus handler", e); //NON-NLS
|
||||
}).build()
|
||||
));
|
||||
|
||||
/**
|
||||
* The tag name corresponding to the "built-in" tag "Follow Up"
|
||||
*/
|
||||
private TagName followUpTagName;
|
||||
private TagName bookmarkTagName;
|
||||
|
||||
public DrawableTagsManager(TagsManager autopsyTagsManager) {
|
||||
this.autopsyTagsManager = autopsyTagsManager;
|
||||
private final EventBus tagsEventBus
|
||||
= new AsyncEventBus(
|
||||
Executors.newSingleThreadExecutor(
|
||||
new BasicThreadFactory.Builder()
|
||||
.namingPattern("Tags Event Bus")//NON-NLS
|
||||
.uncaughtExceptionHandler((Thread thread, Throwable throwable)
|
||||
-> logger.log(Level.SEVERE, "Uncaught exception in DrawableTagsManager event bus handler.", throwable)) //NON-NLS
|
||||
.build()));
|
||||
|
||||
public DrawableTagsManager(ImageGalleryController controller) throws TskCoreException {
|
||||
this.autopsyTagsManager = controller.getAutopsyCase().getServices().getTagsManager();
|
||||
followUpTagName = getTagName(Bundle.DrawableTagsManager_followUp());
|
||||
bookmarkTagName = getTagName(Bundle.DrawableTagsManager_bookMark());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,74 +103,51 @@ public class DrawableTagsManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* assign a new TagsManager to back this one, ie when the current case
|
||||
* changes
|
||||
* Get the follow up TagName.
|
||||
*
|
||||
* @param autopsyTagsManager
|
||||
* @return The follow up TagName.
|
||||
*/
|
||||
public void setAutopsyTagsManager(TagsManager autopsyTagsManager) {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
this.autopsyTagsManager = autopsyTagsManager;
|
||||
clearFollowUpTagName();
|
||||
}
|
||||
public TagName getFollowUpTagName() {
|
||||
return followUpTagName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use when closing a case to make sure everything is re-initialized in the
|
||||
* next case.
|
||||
* Get the bookmark TagName.
|
||||
*
|
||||
* @return The bookmark TagName.
|
||||
*/
|
||||
public void clearFollowUpTagName() {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
followUpTagName = null;
|
||||
}
|
||||
private TagName getBookmarkTagName() throws TskCoreException {
|
||||
return bookmarkTagName;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the (cached) follow up TagName
|
||||
* Get all the TagNames that are not categories
|
||||
*
|
||||
* @return
|
||||
* @return All the TagNames that are not categories, in alphabetical order
|
||||
* by displayName.
|
||||
*
|
||||
* @throws TskCoreException
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
public TagName getFollowUpTagName() throws TskCoreException {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
if (Objects.isNull(followUpTagName)) {
|
||||
followUpTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.followUp"));
|
||||
}
|
||||
return followUpTagName;
|
||||
}
|
||||
public List<TagName> getNonCategoryTagNames() throws TskCoreException {
|
||||
return autopsyTagsManager.getAllTagNames().stream()
|
||||
.filter(CategoryManager::isNotCategoryTagName)
|
||||
.distinct().sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Object getBookmarkTagName() throws TskCoreException {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
if (Objects.isNull(bookmarkTagName)) {
|
||||
bookmarkTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.bookMark"));
|
||||
}
|
||||
return bookmarkTagName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get all the TagNames that are not categories
|
||||
* Get all the TagNames that are categories
|
||||
*
|
||||
* @return all the TagNames that are not categories, in alphabetical order
|
||||
* by displayName, or, an empty set if there was an exception
|
||||
* looking them up from the db.
|
||||
* @return All the TagNames that are categories, in alphabetical order by
|
||||
* displayName.
|
||||
*
|
||||
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||
*/
|
||||
@Nonnull
|
||||
public List<TagName> getNonCategoryTagNames() {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
try {
|
||||
return autopsyTagsManager.getAllTagNames().stream()
|
||||
.filter(CategoryManager::isNotCategoryTagName)
|
||||
.distinct().sorted()
|
||||
.collect(Collectors.toList());
|
||||
} catch (TskCoreException | IllegalStateException ex) {
|
||||
LOGGER.log(Level.WARNING, "couldn't access case", ex); //NON-NLS
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
public List<TagName> getCategoryTagNames() throws TskCoreException {
|
||||
return autopsyTagsManager.getAllTagNames().stream()
|
||||
.filter(CategoryManager::isCategoryTagName)
|
||||
.distinct().sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,9 +161,7 @@ public class DrawableTagsManager {
|
||||
* @throws TskCoreException if there was an error reading from the db
|
||||
*/
|
||||
public List<ContentTag> getContentTags(Content content) throws TskCoreException {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
return autopsyTagsManager.getContentTagsByContent(content);
|
||||
}
|
||||
return autopsyTagsManager.getContentTagsByContent(content);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -207,91 +179,56 @@ public class DrawableTagsManager {
|
||||
}
|
||||
|
||||
public TagName getTagName(String displayName) throws TskCoreException {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
try {
|
||||
TagName returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
|
||||
if (returnTagName != null) {
|
||||
return returnTagName;
|
||||
}
|
||||
try {
|
||||
return autopsyTagsManager.addTagName(displayName);
|
||||
} catch (TagsManager.TagNameAlreadyExistsException ex) {
|
||||
returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
|
||||
if (returnTagName != null) {
|
||||
return returnTagName;
|
||||
}
|
||||
throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex);
|
||||
}
|
||||
} catch (NullPointerException | IllegalStateException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Case was closed out from underneath", ex); //NON-NLS
|
||||
throw new TskCoreException("Case was closed out from underneath", ex);
|
||||
|
||||
TagName returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
|
||||
if (returnTagName != null) {
|
||||
return returnTagName;
|
||||
}
|
||||
try {
|
||||
return autopsyTagsManager.addTagName(displayName);
|
||||
} catch (TagsManager.TagNameAlreadyExistsException ex) {
|
||||
returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
|
||||
if (returnTagName != null) {
|
||||
return returnTagName;
|
||||
}
|
||||
throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public TagName getTagName(DhsImageCategory cat) {
|
||||
try {
|
||||
return getTagName(cat.getDisplayName());
|
||||
} catch (TskCoreException ex) {
|
||||
return null;
|
||||
}
|
||||
public TagName getTagName(DhsImageCategory cat) throws TskCoreException {
|
||||
return getTagName(cat.getDisplayName());
|
||||
}
|
||||
|
||||
public ContentTag addContentTag(DrawableFile file, TagName tagName, String comment) throws TskCoreException {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment);
|
||||
}
|
||||
return autopsyTagsManager.addContentTag(file.getAbstractFile(), tagName, comment);
|
||||
}
|
||||
|
||||
public List<ContentTag> getContentTagsByTagName(TagName t) throws TskCoreException {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
return autopsyTagsManager.getContentTagsByTagName(t);
|
||||
}
|
||||
public List<ContentTag> getContentTagsByTagName(TagName tagName) throws TskCoreException {
|
||||
return autopsyTagsManager.getContentTagsByTagName(tagName);
|
||||
}
|
||||
|
||||
public List<TagName> getAllTagNames() throws TskCoreException {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
return autopsyTagsManager.getAllTagNames();
|
||||
}
|
||||
return autopsyTagsManager.getAllTagNames();
|
||||
}
|
||||
|
||||
public List<TagName> getTagNamesInUse() throws TskCoreException {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
return autopsyTagsManager.getTagNamesInUse();
|
||||
}
|
||||
return autopsyTagsManager.getTagNamesInUse();
|
||||
}
|
||||
|
||||
public void deleteContentTag(ContentTag ct) throws TskCoreException {
|
||||
synchronized (autopsyTagsManagerLock) {
|
||||
autopsyTagsManager.deleteContentTag(ct);
|
||||
}
|
||||
public void deleteContentTag(ContentTag contentTag) throws TskCoreException {
|
||||
autopsyTagsManager.deleteContentTag(contentTag);
|
||||
}
|
||||
|
||||
public Node getGraphic(TagName tagname) {
|
||||
try {
|
||||
if (tagname.equals(getFollowUpTagName())) {
|
||||
return new ImageView(getFollowUpImage());
|
||||
return new ImageView(FOLLOW_UP_IMAGE);
|
||||
} else if (tagname.equals(getBookmarkTagName())) {
|
||||
return new ImageView(getBookmarkImage());
|
||||
return new ImageView(BOOKMARK_IMAGE);
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to get \"Follow Up\" or \"Bookmark\"tag name from db.", ex);
|
||||
logger.log(Level.SEVERE, "Failed to get \"Follow Up\" or \"Bookmark\"tag name from db.", ex);
|
||||
}
|
||||
return DrawableAttribute.TAGS.getGraphicForValue(tagname);
|
||||
}
|
||||
|
||||
synchronized private static Image getFollowUpImage() {
|
||||
if (FOLLOW_UP_IMAGE == null) {
|
||||
FOLLOW_UP_IMAGE = new Image("/org/sleuthkit/autopsy/imagegallery/images/flag_red.png");
|
||||
}
|
||||
return FOLLOW_UP_IMAGE;
|
||||
}
|
||||
|
||||
synchronized private static Image getBookmarkImage() {
|
||||
if (BOOKMARK_IMAGE == null) {
|
||||
BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png");
|
||||
}
|
||||
return BOOKMARK_IMAGE;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,26 @@
|
||||
package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
@ -15,26 +33,18 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
*/
|
||||
public class HashSetManager {
|
||||
|
||||
/**
|
||||
* The db that initial values are loaded from.
|
||||
*/
|
||||
private DrawableDB db = null;
|
||||
/** The db that initial values are loaded from. */
|
||||
private final DrawableDB drawableDB;
|
||||
|
||||
public HashSetManager(DrawableDB drawableDB) {
|
||||
this.drawableDB = drawableDB;
|
||||
}
|
||||
|
||||
/**
|
||||
* the internal cache from fileID to a set of hashset names.
|
||||
*/
|
||||
private final LoadingCache<Long, Set<String>> hashSetCache = CacheBuilder.newBuilder().build(CacheLoader.from(this::getHashSetsForFileHelper));
|
||||
|
||||
/**
|
||||
* assign the given db to back this hashset manager.
|
||||
*
|
||||
* @param db
|
||||
*/
|
||||
public void setDb(DrawableDB db) {
|
||||
this.db = db;
|
||||
hashSetCache.invalidateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* helper method to load hashset hits for the given fileID from the db
|
||||
*
|
||||
@ -44,9 +54,14 @@ public class HashSetManager {
|
||||
*/
|
||||
private Set<String> getHashSetsForFileHelper(long fileID) {
|
||||
try {
|
||||
return db.getHashSetsForFile(fileID);
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getLogger(HashSetManager.class.getName()).log(Level.SEVERE, "Failed to get Hash Sets for file", ex); //NON-NLS
|
||||
if (drawableDB.isClosed()) {
|
||||
Logger.getLogger(HashSetManager.class.getName()).log(Level.WARNING, "Failed to get Hash Sets for file. The Db connection was already closed."); //NON-NLS
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
return drawableDB.getHashSetsForFile(fileID);
|
||||
}
|
||||
} catch (TskCoreException | SQLException ex) {
|
||||
Logger.getLogger(HashSetManager.class.getName()).log(Level.SEVERE, "Failed to get Hash Sets for file."); //NON-NLS
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Copyright 2013-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -26,16 +26,19 @@ import java.util.logging.Level;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.beans.binding.IntegerBinding;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.property.ReadOnlyLongProperty;
|
||||
import javafx.beans.property.ReadOnlyLongWrapper;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* Represents a set of image/video files in a group. The UI listens to changes
|
||||
@ -76,7 +79,7 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
|
||||
}
|
||||
|
||||
@SuppressWarnings("ReturnOfCollectionOrArrayField")
|
||||
public synchronized ObservableList<Long> getFileIDs() {
|
||||
public ObservableList<Long> getFileIDs() {
|
||||
return unmodifiableFileIDS;
|
||||
}
|
||||
|
||||
@ -121,11 +124,11 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
|
||||
if (hashSetHitsCount.get() < 0) {
|
||||
try {
|
||||
hashSetHitsCount.set(fileIDs.stream()
|
||||
.map(fileID -> ImageGalleryController.getDefault().getHashSetManager().isInAnyHashSet(fileID))
|
||||
.map(ImageGalleryModule.getController().getHashSetManager()::isInAnyHashSet)
|
||||
.filter(Boolean::booleanValue)
|
||||
.count());
|
||||
} catch (IllegalStateException | NullPointerException ex) {
|
||||
LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS
|
||||
} catch (NoCurrentCaseException ex) {
|
||||
LOGGER.log(Level.WARNING, "Could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS
|
||||
}
|
||||
}
|
||||
return hashSetHitsCount.get();
|
||||
@ -139,10 +142,10 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
|
||||
public final synchronized long getUncategorizedCount() {
|
||||
if (uncatCount.get() < 0) {
|
||||
try {
|
||||
uncatCount.set(ImageGalleryController.getDefault().getDatabase().getUncategorizedCount(fileIDs));
|
||||
uncatCount.set(ImageGalleryModule.getController().getDatabase().getUncategorizedCount(fileIDs));
|
||||
|
||||
} catch (IllegalStateException | NullPointerException ex) {
|
||||
LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS
|
||||
} catch (TskCoreException | NoCurrentCaseException ex) {
|
||||
LOGGER.log(Level.WARNING, "Could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,8 +166,8 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
|
||||
return seen.get();
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanWrapper seenProperty() {
|
||||
return seen;
|
||||
public ReadOnlyBooleanProperty seenProperty() {
|
||||
return seen.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Copyright 2013-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,27 +18,31 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import javafx.scene.Node;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
import org.sleuthkit.datamodel.TagName;
|
||||
|
||||
/**
|
||||
* key identifying information of a {@link Grouping}. Used to look up groups in
|
||||
* {@link Map}s and from the db.
|
||||
* Key identifying information of a DrawableGroup. Used to look up groups in
|
||||
* Maps and from the db.
|
||||
*
|
||||
* @param <T> The type of the values of the attribute this key uses.
|
||||
*/
|
||||
@Immutable
|
||||
public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>> {
|
||||
|
||||
private final T val;
|
||||
|
||||
private final DrawableAttribute<T> attr;
|
||||
private final DataSource dataSource;
|
||||
|
||||
public GroupKey(DrawableAttribute<T> attr, T val) {
|
||||
public GroupKey(DrawableAttribute<T> attr, T val, DataSource dataSource) {
|
||||
this.attr = attr;
|
||||
this.val = val;
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
@ -49,6 +53,10 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
|
||||
return attr;
|
||||
}
|
||||
|
||||
public Optional< DataSource> getDataSource() {
|
||||
return Optional.ofNullable(dataSource);
|
||||
}
|
||||
|
||||
public String getValueDisplayName() {
|
||||
return Objects.equals(attr, DrawableAttribute.TAGS)
|
||||
? ((TagName) getValue()).getDisplayName()
|
||||
@ -63,13 +71,19 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 5;
|
||||
hash = 29 * hash + Objects.hashCode(this.val);
|
||||
hash = 29 * hash + Objects.hashCode(this.attr);
|
||||
|
||||
hash = 79 * hash + Objects.hashCode(this.val);
|
||||
hash = 79 * hash + Objects.hashCode(this.attr);
|
||||
hash = 79 * hash + Objects.hashCode(this.dataSource);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
@ -77,11 +91,14 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
|
||||
return false;
|
||||
}
|
||||
final GroupKey<?> other = (GroupKey<?>) obj;
|
||||
if (this.attr != other.attr) {
|
||||
if (!Objects.equals(this.val, other.val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(this.val, other.val);
|
||||
if (!Objects.equals(this.attr, other.attr)) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(this.dataSource, other.dataSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -92,4 +109,8 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
|
||||
public Node getGraphic() {
|
||||
return attr.getGraphicForValue(val);
|
||||
}
|
||||
|
||||
public long getDataSourceObjId() {
|
||||
return getDataSource().map(DataSource::getId).orElse(0L);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,8 @@ import org.openide.util.NbBundle;
|
||||
/**
|
||||
* Pseudo enum of possible properties to sort groups by.
|
||||
*/
|
||||
@NbBundle.Messages({"GroupSortBy.groupSize=Group Size",
|
||||
@NbBundle.Messages({
|
||||
"GroupSortBy.groupSize=Group Size",
|
||||
"GroupSortBy.groupName=Group Name",
|
||||
"GroupSortBy.none=None",
|
||||
"GroupSortBy.priority=Priority"})
|
||||
@ -37,40 +38,35 @@ public class GroupSortBy implements Comparator<DrawableGroup> {
|
||||
/**
|
||||
* sort the groups by the number of files in each
|
||||
*/
|
||||
public final static GroupSortBy FILE_COUNT =
|
||||
new GroupSortBy(Bundle.GroupSortBy_groupSize(), "folder-open-image.png",
|
||||
public final static GroupSortBy FILE_COUNT
|
||||
= new GroupSortBy(Bundle.GroupSortBy_groupSize(), "folder-open-image.png",
|
||||
Comparator.comparing(DrawableGroup::getSize));
|
||||
|
||||
/**
|
||||
* sort the groups by the natural order of the grouping value ( eg group
|
||||
* them by path alphabetically )
|
||||
*/
|
||||
public final static GroupSortBy GROUP_BY_VALUE =
|
||||
new GroupSortBy(Bundle.GroupSortBy_groupName(), "folder-rename.png",
|
||||
public final static GroupSortBy GROUP_BY_VALUE
|
||||
= new GroupSortBy(Bundle.GroupSortBy_groupName(), "folder-rename.png",
|
||||
Comparator.comparing(DrawableGroup::getGroupByValueDislpayName));
|
||||
|
||||
/**
|
||||
* don't sort the groups just use what ever order they come in (ingest
|
||||
* order)
|
||||
*/
|
||||
public final static GroupSortBy NONE =
|
||||
new GroupSortBy(Bundle.GroupSortBy_none(), "prohibition.png",
|
||||
public final static GroupSortBy NONE
|
||||
= new GroupSortBy(Bundle.GroupSortBy_none(), "prohibition.png",
|
||||
new AllEqualComparator<>());
|
||||
|
||||
/**
|
||||
* sort the groups by some priority metric to be determined and implemented
|
||||
*/
|
||||
public final static GroupSortBy PRIORITY =
|
||||
new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png",
|
||||
public final static GroupSortBy PRIORITY
|
||||
= new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png",
|
||||
Comparator.comparing(DrawableGroup::getHashHitDensity)
|
||||
.thenComparing(Comparator.comparing(DrawableGroup::getUncategorizedCount))
|
||||
.reversed());
|
||||
|
||||
@Override
|
||||
public int compare(DrawableGroup o1, DrawableGroup o2) {
|
||||
return delegate.compare(o1, o2);
|
||||
}
|
||||
|
||||
private final static ObservableList<GroupSortBy> values = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(PRIORITY, NONE, GROUP_BY_VALUE, FILE_COUNT));
|
||||
|
||||
/**
|
||||
@ -109,6 +105,11 @@ public class GroupSortBy implements Comparator<DrawableGroup> {
|
||||
return icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(DrawableGroup o1, DrawableGroup o2) {
|
||||
return delegate.compare(o1, o2);
|
||||
}
|
||||
|
||||
static class AllEqualComparator<A> implements Comparator<A> {
|
||||
|
||||
@Override
|
||||
|
@ -22,9 +22,9 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
*
|
||||
* Encapsulate information about the state of the group section of the UI.
|
||||
*/
|
||||
public class GroupViewState {
|
||||
public final class GroupViewState {
|
||||
|
||||
private final DrawableGroup group;
|
||||
|
||||
@ -32,8 +32,8 @@ public class GroupViewState {
|
||||
|
||||
private final Optional<Long> slideShowfileID;
|
||||
|
||||
public DrawableGroup getGroup() {
|
||||
return group;
|
||||
public Optional<DrawableGroup> getGroup() {
|
||||
return Optional.ofNullable(group);
|
||||
}
|
||||
|
||||
public GroupViewMode getMode() {
|
||||
@ -44,18 +44,18 @@ public class GroupViewState {
|
||||
return slideShowfileID;
|
||||
}
|
||||
|
||||
private GroupViewState(DrawableGroup g, GroupViewMode mode, Long slideShowfileID) {
|
||||
this.group = g;
|
||||
private GroupViewState(DrawableGroup group, GroupViewMode mode, Long slideShowfileID) {
|
||||
this.group = group;
|
||||
this.mode = mode;
|
||||
this.slideShowfileID = Optional.ofNullable(slideShowfileID);
|
||||
}
|
||||
|
||||
public static GroupViewState tile(DrawableGroup g) {
|
||||
return new GroupViewState(g, GroupViewMode.TILE, null);
|
||||
public static GroupViewState tile(DrawableGroup group) {
|
||||
return new GroupViewState(group, GroupViewMode.TILE, null);
|
||||
}
|
||||
|
||||
public static GroupViewState slideShow(DrawableGroup g, Long fileID) {
|
||||
return new GroupViewState(g, GroupViewMode.SLIDE_SHOW, fileID);
|
||||
public static GroupViewState slideShow(DrawableGroup group, Long fileID) {
|
||||
return new GroupViewState(group, GroupViewMode.SLIDE_SHOW, fileID);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -82,10 +82,7 @@ public class GroupViewState {
|
||||
if (this.mode != other.mode) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.slideShowfileID, other.slideShowfileID)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return Objects.equals(this.slideShowfileID, other.slideShowfileID);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,20 +18,43 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.logging.Level;
|
||||
import javafx.scene.control.ButtonBase;
|
||||
import javafx.scene.control.Dialog;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
import org.controlsfx.control.action.Action;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
|
||||
/**
|
||||
* Static utility methods for working with GUI components
|
||||
*/
|
||||
public class GuiUtils {
|
||||
public final class GuiUtils {
|
||||
|
||||
private final static Logger logger = Logger.getLogger(GuiUtils.class.getName());
|
||||
|
||||
/** Image to use as title bar icon in dialogs */
|
||||
private static final Image AUTOPSY_ICON;
|
||||
|
||||
static {
|
||||
Image tempImg = null;
|
||||
try {
|
||||
tempImg = new Image(new URL("nbresloc:/org/netbeans/core/startup/frame.gif").openStream()); //NON-NLS
|
||||
} catch (IOException ex) {
|
||||
logger.log(Level.WARNING, "Failed to load branded icon for progress dialog.", ex); //NON-NLS
|
||||
}
|
||||
AUTOPSY_ICON = tempImg;
|
||||
}
|
||||
|
||||
private GuiUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* create a MenuItem that performs the given action and also set the Action
|
||||
* Create a MenuItem that performs the given action and also set the Action
|
||||
* as the action for the given Button. Usefull to have a SplitMenuButton
|
||||
* remember the last chosen menu item as its action.
|
||||
*
|
||||
@ -51,4 +74,14 @@ public class GuiUtils {
|
||||
});
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the title bar icon for the given Dialog to be the Autopsy logo icon.
|
||||
*
|
||||
* @param dialog The dialog to set the title bar icon for.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
public static void setDialogIcons(Dialog<?> dialog) {
|
||||
((Stage) dialog.getDialogPane().getScene().getWindow()).getIcons().setAll(AUTOPSY_ICON);
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ public class StatusBar extends AnchorPane {
|
||||
});
|
||||
|
||||
Platform.runLater(() -> staleLabel.setTooltip(new Tooltip(Bundle.StatuBar_toolTip())));
|
||||
staleLabel.visibleProperty().bind(controller.stale());
|
||||
staleLabel.visibleProperty().bind(controller.staleProperty());
|
||||
}
|
||||
|
||||
public StatusBar(ImageGalleryController controller) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Copyright 2013-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -34,9 +34,10 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.util.Pair;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager.CategoryChangeEvent;
|
||||
|
||||
/**
|
||||
* Displays summary statistics (counts) for each group
|
||||
@ -51,11 +52,13 @@ public class SummaryTablePane extends AnchorPane {
|
||||
|
||||
@FXML
|
||||
private TableView<Pair<DhsImageCategory, Long>> tableView;
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages({"SummaryTablePane.catColumn=Category",
|
||||
"SummaryTablePane.countColumn=# Files"})
|
||||
@NbBundle.Messages({
|
||||
"SummaryTablePane.catColumn=Category",
|
||||
"SummaryTablePane.countColumn=# Files"})
|
||||
void initialize() {
|
||||
assert catColumn != null : "fx:id=\"catColumn\" was not injected: check your FXML file 'SummaryTablePane.fxml'.";
|
||||
assert countColumn != null : "fx:id=\"countColumn\" was not injected: check your FXML file 'SummaryTablePane.fxml'.";
|
||||
@ -67,11 +70,11 @@ public class SummaryTablePane extends AnchorPane {
|
||||
tableView.prefHeightProperty().set(7 * 25);
|
||||
|
||||
//set up columns
|
||||
catColumn.setCellValueFactory((TableColumn.CellDataFeatures<Pair<DhsImageCategory, Long>, String> p) -> new SimpleObjectProperty<>(p.getValue().getKey().getDisplayName()));
|
||||
catColumn.setCellValueFactory(params -> new SimpleObjectProperty<>(params.getValue().getKey().getDisplayName()));
|
||||
catColumn.setPrefWidth(USE_COMPUTED_SIZE);
|
||||
catColumn.setText(Bundle.SummaryTablePane_catColumn());
|
||||
|
||||
countColumn.setCellValueFactory((TableColumn.CellDataFeatures<Pair<DhsImageCategory, Long>, Long> p) -> new SimpleObjectProperty<>(p.getValue().getValue()));
|
||||
countColumn.setCellValueFactory(params -> new SimpleObjectProperty<>(params.getValue().getValue()));
|
||||
countColumn.setPrefWidth(USE_COMPUTED_SIZE);
|
||||
countColumn.setText(Bundle.SummaryTablePane_countColumn());
|
||||
|
||||
@ -85,14 +88,15 @@ public class SummaryTablePane extends AnchorPane {
|
||||
public SummaryTablePane(ImageGalleryController controller) {
|
||||
this.controller = controller;
|
||||
FXMLConstructor.construct(this, "SummaryTablePane.fxml"); //NON-NLS
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* listen to Category updates and rebuild the table
|
||||
*
|
||||
* @param evt The change event.
|
||||
*/
|
||||
@Subscribe
|
||||
public void handleCategoryChanged(org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager.CategoryChangeEvent evt) {
|
||||
public void handleCategoryChanged(CategoryChangeEvent evt) {
|
||||
final ObservableList<Pair<DhsImageCategory, Long>> data = FXCollections.observableArrayList();
|
||||
if (Case.isCaseOpen()) {
|
||||
for (DhsImageCategory cat : DhsImageCategory.values()) {
|
||||
|
@ -12,29 +12,41 @@
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
|
||||
<fx:root minWidth="-1.0" orientation="HORIZONTAL" prefWidth="-1.0" type="javafx.scene.control.ToolBar" xmlns="http://javafx.com/javafx/8.0.60" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<items>
|
||||
<fx:root minWidth="-1.0" orientation="HORIZONTAL" prefWidth="-1.0" type="javafx.scene.control.ToolBar" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<items>
|
||||
<HBox alignment="CENTER" spacing="5.0">
|
||||
<children>
|
||||
<Label fx:id="groupByLabel" text="Group By:">
|
||||
<labelFor>
|
||||
<ComboBox fx:id="groupByBox" editable="false" />
|
||||
</labelFor>
|
||||
|
||||
</Label>
|
||||
<fx:reference source="groupByBox" />
|
||||
|
||||
<ComboBox fx:id="groupByBox" prefWidth="100.0" />
|
||||
</children>
|
||||
</HBox>
|
||||
<ImageView fx:id="sortHelpImageView" fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/question-frame.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
<HBox alignment="CENTER" spacing="5.0">
|
||||
<children>
|
||||
<Label mnemonicParsing="false" text=" Data Source: ">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/datasource.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Label>
|
||||
<ComboBox fx:id="dataSourceComboBox" editable="false" maxWidth="200.0" />
|
||||
|
||||
</children>
|
||||
</HBox>
|
||||
<ImageView fx:id="sortHelpImageView" fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../images/question-frame.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
|
||||
|
||||
|
||||
|
||||
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="10.0" />
|
||||
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="20.0" />
|
||||
<HBox alignment="CENTER" spacing="5.0">
|
||||
<children>
|
||||
<Label fx:id="tagImageViewLabel" text="Tag Group's Files:">
|
||||
@ -76,7 +88,7 @@
|
||||
<Insets left="5.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="10.0" />
|
||||
<Separator orientation="VERTICAL" prefHeight="-1.0" prefWidth="20.0" />
|
||||
<HBox alignment="CENTER" spacing="5.0">
|
||||
<children>
|
||||
<Label fx:id="thumbnailSizeLabel" text="Thumbnail Size (px):">
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Copyright 2013-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,22 +18,33 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Lists;
|
||||
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 java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.SingleSelectionModel;
|
||||
import javafx.scene.control.Slider;
|
||||
import javafx.scene.control.SplitMenuButton;
|
||||
import javafx.scene.control.ToolBar;
|
||||
@ -42,63 +53,70 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.util.StringConverter;
|
||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
import org.controlsfx.control.PopOver;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.Case;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_ADDED;
|
||||
import static org.sleuthkit.autopsy.casemodule.Case.Events.DATA_SOURCE_DELETED;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeGroupAction;
|
||||
import org.sleuthkit.autopsy.imagegallery.actions.TagGroupAction;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
|
||||
import org.sleuthkit.datamodel.DataSource;
|
||||
|
||||
/**
|
||||
* Controller for the ToolBar
|
||||
*/
|
||||
public class Toolbar extends ToolBar {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(Toolbar.class.getName());
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Toolbar.class.getName());
|
||||
private static final int SIZE_SLIDER_DEFAULT = 100;
|
||||
|
||||
@FXML
|
||||
private ComboBox<Optional<DataSource>> dataSourceComboBox;
|
||||
@FXML
|
||||
private ImageView sortHelpImageView;
|
||||
|
||||
@FXML
|
||||
private ComboBox<DrawableAttribute<?>> groupByBox;
|
||||
|
||||
@FXML
|
||||
private Slider sizeSlider;
|
||||
|
||||
@FXML
|
||||
private SplitMenuButton catGroupMenuButton;
|
||||
|
||||
@FXML
|
||||
private SplitMenuButton tagGroupMenuButton;
|
||||
|
||||
@FXML
|
||||
private Label groupByLabel;
|
||||
|
||||
@FXML
|
||||
private Label tagImageViewLabel;
|
||||
|
||||
@FXML
|
||||
private Label categoryImageViewLabel;
|
||||
|
||||
@FXML
|
||||
private Label thumbnailSizeLabel;
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
private SortChooser<DrawableGroup, GroupSortBy> sortChooser;
|
||||
|
||||
private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(Toolbar.class);
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
private final ObservableList<Optional<DataSource>> dataSources = FXCollections.observableArrayList();
|
||||
private SingleSelectionModel<Optional<DataSource>> dataSourceSelectionModel;
|
||||
|
||||
private final InvalidationListener queryInvalidationListener = new InvalidationListener() {
|
||||
public void invalidated(Observable o) {
|
||||
controller.getGroupManager().regroup(
|
||||
@Override
|
||||
public void invalidated(Observable invalidated) {
|
||||
controller.getGroupManager().regroup(getSelectedDataSource(),
|
||||
groupByBox.getSelectionModel().getSelectedItem(),
|
||||
sortChooser.getComparator(),
|
||||
sortChooser.getSortOrder(),
|
||||
@ -106,61 +124,94 @@ public class Toolbar extends ToolBar {
|
||||
}
|
||||
};
|
||||
|
||||
public DoubleProperty thumbnailSizeProperty() {
|
||||
return sizeSlider.valueProperty();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@NbBundle.Messages({"Toolbar.groupByLabel=Group By:",
|
||||
"Toolbar.sortByLabel=Sort By:",
|
||||
"Toolbar.ascRadio=Ascending",
|
||||
"Toolbar.descRadio=Descending",
|
||||
"Toolbar.tagImageViewLabel=Tag Group's Files:",
|
||||
"Toolbar.categoryImageViewLabel=Categorize Group's Files:",
|
||||
"Toolbar.thumbnailSizeLabel=Thumbnail Size (px):",
|
||||
"Toolbar.sortHelp=The sort direction (ascending/descending) affects the queue of unseen groups that Image Gallery maintains, but changes to this queue aren't apparent until the \"Next Unseen Group\" button is pressed.",
|
||||
"Toolbar.sortHelpTitle=Group Sorting",})
|
||||
@NbBundle.Messages(
|
||||
{"Toolbar.groupByLabel=Group By:",
|
||||
"Toolbar.sortByLabel=Sort By:",
|
||||
"Toolbar.ascRadio=Ascending",
|
||||
"Toolbar.descRadio=Descending",
|
||||
"Toolbar.tagImageViewLabel=Tag Group's Files:",
|
||||
"Toolbar.categoryImageViewLabel=Categorize Group's Files:",
|
||||
"Toolbar.thumbnailSizeLabel=Thumbnail Size (px):",
|
||||
"Toolbar.sortHelp=The sort direction (ascending/descending) affects the queue of unseen groups that Image Gallery maintains, but changes to this queue aren't apparent until the \"Next Unseen Group\" button is pressed.",
|
||||
"Toolbar.sortHelpTitle=Group Sorting",
|
||||
"Toolbar.getDataSources.errMessage=Unable to get datasources for current case.",
|
||||
"Toolbar.nonPathGroupingWarning.content=Proceed with regrouping?",
|
||||
"Toolbar.nonPathGroupingWarning.header=Grouping by attributes other than path does not support the data source filter.\nFiles and groups from all data sources will be shown.",
|
||||
"Toolbar.nonPathGroupingWarning.title=Image Gallery"})
|
||||
void initialize() {
|
||||
assert catGroupMenuButton != null : "fx:id=\"catSelectedMenubutton\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||
assert groupByBox != null : "fx:id=\"groupByBox\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||
assert dataSourceComboBox != null : "fx:id=\"dataSourceComboBox\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||
assert sortHelpImageView != null : "fx:id=\"sortHelpImageView\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||
assert tagImageViewLabel != null : "fx:id=\"tagImageViewLabel\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||
assert tagGroupMenuButton != null : "fx:id=\"tagGroupMenuButton\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||
assert categoryImageViewLabel != null : "fx:id=\"categoryImageViewLabel\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||
assert catGroupMenuButton != null : "fx:id=\"catGroupMenuButton\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||
assert thumbnailSizeLabel != null : "fx:id=\"thumbnailSizeLabel\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||
assert sizeSlider != null : "fx:id=\"sizeSlider\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||
assert tagGroupMenuButton != null : "fx:id=\"tagSelectedMenubutton\" was not injected: check your FXML file 'Toolbar.fxml'.";
|
||||
this.dataSourceSelectionModel = dataSourceComboBox.getSelectionModel();
|
||||
|
||||
controller.viewState().addListener((observable, oldViewState, newViewState) -> {
|
||||
Platform.runLater(() -> syncGroupControlsEnabledState(newViewState));
|
||||
});
|
||||
syncGroupControlsEnabledState(controller.viewState().get());
|
||||
//set internationalized label text
|
||||
groupByLabel.setText(Bundle.Toolbar_groupByLabel());
|
||||
tagImageViewLabel.setText(Bundle.Toolbar_tagImageViewLabel());
|
||||
categoryImageViewLabel.setText(Bundle.Toolbar_categoryImageViewLabel());
|
||||
thumbnailSizeLabel.setText(Bundle.Toolbar_thumbnailSizeLabel());
|
||||
sizeSlider.valueProperty().bindBidirectional(controller.thumbnailSizeProperty());
|
||||
controller.viewStateProperty().addListener((observable, oldViewState, newViewState)
|
||||
-> Platform.runLater(() -> syncGroupControlsEnabledState(newViewState))
|
||||
);
|
||||
syncGroupControlsEnabledState(controller.viewStateProperty().get());
|
||||
|
||||
try {
|
||||
TagGroupAction followUpGroupAction = new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller);
|
||||
tagGroupMenuButton.setOnAction(followUpGroupAction);
|
||||
tagGroupMenuButton.setText(followUpGroupAction.getText());
|
||||
tagGroupMenuButton.setGraphic(followUpGroupAction.getGraphic());
|
||||
} catch (TskCoreException ex) {
|
||||
/*
|
||||
* The problem appears to be a timing issue where a case is closed
|
||||
* before this initialization is completed, which It appears to be
|
||||
* harmless, so we are temporarily changing this log message to a
|
||||
* WARNING.
|
||||
*
|
||||
* TODO (JIRA-3010): SEVERE error logged by image Gallery UI
|
||||
*/
|
||||
if (Case.isCaseOpen()) {
|
||||
LOGGER.log(Level.WARNING, "Could not create Follow Up tag menu item", ex); //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
|
||||
}
|
||||
}
|
||||
tagGroupMenuButton.showingProperty().addListener(showing -> {
|
||||
if (tagGroupMenuButton.isShowing()) {
|
||||
List<MenuItem> selTagMenues = Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
|
||||
tn -> GuiUtils.createAutoAssigningMenuItem(tagGroupMenuButton, new TagGroupAction(tn, controller)));
|
||||
tagGroupMenuButton.getItems().setAll(selTagMenues);
|
||||
initDataSourceComboBox();
|
||||
groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs()));
|
||||
groupByBox.getSelectionModel().select(DrawableAttribute.PATH);
|
||||
groupByBox.disableProperty().bind(controller.regroupDisabledProperty());
|
||||
groupByBox.setCellFactory(listView -> new AttributeListCell());
|
||||
groupByBox.setButtonCell(new AttributeListCell());
|
||||
groupByBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (oldValue == DrawableAttribute.PATH
|
||||
&& newValue != DrawableAttribute.PATH
|
||||
&& getSelectedDataSource() != null) {
|
||||
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, Bundle.Toolbar_nonPathGroupingWarning_content());
|
||||
alert.setHeaderText(Bundle.Toolbar_nonPathGroupingWarning_header());
|
||||
alert.setTitle(Bundle.Toolbar_nonPathGroupingWarning_title());
|
||||
alert.initModality(Modality.APPLICATION_MODAL);
|
||||
alert.initOwner(getScene().getWindow());
|
||||
GuiUtils.setDialogIcons(alert);
|
||||
if (alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) {
|
||||
queryInvalidationListener.invalidated(observable);
|
||||
} else {
|
||||
Platform.runLater(() -> groupByBox.getSelectionModel().select(DrawableAttribute.PATH));
|
||||
}
|
||||
} else {
|
||||
queryInvalidationListener.invalidated(observable);
|
||||
}
|
||||
});
|
||||
|
||||
sortChooser = new SortChooser<>(GroupSortBy.getValues());
|
||||
sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> {
|
||||
final boolean orderDisabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY;
|
||||
sortChooser.setSortOrderDisabled(orderDisabled);
|
||||
|
||||
final SortChooser.ValueType valueType = newComparator == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC;
|
||||
sortChooser.setValueType(valueType);
|
||||
queryInvalidationListener.invalidated(observable);
|
||||
});
|
||||
sortChooser.setComparator(controller.getGroupManager().getSortBy());
|
||||
sortChooser.sortOrderProperty().addListener(queryInvalidationListener);
|
||||
getItems().add(2, sortChooser);
|
||||
|
||||
sortHelpImageView.setCursor(Cursor.HAND);
|
||||
sortHelpImageView.setOnMouseClicked(clicked -> {
|
||||
Text text = new Text(Bundle.Toolbar_sortHelp());
|
||||
text.setWrappingWidth(480); //This is a hack to fix the layout.
|
||||
showPopoverHelp(sortHelpImageView,
|
||||
Bundle.Toolbar_sortHelpTitle(),
|
||||
sortHelpImageView.getImage(), text);
|
||||
});
|
||||
initTagMenuButton();
|
||||
|
||||
CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(DhsImageCategory.FIVE, controller);
|
||||
catGroupMenuButton.setOnAction(cat5GroupAction);
|
||||
catGroupMenuButton.setText(cat5GroupAction.getText());
|
||||
@ -173,41 +224,113 @@ public class Toolbar extends ToolBar {
|
||||
}
|
||||
});
|
||||
|
||||
groupByLabel.setText(Bundle.Toolbar_groupByLabel());
|
||||
tagImageViewLabel.setText(Bundle.Toolbar_tagImageViewLabel());
|
||||
categoryImageViewLabel.setText(Bundle.Toolbar_categoryImageViewLabel());
|
||||
thumbnailSizeLabel.setText(Bundle.Toolbar_thumbnailSizeLabel());
|
||||
}
|
||||
|
||||
groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs()));
|
||||
groupByBox.getSelectionModel().select(DrawableAttribute.PATH);
|
||||
groupByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener);
|
||||
groupByBox.disableProperty().bind(ImageGalleryController.getDefault().regroupDisabled());
|
||||
groupByBox.setCellFactory(listView -> new AttributeListCell());
|
||||
groupByBox.setButtonCell(new AttributeListCell());
|
||||
private DataSource getSelectedDataSource() {
|
||||
Optional<DataSource> empty = Optional.empty();
|
||||
return defaultIfNull(dataSourceSelectionModel.getSelectedItem(), empty).orElse(null);
|
||||
}
|
||||
|
||||
sortChooser = new SortChooser<>(GroupSortBy.getValues());
|
||||
sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> {
|
||||
final boolean orderDisabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY;
|
||||
sortChooser.setSortOrderDisabled(orderDisabled);
|
||||
private void initDataSourceComboBox() {
|
||||
dataSourceComboBox.setCellFactory(param -> new DataSourceCell());
|
||||
dataSourceComboBox.setButtonCell(new DataSourceCell());
|
||||
dataSourceComboBox.setConverter(new StringConverter<Optional<DataSource>>() {
|
||||
@Override
|
||||
public String toString(Optional<DataSource> object) {
|
||||
return object.map(DataSource::getName).orElse("All");
|
||||
}
|
||||
|
||||
final SortChooser.ValueType valueType = newComparator == GroupSortBy.GROUP_BY_VALUE ? SortChooser.ValueType.LEXICOGRAPHIC : SortChooser.ValueType.NUMERIC;
|
||||
sortChooser.setValueType(valueType);
|
||||
queryInvalidationListener.invalidated(observable);
|
||||
@Override
|
||||
public Optional<DataSource> fromString(String string) {
|
||||
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
});
|
||||
dataSourceComboBox.setItems(dataSources);
|
||||
|
||||
sortChooser.sortOrderProperty().addListener(queryInvalidationListener);
|
||||
sortChooser.setComparator(controller.getGroupManager().getSortBy());
|
||||
getItems().add(1, sortChooser);
|
||||
sortHelpImageView.setCursor(Cursor.HAND);
|
||||
Case.addEventTypeSubscriber(ImmutableSet.of(DATA_SOURCE_ADDED, DATA_SOURCE_DELETED),
|
||||
evt -> {
|
||||
Platform.runLater(() -> {
|
||||
Optional<DataSource> selectedItem = dataSourceSelectionModel.getSelectedItem();
|
||||
syncDataSources().addListener(() -> dataSourceSelectionModel.select(selectedItem), Platform::runLater);
|
||||
});
|
||||
});
|
||||
syncDataSources();
|
||||
|
||||
sortHelpImageView.setOnMouseClicked(clicked -> {
|
||||
Text text = new Text(Bundle.Toolbar_sortHelp());
|
||||
text.setWrappingWidth(480); //This is a hack to fix the layout.
|
||||
showPopoverHelp(sortHelpImageView,
|
||||
Bundle.Toolbar_sortHelpTitle(),
|
||||
sortHelpImageView.getImage(), text);
|
||||
controller.getGroupManager().getDataSourceProperty()
|
||||
.addListener((observable, oldDataSource, newDataSource)
|
||||
-> dataSourceSelectionModel.select(Optional.ofNullable(newDataSource)));
|
||||
dataSourceSelectionModel.select(Optional.ofNullable(controller.getGroupManager().getDataSource()));
|
||||
dataSourceComboBox.disableProperty().bind(groupByBox.getSelectionModel().selectedItemProperty().isNotEqualTo(DrawableAttribute.PATH));
|
||||
dataSourceSelectionModel.selectedItemProperty().addListener(queryInvalidationListener);
|
||||
}
|
||||
|
||||
private void initTagMenuButton() {
|
||||
ListenableFuture<TagGroupAction> future = exec.submit(() -> new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller));
|
||||
Futures.addCallback(future, new FutureCallback<TagGroupAction>() {
|
||||
@Override
|
||||
public void onSuccess(TagGroupAction followUpGroupAction) {
|
||||
tagGroupMenuButton.setOnAction(followUpGroupAction);
|
||||
tagGroupMenuButton.setText(followUpGroupAction.getText());
|
||||
tagGroupMenuButton.setGraphic(followUpGroupAction.getGraphic());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
/*
|
||||
* The problem appears to be a timing issue where a case is
|
||||
* closed before this initialization is completed, which It
|
||||
* appears to be harmless, so we are temporarily changing this
|
||||
* log message to a WARNING.
|
||||
*
|
||||
* TODO (JIRA-3010): SEVERE error logged by image Gallery UI
|
||||
*/
|
||||
if (Case.isCaseOpen()) {
|
||||
logger.log(Level.WARNING, "Could not create Follow Up tag menu item", throwable); //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 -> {
|
||||
if (tagGroupMenuButton.isShowing()) {
|
||||
ListenableFuture<List<MenuItem>> getTagsFuture = exec.submit(() -> {
|
||||
return Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
|
||||
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
|
||||
public void onFailure(Throwable t) {
|
||||
logger.log(Level.SEVERE, "Error getting non-gategory tag names.", t);
|
||||
}
|
||||
}, Platform::runLater);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
||||
private ListenableFuture<List<DataSource>> syncDataSources() {
|
||||
ListenableFuture<List<DataSource>> future = exec.submit(controller.getSleuthKitCase()::getDataSources);
|
||||
Futures.addCallback(future, new FutureCallback<List<DataSource>>() {
|
||||
@Override
|
||||
public void onSuccess(List<DataSource> result) {
|
||||
dataSources.setAll(Collections.singleton(Optional.empty()));
|
||||
result.forEach(dataSource -> dataSources.add(Optional.of(dataSource)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
logger.log(Level.SEVERE, "Unable to get datasources for current case.", t); //NON-NLS
|
||||
}
|
||||
}, Platform::runLater);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -237,13 +360,20 @@ public class Toolbar extends ToolBar {
|
||||
popOver.show(owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the tag and catagory controls if and only if there is no group
|
||||
* selected.
|
||||
*
|
||||
* @param newViewState The GroupViewState to use as a source of the
|
||||
* selection.
|
||||
*/
|
||||
private void syncGroupControlsEnabledState(GroupViewState newViewState) {
|
||||
boolean noGroupSelected = newViewState == null
|
||||
? true
|
||||
: newViewState.getGroup() == null;
|
||||
|
||||
tagGroupMenuButton.setDisable(noGroupSelected);
|
||||
catGroupMenuButton.setDisable(noGroupSelected);
|
||||
boolean noGroupSelected = (null == newViewState)
|
||||
|| (null == newViewState.getGroup());
|
||||
Platform.runLater(() -> {
|
||||
tagGroupMenuButton.setDisable(noGroupSelected);
|
||||
catGroupMenuButton.setDisable(noGroupSelected);
|
||||
});
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
@ -256,5 +386,22 @@ public class Toolbar extends ToolBar {
|
||||
public Toolbar(ImageGalleryController controller) {
|
||||
this.controller = controller;
|
||||
FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cell used to represent a DataSource in the dataSourceComboBoc
|
||||
*/
|
||||
static private class DataSourceCell extends ListCell<Optional<DataSource>> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Optional<DataSource> item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText("All");
|
||||
} else {
|
||||
setText(item.map(DataSource::getName).orElse("All"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ public class DrawableTile extends DrawableTileBase {
|
||||
|
||||
@Override
|
||||
Task<Image> newReadImageTask(DrawableFile file) {
|
||||
return file.getThumbnailTask();
|
||||
return getController().getThumbsCache().getThumbnailTask(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-2017 Basis Technology Corp.
|
||||
* Copyright 2013-2018 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -79,9 +79,9 @@ import org.sleuthkit.datamodel.TagName;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* An abstract base class for {@link DrawableTile} and {@link SlideShowView},
|
||||
* since they share a similar node tree and many behaviors, other implementors
|
||||
* of {@link DrawableView}s should implement the interface directly
|
||||
* An abstract base class for DrawableTile and SlideShowView, since they share a
|
||||
* similar node tree and many behaviors, other implementors of DrawableViews
|
||||
* should implement the interface directly
|
||||
*
|
||||
*
|
||||
* TODO: refactor ExternalViewerAction to supply its own name
|
||||
@ -89,7 +89,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
@NbBundle.Messages({"DrawableTileBase.externalViewerAction.text=Open in External Viewer"})
|
||||
public abstract class DrawableTileBase extends DrawableUIBase {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DrawableTileBase.class.getName());
|
||||
private static final Logger logger = Logger.getLogger(DrawableTileBase.class.getName());
|
||||
|
||||
private static final Border UNSELECTED_BORDER = new Border(new BorderStroke(Color.GRAY, BorderStrokeStyle.SOLID, new CornerRadii(2), new BorderWidths(3)));
|
||||
private static final Border SELECTED_BORDER = new Border(new BorderStroke(Color.BLUE, BorderStrokeStyle.SOLID, new CornerRadii(2), new BorderWidths(3)));
|
||||
@ -187,28 +187,32 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
||||
final ArrayList<MenuItem> menuItems = new ArrayList<>();
|
||||
|
||||
menuItems.add(CategorizeAction.getCategoriesMenu(getController()));
|
||||
menuItems.add(AddTagAction.getTagMenu(getController()));
|
||||
|
||||
|
||||
try {
|
||||
menuItems.add(AddTagAction.getTagMenu(getController()));
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error building tagging context menu.", ex);
|
||||
}
|
||||
|
||||
final Collection<AbstractFile> selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
||||
if(selectedFilesList.size() == 1) {
|
||||
if (selectedFilesList.size() == 1) {
|
||||
menuItems.add(DeleteTagAction.getTagMenu(getController()));
|
||||
}
|
||||
|
||||
final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles());
|
||||
extractMenuItem.setOnAction(actionEvent -> {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
|
||||
ExtractAction.getInstance().actionPerformed(new java.awt.event.ActionEvent(etc, 0, null));
|
||||
});
|
||||
});
|
||||
extractMenuItem.setOnAction(actionEvent
|
||||
-> SwingUtilities.invokeLater(() -> {
|
||||
TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
|
||||
ExtractAction.getInstance().actionPerformed(new java.awt.event.ActionEvent(etc, 0, null));
|
||||
}));
|
||||
menuItems.add(extractMenuItem);
|
||||
|
||||
MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer());
|
||||
contentViewer.setOnAction(actionEvent -> {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
new NewWindowViewAction(Bundle.DrawableTileBase_menuItem_showContentViewer(), new FileNode(file.getAbstractFile())).actionPerformed(null);
|
||||
});
|
||||
});
|
||||
contentViewer.setOnAction(actionEvent
|
||||
-> SwingUtilities.invokeLater(() -> {
|
||||
new NewWindowViewAction(Bundle.DrawableTileBase_menuItem_showContentViewer(), new FileNode(file.getAbstractFile()))
|
||||
.actionPerformed(null);
|
||||
}));
|
||||
menuItems.add(contentViewer);
|
||||
|
||||
OpenExternalViewerAction openExternalViewerAction = new OpenExternalViewerAction(file);
|
||||
@ -243,35 +247,32 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
||||
protected abstract String getTextForLabel();
|
||||
|
||||
protected void initialize() {
|
||||
followUpToggle.setOnAction(actionEvent -> {
|
||||
getFile().ifPresent(file -> {
|
||||
if (followUpToggle.isSelected() == true) {
|
||||
try {
|
||||
selectionModel.clearAndSelect(file.getId());
|
||||
new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent);
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex); //NON-NLS
|
||||
}
|
||||
} else {
|
||||
new DeleteFollowUpTagAction(getController(), file).handle(actionEvent);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
followUpToggle.setOnAction(
|
||||
actionEvent -> getFile().ifPresent(
|
||||
file -> {
|
||||
if (followUpToggle.isSelected() == true) {
|
||||
selectionModel.clearAndSelect(file.getId());
|
||||
new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent);
|
||||
} else {
|
||||
new DeleteFollowUpTagAction(getController(), file).handle(actionEvent);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
protected boolean hasFollowUp() {
|
||||
if (getFileID().isPresent()) {
|
||||
try {
|
||||
TagName followUpTagName = getController().getTagsManager().getFollowUpTagName();
|
||||
|
||||
TagName followUpTagName = getController().getTagsManager().getFollowUpTagName();
|
||||
if (getFile().isPresent()) {
|
||||
return DrawableAttribute.TAGS.getValue(getFile().get()).stream()
|
||||
.anyMatch(followUpTagName::equals);
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.WARNING, "failed to get follow up tag name ", ex); //NON-NLS
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -342,18 +343,14 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
||||
@Override
|
||||
public void handleTagAdded(ContentTagAddedEvent evt) {
|
||||
getFileID().ifPresent(fileID -> {
|
||||
try {
|
||||
final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName();
|
||||
final ContentTag addedTag = evt.getAddedTag();
|
||||
if (fileID == addedTag.getContent().getId()
|
||||
&& addedTag.getName().equals(followUpTagName)) {
|
||||
Platform.runLater(() -> {
|
||||
followUpImageView.setImage(followUpIcon);
|
||||
followUpToggle.setSelected(true);
|
||||
});
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to get followup tag name. Unable to update follow up status for file. ", ex); //NON-NLS
|
||||
final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS
|
||||
final ContentTag addedTag = evt.getAddedTag();
|
||||
if (fileID == addedTag.getContent().getId()
|
||||
&& addedTag.getName().equals(followUpTagName)) {
|
||||
Platform.runLater(() -> {
|
||||
followUpImageView.setImage(followUpIcon);
|
||||
followUpToggle.setSelected(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -362,15 +359,11 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
||||
@Override
|
||||
public void handleTagDeleted(ContentTagDeletedEvent evt) {
|
||||
getFileID().ifPresent(fileID -> {
|
||||
try {
|
||||
final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName();
|
||||
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo();
|
||||
if (fileID == deletedTagInfo.getContentID()
|
||||
&& deletedTagInfo.getName().equals(followUpTagName)) {
|
||||
updateFollowUpIcon();
|
||||
}
|
||||
} catch (TskCoreException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to get followup tag name. Unable to update follow up status for file. ", ex); //NON-NLS
|
||||
final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS
|
||||
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo();
|
||||
if (fileID == deletedTagInfo.getContentID()
|
||||
&& deletedTagInfo.getName().equals(followUpTagName)) {
|
||||
updateFollowUpIcon();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015 Basis Technology Corp.
|
||||
* Copyright 2015-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -25,7 +25,6 @@ import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
@ -41,7 +40,6 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.controlsfx.control.action.ActionUtils;
|
||||
import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction;
|
||||
@ -55,10 +53,10 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
"DrawableUIBase.errorLabel.OOMText=Insufficent memory"})
|
||||
abstract public class DrawableUIBase extends AnchorPane implements DrawableView {
|
||||
|
||||
/** The use of SingleThreadExecutor means we can only load a single image at
|
||||
* a time */
|
||||
static final Executor exec = Executors.newSingleThreadExecutor();
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName());
|
||||
|
||||
@FXML
|
||||
BorderPane imageBorder;
|
||||
@FXML
|
||||
@ -96,20 +94,17 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
@Override
|
||||
synchronized public Optional<DrawableFile> getFile() {
|
||||
if (fileIDOpt.isPresent()) {
|
||||
if (fileOpt.isPresent() && fileOpt.get().getId() == fileIDOpt.get()) {
|
||||
return fileOpt;
|
||||
} else {
|
||||
if (!fileOpt.isPresent() || fileOpt.get().getId() != fileIDOpt.get()) {
|
||||
try {
|
||||
fileOpt = Optional.ofNullable(getController().getFileFromId(fileIDOpt.get()));
|
||||
fileOpt = Optional.ofNullable(getController().getFileFromID(fileIDOpt.get()));
|
||||
} catch (TskCoreException ex) {
|
||||
Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex); //NON-NLS
|
||||
fileOpt = Optional.empty();
|
||||
}
|
||||
return fileOpt;
|
||||
}
|
||||
} else {
|
||||
return Optional.empty();
|
||||
fileOpt = Optional.empty();
|
||||
}
|
||||
return fileOpt;
|
||||
}
|
||||
|
||||
protected abstract void setFileHelper(Long newFileID);
|
||||
@ -126,9 +121,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
}
|
||||
|
||||
synchronized protected void updateContent() {
|
||||
if (getFile().isPresent()) {
|
||||
doReadImageTask(getFile().get());
|
||||
}
|
||||
getFile().ifPresent(this::doReadImageTask);
|
||||
}
|
||||
|
||||
synchronized Node doReadImageTask(DrawableFile file) {
|
||||
@ -138,16 +131,16 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
Platform.runLater(() -> imageBorder.setCenter(progressNode));
|
||||
|
||||
//called on fx thread
|
||||
myTask.setOnSucceeded(succeeded -> {
|
||||
myTask.setOnSucceeded(succeeded -> { //on fx thread
|
||||
showImage(file, myTask);
|
||||
synchronized (DrawableUIBase.this) {
|
||||
imageTask = null;
|
||||
}
|
||||
});
|
||||
myTask.setOnFailed(failed -> {
|
||||
myTask.setOnFailed(failed -> { //on fx thread
|
||||
Throwable exception = myTask.getException();
|
||||
if (exception instanceof OutOfMemoryError
|
||||
&& exception.getMessage().contains("Java heap space")) { //NON-NLS
|
||||
&& exception.getMessage().contains("Java heap space")) { //NON-NLS
|
||||
showErrorNode(Bundle.DrawableUIBase_errorLabel_OOMText(), file);
|
||||
} else {
|
||||
showErrorNode(Bundle.DrawableUIBase_errorLabel_text(), file);
|
||||
@ -156,7 +149,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
imageTask = null;
|
||||
}
|
||||
});
|
||||
myTask.setOnCancelled(cancelled -> {
|
||||
myTask.setOnCancelled(cancelled -> { //on fx thread
|
||||
synchronized (DrawableUIBase.this) {
|
||||
imageTask = null;
|
||||
}
|
||||
@ -180,9 +173,12 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new progress indicator to use as a place holder for the image in
|
||||
* this view.
|
||||
*
|
||||
* @param file the value of file
|
||||
* @param imageTask the value of imageTask
|
||||
* @param imageTask The imageTask to get a progress indicator for.
|
||||
*
|
||||
* @return The new Node to use as a progress indicator.
|
||||
*/
|
||||
Node newProgressIndicator(final Task<?> imageTask) {
|
||||
ProgressIndicator loadingProgressIndicator = new ProgressIndicator(-1);
|
||||
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2015-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
@ -16,8 +34,8 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||
|
||||
@ -60,9 +78,8 @@ public interface DrawableView {
|
||||
|
||||
/**
|
||||
* update the visual representation of the category of the assigned file.
|
||||
* Implementations of {@link DrawableView} must register themselves with
|
||||
* {@link CategoryManager#registerListener(java.lang.Object)} to ahve this
|
||||
* method invoked
|
||||
* Implementations of DrawableView } must register themselves with
|
||||
* CategoryManager.registerListener()} to have this method invoked
|
||||
*
|
||||
* @param evt the CategoryChangeEvent to handle
|
||||
*/
|
||||
@ -95,6 +112,7 @@ public interface DrawableView {
|
||||
Logger.getLogger(DrawableView.class.getName()).log(Level.WARNING, "Error looking up hash set hits"); //NON-NLS
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static Border getCategoryBorder(DhsImageCategory category) {
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import javafx.scene.control.RadioButton?>
|
||||
@ -11,6 +12,7 @@
|
||||
<?import javafx.scene.control.ToolBar?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
@ -18,7 +20,7 @@
|
||||
<?import org.controlsfx.control.GridView?>
|
||||
<?import org.controlsfx.control.SegmentedButton?>
|
||||
|
||||
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1">
|
||||
|
||||
<center>
|
||||
<GridView fx:id="gridView" BorderPane.alignment="CENTER" />
|
||||
@ -29,7 +31,7 @@
|
||||
<HBox alignment="CENTER_LEFT" spacing="5.0" BorderPane.alignment="TOP_LEFT">
|
||||
<children>
|
||||
<Label fx:id="bottomLabel" text="Group Viewing History: " />
|
||||
<Button fx:id="backButton" mnemonicParsing="false" text="back">
|
||||
<Button fx:id="backButton" mnemonicParsing="false" text="Back">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
@ -38,7 +40,7 @@
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button fx:id="forwardButton" mnemonicParsing="false" text="forward">
|
||||
<Button fx:id="forwardButton" mnemonicParsing="false" text="Forward">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
@ -56,18 +58,23 @@
|
||||
<right>
|
||||
<HBox alignment="CENTER_RIGHT" spacing="5.0" BorderPane.alignment="TOP_RIGHT">
|
||||
<children>
|
||||
<Button fx:id="nextButton" contentDisplay="RIGHT" mnemonicParsing="false" text="next unseen group" BorderPane.alignment="CENTER_RIGHT">
|
||||
<BorderPane.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</BorderPane.margin>
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../../images/control-double.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
<CheckBox fx:id="seenByOtherExaminersCheckBox" mnemonicParsing="false" text="Don't show groups seen by other examiners" />
|
||||
<AnchorPane fx:id="nextButtonPane" BorderPane.alignment="CENTER_RIGHT">
|
||||
<BorderPane.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</BorderPane.margin>
|
||||
<children>
|
||||
<Button fx:id="nextButton" contentDisplay="RIGHT" minWidth="175.0" mnemonicParsing="false" text="All Groups Gave Been Seen">
|
||||
<graphic>
|
||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../../images/control-double.png" />
|
||||
</image>
|
||||
</ImageView>
|
||||
</graphic>
|
||||
</Button>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</children>
|
||||
<BorderPane.margin>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Copyright 2013-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -21,6 +21,10 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Lists;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@ -53,7 +57,9 @@ import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuItem;
|
||||
@ -81,6 +87,7 @@ import static javafx.scene.input.KeyCode.RIGHT;
|
||||
import static javafx.scene.input.KeyCode.UP;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.Border;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.BorderStroke;
|
||||
@ -90,8 +97,8 @@ import javafx.scene.layout.CornerRadii;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.util.Duration;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.SwingUtilities;
|
||||
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.controlsfx.control.GridCell;
|
||||
import org.controlsfx.control.GridView;
|
||||
@ -107,6 +114,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.ContextMenuActionsProvider;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.directorytree.ExtractAction;
|
||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
|
||||
@ -122,18 +130,17 @@ import org.sleuthkit.autopsy.imagegallery.actions.RedoAction;
|
||||
import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter;
|
||||
import org.sleuthkit.autopsy.imagegallery.actions.TagSelectedFilesAction;
|
||||
import org.sleuthkit.autopsy.imagegallery.actions.UndoAction;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
||||
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
|
||||
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
|
||||
import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* A GroupPane displays the contents of a {@link DrawableGroup}. It supports
|
||||
* both a {@link GridView} based view and a {@link SlideShowView} view by
|
||||
* swapping out its internal components.
|
||||
* A GroupPane displays the contents of a DrawableGroup. It supports both
|
||||
* GridView and SlideShowView modes by swapping out its internal components.
|
||||
*
|
||||
*
|
||||
* TODO: Extract the The GridView instance to a separate class analogous to the
|
||||
@ -144,39 +151,34 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
* https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview
|
||||
*/
|
||||
public class GroupPane extends BorderPane {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(GroupPane.class.getName());
|
||||
|
||||
private static final Logger logger = Logger.getLogger(GroupPane.class.getName());
|
||||
|
||||
private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2);
|
||||
private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2);
|
||||
|
||||
|
||||
private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE);
|
||||
|
||||
private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)),
|
||||
|
||||
private static final Timeline flashAnimation = new Timeline(
|
||||
new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)),
|
||||
new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR))
|
||||
);
|
||||
|
||||
private final FileIDSelectionModel selectionModel;
|
||||
private static final List<KeyCode> 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);
|
||||
|
||||
private final Back backAction;
|
||||
|
||||
private final Forward forwardAction;
|
||||
|
||||
|
||||
private static final List<KeyCode> categoryKeyCodes = Arrays.asList(
|
||||
NUMPAD0, NUMPAD1, NUMPAD2, NUMPAD3, NUMPAD4, NUMPAD5,
|
||||
DIGIT0, DIGIT1, DIGIT2, DIGIT3, DIGIT4, DIGIT5);
|
||||
|
||||
@FXML
|
||||
private Button undoButton;
|
||||
@FXML
|
||||
private Button redoButton;
|
||||
|
||||
|
||||
@FXML
|
||||
private SplitMenuButton catSelectedSplitMenu;
|
||||
|
||||
@FXML
|
||||
private SplitMenuButton tagSelectedSplitMenu;
|
||||
|
||||
@FXML
|
||||
private ToolBar headerToolBar;
|
||||
|
||||
@FXML
|
||||
private ToggleButton cat0Toggle;
|
||||
@FXML
|
||||
@ -189,30 +191,29 @@ public class GroupPane extends BorderPane {
|
||||
private ToggleButton cat4Toggle;
|
||||
@FXML
|
||||
private ToggleButton cat5Toggle;
|
||||
|
||||
|
||||
@FXML
|
||||
private SegmentedButton segButton;
|
||||
|
||||
private SlideShowView slideShowPane;
|
||||
|
||||
|
||||
@FXML
|
||||
private ToggleButton slideShowToggle;
|
||||
|
||||
@FXML
|
||||
private GridView<Long> gridView;
|
||||
|
||||
@FXML
|
||||
private ToggleButton tileToggle;
|
||||
|
||||
|
||||
private SlideShowView slideShowPane;
|
||||
|
||||
@FXML
|
||||
private GridView<Long> gridView;
|
||||
@FXML
|
||||
private Button nextButton;
|
||||
|
||||
@FXML
|
||||
private AnchorPane nextButtonPane;
|
||||
@FXML
|
||||
private CheckBox seenByOtherExaminersCheckBox;
|
||||
@FXML
|
||||
private Button backButton;
|
||||
|
||||
@FXML
|
||||
private Button forwardButton;
|
||||
|
||||
@FXML
|
||||
private Label groupLabel;
|
||||
@FXML
|
||||
@ -223,36 +224,33 @@ public class GroupPane extends BorderPane {
|
||||
private Label catContainerLabel;
|
||||
@FXML
|
||||
private Label catHeadingLabel;
|
||||
|
||||
|
||||
@FXML
|
||||
private HBox catSegmentedContainer;
|
||||
@FXML
|
||||
private HBox catSplitMenuContainer;
|
||||
|
||||
private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler();
|
||||
|
||||
private final NextUnseenGroup nextGroupAction;
|
||||
|
||||
|
||||
private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(GroupPane.class);
|
||||
|
||||
private final ImageGalleryController controller;
|
||||
|
||||
private ContextMenu contextMenu;
|
||||
|
||||
|
||||
private final FileIDSelectionModel selectionModel;
|
||||
private Integer selectionAnchorIndex;
|
||||
|
||||
private final UndoAction undoAction;
|
||||
private final RedoAction redoAction;
|
||||
|
||||
GroupViewMode getGroupViewMode() {
|
||||
return groupViewMode.get();
|
||||
}
|
||||
private final Back backAction;
|
||||
private final Forward forwardAction;
|
||||
private final NextUnseenGroup nextGroupAction;
|
||||
|
||||
/**
|
||||
* the current GroupViewMode of this GroupPane
|
||||
*/
|
||||
private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler();
|
||||
|
||||
private ContextMenu contextMenu;
|
||||
|
||||
/** the current GroupViewMode of this GroupPane */
|
||||
private final SimpleObjectProperty<GroupViewMode> groupViewMode = new SimpleObjectProperty<>(GroupViewMode.TILE);
|
||||
|
||||
/**
|
||||
* the grouping this pane is currently the view for
|
||||
*/
|
||||
/** the grouping this pane is currently the view for */
|
||||
private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>();
|
||||
|
||||
/**
|
||||
@ -263,7 +261,7 @@ public class GroupPane extends BorderPane {
|
||||
*/
|
||||
@ThreadConfined(type = ThreadType.JFX)
|
||||
private final Map<Long, DrawableCell> cellMap = new HashMap<>();
|
||||
|
||||
|
||||
private final InvalidationListener filesSyncListener = (observable) -> {
|
||||
final String header = getHeaderString();
|
||||
final List<Long> fileIds = getGroup().getFileIDs();
|
||||
@ -273,7 +271,7 @@ public class GroupPane extends BorderPane {
|
||||
groupLabel.setText(header);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
public GroupPane(ImageGalleryController controller) {
|
||||
this.controller = controller;
|
||||
this.selectionModel = controller.getSelectionModel();
|
||||
@ -282,10 +280,14 @@ public class GroupPane extends BorderPane {
|
||||
forwardAction = new Forward(controller);
|
||||
undoAction = new UndoAction(controller);
|
||||
redoAction = new RedoAction(controller);
|
||||
|
||||
|
||||
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
GroupViewMode getGroupViewMode() {
|
||||
return groupViewMode.get();
|
||||
}
|
||||
|
||||
@ThreadConfined(type = ThreadType.JFX)
|
||||
public void activateSlideShowViewer(Long slideShowFileID) {
|
||||
groupViewMode.set(GroupViewMode.SLIDE_SHOW);
|
||||
@ -301,16 +303,16 @@ public class GroupPane extends BorderPane {
|
||||
} else {
|
||||
slideShowPane.setFile(slideShowFileID);
|
||||
}
|
||||
|
||||
|
||||
setCenter(slideShowPane);
|
||||
slideShowPane.requestFocus();
|
||||
|
||||
|
||||
}
|
||||
|
||||
void syncCatToggle(DrawableFile file) {
|
||||
getToggleForCategory(file.getCategory()).setSelected(true);
|
||||
}
|
||||
|
||||
|
||||
public void activateTileViewer() {
|
||||
groupViewMode.set(GroupViewMode.TILE);
|
||||
tileToggle.setSelected(true);
|
||||
@ -322,17 +324,19 @@ public class GroupPane extends BorderPane {
|
||||
slideShowPane = null;
|
||||
this.scrollToFileID(selectionModel.lastSelectedProperty().get());
|
||||
}
|
||||
|
||||
|
||||
public DrawableGroup getGroup() {
|
||||
return grouping.get();
|
||||
}
|
||||
|
||||
|
||||
private void selectAllFiles() {
|
||||
selectionModel.clearAndSelectAll(getGroup().getFileIDs());
|
||||
}
|
||||
|
||||
/**
|
||||
* create the string to display in the group header
|
||||
* Create the string to display in the group header.
|
||||
*
|
||||
* @return The string to display in the group header.
|
||||
*/
|
||||
@NbBundle.Messages({"# {0} - default group name",
|
||||
"# {1} - hashset hits count",
|
||||
@ -343,15 +347,15 @@ public class GroupPane extends BorderPane {
|
||||
: Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()),
|
||||
getGroup().getHashSetHitsCount(), getGroup().getSize());
|
||||
}
|
||||
|
||||
|
||||
ContextMenu getContextMenu() {
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
|
||||
ReadOnlyObjectProperty<DrawableGroup> grouping() {
|
||||
return grouping.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
|
||||
private ToggleButton getToggleForCategory(DhsImageCategory category) {
|
||||
switch (category) {
|
||||
case ZERO:
|
||||
@ -383,20 +387,21 @@ public class GroupPane extends BorderPane {
|
||||
"GroupPane.catContainerLabel.displayText=Categorize Selected File:",
|
||||
"GroupPane.catHeadingLabel.displayText=Category:"})
|
||||
void initialize() {
|
||||
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
||||
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
||||
assert cat2Toggle != null : "fx:id=\"cat2Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
||||
assert cat3Toggle != null : "fx:id=\"cat3Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
||||
assert cat4Toggle != null : "fx:id=\"cat4Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
||||
assert cat5Toggle != null : "fx:id=\"cat5Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
||||
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert cat2Toggle != null : "fx:id=\"cat2Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert cat3Toggle != null : "fx:id=\"cat3Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert cat4Toggle != null : "fx:id=\"cat4Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert cat5Toggle != null : "fx:id=\"cat5Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert gridView != null : "fx:id=\"tilePane\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert catSelectedSplitMenu != null : "fx:id=\"grpCatSplitMenu\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
||||
assert tagSelectedSplitMenu != null : "fx:id=\"grpTagSplitMenu\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
||||
assert headerToolBar != null : "fx:id=\"headerToolBar\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
||||
assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
||||
assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
||||
assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
||||
|
||||
assert catSelectedSplitMenu != null : "fx:id=\"grpCatSplitMenu\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert tagSelectedSplitMenu != null : "fx:id=\"grpTagSplitMenu\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert headerToolBar != null : "fx:id=\"headerToolBar\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
assert seenByOtherExaminersCheckBox != null : "fx:id=\"seenByOtherExaminersCheckBox\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||
|
||||
for (DhsImageCategory cat : DhsImageCategory.values()) {
|
||||
ToggleButton toggleForCategory = getToggleForCategory(cat);
|
||||
toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2)));
|
||||
@ -421,32 +426,41 @@ public class GroupPane extends BorderPane {
|
||||
gridView.cellHeightProperty().bind(cellSize);
|
||||
gridView.cellWidthProperty().bind(cellSize);
|
||||
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell());
|
||||
|
||||
|
||||
BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected());
|
||||
catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
|
||||
tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
|
||||
|
||||
|
||||
TagSelectedFilesAction followUpSelectedAction = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); //NON-NLS
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller);
|
||||
tagSelectedSplitMenu.setText(followUpSelectedACtion.getText());
|
||||
tagSelectedSplitMenu.setGraphic(followUpSelectedACtion.getGraphic());
|
||||
tagSelectedSplitMenu.setOnAction(followUpSelectedACtion);
|
||||
} catch (TskCoreException tskCoreException) {
|
||||
LOGGER.log(Level.WARNING, "failed to load FollowUpTagName", tskCoreException); //NON-NLS
|
||||
}
|
||||
tagSelectedSplitMenu.setText(followUpSelectedAction.getText());
|
||||
tagSelectedSplitMenu.setGraphic(followUpSelectedAction.getGraphic());
|
||||
tagSelectedSplitMenu.setOnAction(followUpSelectedAction);
|
||||
tagSelectedSplitMenu.showingProperty().addListener(showing -> {
|
||||
if (tagSelectedSplitMenu.isShowing()) {
|
||||
List<MenuItem> selTagMenues = Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
|
||||
tagName -> GuiUtils.createAutoAssigningMenuItem(tagSelectedSplitMenu, new TagSelectedFilesAction(tagName, controller)));
|
||||
tagSelectedSplitMenu.getItems().setAll(selTagMenues);
|
||||
|
||||
ListenableFuture<List<MenuItem>> getTagsFuture = exec.submit(()
|
||||
-> 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);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller);
|
||||
|
||||
catSelectedSplitMenu.setOnAction(cat5SelectedAction);
|
||||
|
||||
catSelectedSplitMenu.setText(cat5SelectedAction.getText());
|
||||
catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic());
|
||||
catSelectedSplitMenu.showingProperty().addListener(showing -> {
|
||||
@ -456,12 +470,12 @@ public class GroupPane extends BorderPane {
|
||||
catSelectedSplitMenu.getItems().setAll(categoryMenues);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
slideShowToggle.getStyleClass().remove("radio-button");
|
||||
slideShowToggle.getStyleClass().add("toggle-button");
|
||||
tileToggle.getStyleClass().remove("radio-button");
|
||||
tileToggle.getStyleClass().add("toggle-button");
|
||||
|
||||
|
||||
bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText());
|
||||
headerLabel.setText(Bundle.GroupPane_hederLabel_displayText());
|
||||
catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText());
|
||||
@ -481,12 +495,12 @@ public class GroupPane extends BorderPane {
|
||||
//listen to toggles and update view state
|
||||
slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get()));
|
||||
tileToggle.setOnAction(onAction -> activateTileViewer());
|
||||
|
||||
controller.viewState().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState));
|
||||
|
||||
|
||||
controller.viewStateProperty().addListener((observable, oldViewState, newViewState) -> setViewState(newViewState));
|
||||
|
||||
addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler);
|
||||
gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler());
|
||||
|
||||
|
||||
ActionUtils.configureButton(undoAction, undoButton);
|
||||
ActionUtils.configureButton(redoAction, redoButton);
|
||||
ActionUtils.configureButton(forwardAction, forwardButton);
|
||||
@ -502,7 +516,7 @@ public class GroupPane extends BorderPane {
|
||||
nextButton.setEffect(null);
|
||||
onAction.handle(actionEvent);
|
||||
});
|
||||
|
||||
|
||||
nextGroupAction.disabledProperty().addListener((Observable observable) -> {
|
||||
boolean newValue = nextGroupAction.isDisabled();
|
||||
nextButton.setEffect(newValue ? null : DROP_SHADOW);
|
||||
@ -513,17 +527,27 @@ public class GroupPane extends BorderPane {
|
||||
}
|
||||
});
|
||||
|
||||
seenByOtherExaminersCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
nextButtonPane.setDisable(true);
|
||||
nextButtonPane.setCursor(Cursor.WAIT);
|
||||
exec.submit(() -> controller.getGroupManager().setCollaborativeMode(newValue))
|
||||
.addListener(() -> {
|
||||
nextButtonPane.setDisable(false);
|
||||
nextButtonPane.setCursor(Cursor.DEFAULT);
|
||||
}, Platform::runLater);
|
||||
});
|
||||
|
||||
//listen to tile selection and make sure it is visible in scroll area
|
||||
selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> {
|
||||
if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW
|
||||
&& slideShowPane != null) {
|
||||
&& slideShowPane != null) {
|
||||
slideShowPane.setFile(newFileId);
|
||||
} else {
|
||||
scrollToFileID(newFileId);
|
||||
}
|
||||
});
|
||||
|
||||
setViewState(controller.viewState().get());
|
||||
|
||||
setViewState(controller.viewStateProperty().get());
|
||||
}
|
||||
|
||||
//TODO: make sure we are testing complete visability not just bounds intersection
|
||||
@ -532,16 +556,16 @@ public class GroupPane extends BorderPane {
|
||||
if (newFileID == null) {
|
||||
return; //scrolling to no file doesn't make sense, so abort.
|
||||
}
|
||||
|
||||
|
||||
final ObservableList<Long> fileIds = gridView.getItems();
|
||||
|
||||
|
||||
int selectedIndex = fileIds.indexOf(newFileID);
|
||||
if (selectedIndex == -1) {
|
||||
//somehow we got passed a file id that isn't in the curent group.
|
||||
//this should never happen, but if it does everything is going to fail, so abort.
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
getScrollBar().ifPresent(scrollBar -> {
|
||||
DrawableCell cell = cellMap.get(newFileID);
|
||||
|
||||
@ -568,14 +592,14 @@ public class GroupPane extends BorderPane {
|
||||
}
|
||||
cell = cellMap.get(newFileID);
|
||||
}
|
||||
|
||||
|
||||
final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal());
|
||||
Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal());
|
||||
|
||||
//while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates
|
||||
int i = 0;
|
||||
while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) {
|
||||
|
||||
|
||||
if (tileBounds.getMinY() < gridViewBounds.getMinY()) {
|
||||
scrollBar.decrement();
|
||||
} else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) {
|
||||
@ -590,16 +614,16 @@ public class GroupPane extends BorderPane {
|
||||
* assigns a grouping for this pane to represent and initializes grouping
|
||||
* specific properties and listeners
|
||||
*
|
||||
* @param grouping the new grouping assigned to this group
|
||||
* @param newViewState
|
||||
*/
|
||||
void setViewState(GroupViewState viewState) {
|
||||
|
||||
if (isNull(viewState) || isNull(viewState.getGroup())) {
|
||||
void setViewState(GroupViewState newViewState) {
|
||||
|
||||
if (isNull(newViewState) || isNull(newViewState.getGroup().orElse(null))) {
|
||||
if (nonNull(getGroup())) {
|
||||
getGroup().getFileIDs().removeListener(filesSyncListener);
|
||||
}
|
||||
this.grouping.set(null);
|
||||
|
||||
|
||||
Platform.runLater(() -> {
|
||||
gridView.getItems().setAll(Collections.emptyList());
|
||||
setCenter(null);
|
||||
@ -611,40 +635,37 @@ public class GroupPane extends BorderPane {
|
||||
cellMap.clear();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
if (getGroup() != viewState.getGroup()) {
|
||||
if (nonNull(getGroup())) {
|
||||
getGroup().getFileIDs().removeListener(filesSyncListener);
|
||||
}
|
||||
this.grouping.set(viewState.getGroup());
|
||||
|
||||
getGroup().getFileIDs().addListener(filesSyncListener);
|
||||
|
||||
final String header = getHeaderString();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
gridView.getItems().setAll(getGroup().getFileIDs());
|
||||
slideShowToggle.setDisable(gridView.getItems().isEmpty());
|
||||
groupLabel.setText(header);
|
||||
resetScrollBar();
|
||||
if (viewState.getMode() == GroupViewMode.TILE) {
|
||||
activateTileViewer();
|
||||
} else {
|
||||
activateSlideShowViewer(viewState.getSlideShowfileID().orElse(null));
|
||||
}
|
||||
});
|
||||
if (nonNull(getGroup()) && getGroup() != newViewState.getGroup().get()) {
|
||||
getGroup().getFileIDs().removeListener(filesSyncListener);
|
||||
}
|
||||
|
||||
this.grouping.set(newViewState.getGroup().get());
|
||||
|
||||
getGroup().getFileIDs().addListener(filesSyncListener);
|
||||
|
||||
final String header = getHeaderString();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
gridView.getItems().setAll(getGroup().getFileIDs());
|
||||
slideShowToggle.setDisable(gridView.getItems().isEmpty());
|
||||
groupLabel.setText(header);
|
||||
resetScrollBar();
|
||||
if (newViewState.getMode() == GroupViewMode.TILE) {
|
||||
activateTileViewer();
|
||||
} else {
|
||||
activateSlideShowViewer(newViewState.getSlideShowfileID().orElse(null));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ThreadConfined(type = ThreadType.JFX)
|
||||
private void resetScrollBar() {
|
||||
getScrollBar().ifPresent((scrollBar) -> {
|
||||
scrollBar.setValue(0);
|
||||
});
|
||||
getScrollBar().ifPresent(scrollBar -> scrollBar.setValue(0));
|
||||
}
|
||||
|
||||
|
||||
@ThreadConfined(type = ThreadType.JFX)
|
||||
private Optional<ScrollBar> getScrollBar() {
|
||||
if (gridView == null || gridView.getSkin() == null) {
|
||||
@ -652,28 +673,29 @@ public class GroupPane extends BorderPane {
|
||||
}
|
||||
return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); //NON-NLS
|
||||
}
|
||||
|
||||
|
||||
void makeSelection(Boolean shiftDown, Long newFileID) {
|
||||
|
||||
|
||||
if (shiftDown) {
|
||||
//TODO: do more hear to implement slicker multiselect
|
||||
int endIndex = grouping.get().getFileIDs().indexOf(newFileID);
|
||||
int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt();
|
||||
endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().getAsInt();
|
||||
List<Long> subList = grouping.get().getFileIDs().subList(Math.max(0, startIndex), Math.min(endIndex, grouping.get().getFileIDs().size()) + 1);
|
||||
|
||||
|
||||
selectionModel.clearAndSelectAll(subList.toArray(new Long[subList.size()]));
|
||||
selectionModel.select(newFileID);
|
||||
} else {
|
||||
selectionAnchorIndex = null;
|
||||
selectionModel.clearAndSelect(newFileID);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class DrawableCell extends GridCell<Long> {
|
||||
|
||||
|
||||
private final DrawableTile tile = new DrawableTile(GroupPane.this, controller);
|
||||
|
||||
|
||||
DrawableCell() {
|
||||
itemProperty().addListener((ObservableValue<? extends Long> observable, Long oldValue, Long newValue) -> {
|
||||
if (oldValue != null) {
|
||||
@ -689,19 +711,18 @@ public class GroupPane extends BorderPane {
|
||||
}
|
||||
}
|
||||
cellMap.put(newValue, DrawableCell.this);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
setGraphic(tile);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void updateItem(Long item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
tile.setFile(item);
|
||||
}
|
||||
|
||||
|
||||
void resetItem() {
|
||||
tile.setFile(null);
|
||||
}
|
||||
@ -712,10 +733,10 @@ public class GroupPane extends BorderPane {
|
||||
* arrows)
|
||||
*/
|
||||
private class KeyboardHandler implements EventHandler<KeyEvent> {
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(KeyEvent t) {
|
||||
|
||||
|
||||
if (t.getEventType() == KeyEvent.KEY_PRESSED) {
|
||||
switch (t.getCode()) {
|
||||
case SHIFT:
|
||||
@ -758,7 +779,7 @@ public class GroupPane extends BorderPane {
|
||||
t.consume();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) {
|
||||
selectAllFiles();
|
||||
t.consume();
|
||||
@ -772,7 +793,7 @@ public class GroupPane extends BorderPane {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private DhsImageCategory keyCodeToCat(KeyCode t) {
|
||||
if (t != null) {
|
||||
switch (t) {
|
||||
@ -798,16 +819,16 @@ public class GroupPane extends BorderPane {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private void handleArrows(KeyEvent t) {
|
||||
Long lastSelectFileId = selectionModel.lastSelectedProperty().get();
|
||||
|
||||
|
||||
int lastSelectedIndex = lastSelectFileId != null
|
||||
? grouping.get().getFileIDs().indexOf(lastSelectFileId)
|
||||
: Optional.ofNullable(selectionAnchorIndex).orElse(0);
|
||||
|
||||
|
||||
final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1);
|
||||
|
||||
|
||||
final Map<KeyCode, Integer> tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1);
|
||||
|
||||
// implement proper keyboard based multiselect
|
||||
@ -826,41 +847,44 @@ public class GroupPane extends BorderPane {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class MouseHandler implements EventHandler<MouseEvent> {
|
||||
|
||||
|
||||
private ContextMenu buildContextMenu() {
|
||||
ArrayList<MenuItem> menuItems = new ArrayList<>();
|
||||
|
||||
|
||||
menuItems.add(CategorizeAction.getCategoriesMenu(controller));
|
||||
menuItems.add(AddTagAction.getTagMenu(controller));
|
||||
|
||||
|
||||
Collection<? extends ContextMenuActionsProvider> menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class);
|
||||
|
||||
for (ContextMenuActionsProvider provider : menuProviders) {
|
||||
for (final Action act : provider.getActions()) {
|
||||
if (act instanceof Presenter.Popup) {
|
||||
Presenter.Popup aact = (Presenter.Popup) act;
|
||||
menuItems.add(SwingMenuItemAdapter.create(aact.getPopupPresenter()));
|
||||
}
|
||||
}
|
||||
try {
|
||||
menuItems.add(AddTagAction.getTagMenu(controller));
|
||||
} catch (TskCoreException ex) {
|
||||
logger.log(Level.SEVERE, "Error building tagging context menu.", ex);
|
||||
}
|
||||
Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class).stream()
|
||||
.map(ContextMenuActionsProvider::getActions)
|
||||
.flatMap(Collection::stream)
|
||||
.filter(Presenter.Popup.class::isInstance)
|
||||
.map(Presenter.Popup.class::cast)
|
||||
.map(Presenter.Popup::getPopupPresenter)
|
||||
.map(SwingMenuItemAdapter::create)
|
||||
.forEachOrdered(menuItems::add);
|
||||
|
||||
final MenuItem extractMenuItem = new MenuItem(Bundle.GroupPane_gridViewContextMenuItem_extractFiles());
|
||||
extractMenuItem.setOnAction((ActionEvent t) -> {
|
||||
|
||||
extractMenuItem.setOnAction(actionEvent -> {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
|
||||
ExtractAction.getInstance().actionPerformed(new java.awt.event.ActionEvent(etc, 0, null));
|
||||
});
|
||||
});
|
||||
menuItems.add(extractMenuItem);
|
||||
|
||||
|
||||
ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{}));
|
||||
contextMenu.setAutoHide(true);
|
||||
|
||||
contextMenu.setAutoHide(
|
||||
true);
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handle(MouseEvent t) {
|
||||
switch (t.getButton()) {
|
||||
@ -877,11 +901,11 @@ public class GroupPane extends BorderPane {
|
||||
if (t.getClickCount() == 1) {
|
||||
selectAllFiles();
|
||||
}
|
||||
if (selectionModel.getSelected().isEmpty() == false) {
|
||||
if (isNotEmpty(selectionModel.getSelected())) {
|
||||
if (contextMenu == null) {
|
||||
contextMenu = buildContextMenu();
|
||||
}
|
||||
|
||||
|
||||
contextMenu.hide();
|
||||
contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY());
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-15 Basis Technology Corp.
|
||||
* Copyright 2013-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -55,9 +55,9 @@ import org.openide.util.NbBundle;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||
@ -67,13 +67,13 @@ import org.sleuthkit.datamodel.TagName;
|
||||
* Shows details of the selected file.
|
||||
*/
|
||||
@NbBundle.Messages({"MetaDataPane.tableView.placeholder=Select a file to show its details here.",
|
||||
"MetaDataPane.copyMenuItem.text=Copy",
|
||||
"MetaDataPane.titledPane.displayName=Details",
|
||||
"MetaDataPane.attributeColumn.headingName=Attribute",
|
||||
"MetaDataPane.valueColumn.headingName=Value"})
|
||||
"MetaDataPane.copyMenuItem.text=Copy",
|
||||
"MetaDataPane.titledPane.displayName=Details",
|
||||
"MetaDataPane.attributeColumn.headingName=Attribute",
|
||||
"MetaDataPane.valueColumn.headingName=Value"})
|
||||
public class MetaDataPane extends DrawableUIBase {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(MetaDataPane.class.getName());
|
||||
private static final Logger logger = Logger.getLogger(MetaDataPane.class.getName());
|
||||
|
||||
private static final KeyCodeCombination COPY_KEY_COMBINATION = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_DOWN);
|
||||
|
||||
@ -202,7 +202,7 @@ public class MetaDataPane extends DrawableUIBase {
|
||||
|
||||
@Override
|
||||
Task<Image> newReadImageTask(DrawableFile file) {
|
||||
return file.getThumbnailTask();
|
||||
return getController().getThumbsCache().getThumbnailTask(file);
|
||||
}
|
||||
|
||||
public void updateAttributesTable() {
|
||||
|
@ -173,7 +173,7 @@ class GroupCellFactory {
|
||||
private final InvalidationListener groupListener = new GroupListener<>(this);
|
||||
|
||||
/**
|
||||
* reference to group files listener that allows us to remove it from a
|
||||
* Reference to group files listener that allows us to remove it from a
|
||||
* group when a new group is assigned to this Cell
|
||||
*/
|
||||
@Override
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2016 Basis Technology Corp.
|
||||
* Copyright 2016-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -38,6 +38,7 @@ import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
||||
|
||||
/**
|
||||
* Shows path based groups as a tree and others kinds of groups as a flat list (
|
||||
@ -77,17 +78,22 @@ final public class GroupTree extends NavPanel<TreeItem<GroupTreeNode>> {
|
||||
groupTree.setShowRoot(false);
|
||||
|
||||
getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
|
||||
GroupViewState oldState = getController().getViewState();
|
||||
|
||||
while (change.next()) {
|
||||
change.getAddedSubList().stream().forEach(this::insertGroup);
|
||||
change.getRemoved().stream().forEach(this::removeFromTree);
|
||||
}
|
||||
sortGroups();
|
||||
Platform.runLater(() -> {
|
||||
GroupTree.this.sortGroups(false);
|
||||
Optional.ofNullable(oldState)
|
||||
.flatMap(GroupViewState::getGroup)
|
||||
.ifPresent(this::setFocusedGroup);
|
||||
});
|
||||
});
|
||||
|
||||
for (DrawableGroup g : getGroupManager().getAnalyzedGroups()) {
|
||||
insertGroup(g);
|
||||
}
|
||||
sortGroups();
|
||||
getGroupManager().getAnalyzedGroups().forEach(this::insertGroup);
|
||||
Platform.runLater(this::sortGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,12 +108,10 @@ final public class GroupTree extends NavPanel<TreeItem<GroupTreeNode>> {
|
||||
|
||||
if (treeItemForGroup != null) {
|
||||
groupTree.getSelectionModel().select(treeItemForGroup);
|
||||
Platform.runLater(() -> {
|
||||
int row = groupTree.getRow(treeItemForGroup);
|
||||
if (row != -1) {
|
||||
groupTree.scrollTo(row - 2); //put newly selected row 3 from the top
|
||||
}
|
||||
});
|
||||
int row = groupTree.getRow(treeItemForGroup);
|
||||
if (row != -1) {
|
||||
groupTree.scrollTo(row - 2); //put newly selected row 3 from the top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2013-16 Basis Technology Corp.
|
||||
* Copyright 2013-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,7 +18,6 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@ -35,8 +34,8 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||
/**
|
||||
* A node in the nav/hash tree. Manages inserts and removals. Has parents and
|
||||
* children. Does not have graphical properties these are configured in
|
||||
* {@link GroupTreeCell}. Each GroupTreeItem has a TreeNode which has a path
|
||||
* segment and may or may not have a group
|
||||
* GroupTreeCell. Each GroupTreeItem has a TreeNode which has a path segment and
|
||||
* may or may not have a group
|
||||
*/
|
||||
class GroupTreeItem extends TreeItem<GroupTreeNode> {
|
||||
|
||||
@ -131,7 +130,6 @@ class GroupTreeItem extends TreeItem<GroupTreeNode> {
|
||||
}
|
||||
|
||||
synchronized GroupTreeItem getTreeItemForPath(List<String> path) {
|
||||
|
||||
if (path.isEmpty()) {
|
||||
// end of recursion
|
||||
return this;
|
||||
@ -154,9 +152,7 @@ class GroupTreeItem extends TreeItem<GroupTreeNode> {
|
||||
if (parent != null) {
|
||||
parent.childMap.remove(getValue().getPath());
|
||||
|
||||
Platform.runLater(() -> {
|
||||
parent.getChildren().removeAll(Collections.singleton(GroupTreeItem.this));
|
||||
});
|
||||
Platform.runLater(() -> parent.getChildren().remove(this));
|
||||
|
||||
if (parent.childMap.isEmpty()) {
|
||||
parent.removeFromParent();
|
||||
@ -173,8 +169,6 @@ class GroupTreeItem extends TreeItem<GroupTreeNode> {
|
||||
synchronized void resortChildren(Comparator<DrawableGroup> newComp) {
|
||||
this.comp = newComp;
|
||||
getChildren().sort(Comparator.comparing(treeItem -> treeItem.getValue().getGroup(), Comparator.nullsLast(comp)));
|
||||
for (GroupTreeItem ti : childMap.values()) {
|
||||
ti.resortChildren(comp);
|
||||
}
|
||||
childMap.values().forEach(treeItem -> treeItem.resortChildren(comp));
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Autopsy Forensic Browser
|
||||
*
|
||||
* Copyright 2016 Basis Technology Corp.
|
||||
* Copyright 2016-18 Basis Technology Corp.
|
||||
* Contact: carrier <at> sleuthkit <dot> org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -22,6 +22,7 @@ import com.google.common.eventbus.Subscribe;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.SelectionModel;
|
||||
@ -74,9 +75,9 @@ abstract class NavPanel<X> extends Tab {
|
||||
|
||||
sortChooser = new SortChooser<>(GroupComparators.getValues());
|
||||
sortChooser.setComparator(getDefaultComparator());
|
||||
sortChooser.sortOrderProperty().addListener(order -> sortGroups());
|
||||
sortChooser.sortOrderProperty().addListener(order -> NavPanel.this.sortGroups());
|
||||
sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> {
|
||||
sortGroups();
|
||||
NavPanel.this.sortGroups();
|
||||
//only need to listen to changes in category if we are sorting by/ showing the uncategorized count
|
||||
if (newComparator == GroupComparators.UNCATEGORIZED_COUNT) {
|
||||
categoryManager.registerListener(NavPanel.this);
|
||||
@ -90,13 +91,20 @@ abstract class NavPanel<X> extends Tab {
|
||||
toolBar.getItems().add(sortChooser);
|
||||
|
||||
//keep selection in sync with controller
|
||||
controller.viewState().addListener(observable -> {
|
||||
Optional.ofNullable(controller.viewState().get())
|
||||
.map(GroupViewState::getGroup)
|
||||
.ifPresent(this::setFocusedGroup);
|
||||
controller.viewStateProperty().addListener(observable -> {
|
||||
Platform.runLater(()
|
||||
-> Optional.ofNullable(controller.getViewState())
|
||||
.flatMap(GroupViewState::getGroup)
|
||||
.ifPresent(this::setFocusedGroup));
|
||||
});
|
||||
|
||||
getSelectionModel().selectedItemProperty().addListener(o -> updateControllersGroup());
|
||||
// notify controller about group selection in this view
|
||||
getSelectionModel().selectedItemProperty()
|
||||
.addListener((observable, oldItem, newSelectedItem) -> {
|
||||
Optional.ofNullable(newSelectedItem)
|
||||
.map(getDataItemMapper())
|
||||
.ifPresent(group -> controller.advance(GroupViewState.tile(group)));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,7 +114,7 @@ abstract class NavPanel<X> extends Tab {
|
||||
|
||||
@Subscribe
|
||||
public void handleCategoryChange(CategoryManager.CategoryChangeEvent event) {
|
||||
sortGroups();
|
||||
Platform.runLater(this::sortGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,27 +129,24 @@ abstract class NavPanel<X> extends Tab {
|
||||
: comparator.reversed();
|
||||
}
|
||||
|
||||
/**
|
||||
* notify controller about group selection in this view
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
void updateControllersGroup() {
|
||||
Optional.ofNullable(getSelectionModel().getSelectedItem())
|
||||
.map(getDataItemMapper())
|
||||
.ifPresent(group -> controller.advance(GroupViewState.tile(group), false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the groups in this view according to the currently selected sorting
|
||||
* options. Attempts to maintain selection.
|
||||
*/
|
||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||
void sortGroups() {
|
||||
sortGroups(true);
|
||||
}
|
||||
|
||||
public void sortGroups(boolean preserveSelection) {
|
||||
|
||||
X selectedItem = getSelectionModel().getSelectedItem();
|
||||
applyGroupComparator();
|
||||
Optional.ofNullable(selectedItem)
|
||||
.map(getDataItemMapper())
|
||||
.ifPresent(this::setFocusedGroup);
|
||||
if (preserveSelection) {
|
||||
Optional.ofNullable(selectedItem)
|
||||
.map(getDataItemMapper())
|
||||
.ifPresent(this::setFocusedGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -18,13 +18,20 @@
|
||||
*/
|
||||
package org.sleuthkit.autopsy.imagegallery.utils;
|
||||
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executors;
|
||||
import javafx.concurrent.Task;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class TaskUtils {
|
||||
public final class TaskUtils {
|
||||
|
||||
private TaskUtils() {
|
||||
}
|
||||
|
||||
public static <T> Task<T> taskFrom(Callable<T> callable) {
|
||||
return new Task<T>() {
|
||||
@ -35,6 +42,8 @@ public class TaskUtils {
|
||||
};
|
||||
}
|
||||
|
||||
private TaskUtils() {
|
||||
public static ListeningExecutorService getExecutorForClass(Class<?> clazz) {
|
||||
return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
|
||||
new ThreadFactoryBuilder().setNameFormat("Image Gallery " + clazz.getSimpleName() + " BG Thread").build()));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#Updated by build script
|
||||
#Mon, 25 Jun 2018 17:19:36 -0400
|
||||
#Mon, 03 Sep 2018 17:29:44 +0200
|
||||
LBL_splash_window_title=Starting Autopsy
|
||||
SPLASH_HEIGHT=314
|
||||
SPLASH_WIDTH=538
|
||||
|
@ -1,4 +1,4 @@
|
||||
#Updated by build script
|
||||
#Mon, 25 Jun 2018 17:19:36 -0400
|
||||
#Mon, 03 Sep 2018 17:29:44 +0200
|
||||
CTL_MainWindow_Title=Autopsy 4.8.0
|
||||
CTL_MainWindow_Title_No_Project=Autopsy 4.8.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user