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.guiutils</package>
|
||||||
<package>org.sleuthkit.autopsy.healthmonitor</package>
|
<package>org.sleuthkit.autopsy.healthmonitor</package>
|
||||||
<package>org.sleuthkit.autopsy.ingest</package>
|
<package>org.sleuthkit.autopsy.ingest</package>
|
||||||
|
<package>org.sleuthkit.autopsy.ingest.events</package>
|
||||||
<package>org.sleuthkit.autopsy.keywordsearchservice</package>
|
<package>org.sleuthkit.autopsy.keywordsearchservice</package>
|
||||||
<package>org.sleuthkit.autopsy.menuactions</package>
|
<package>org.sleuthkit.autopsy.menuactions</package>
|
||||||
<package>org.sleuthkit.autopsy.modules.encryptiondetection</package>
|
<package>org.sleuthkit.autopsy.modules.encryptiondetection</package>
|
||||||
|
@ -107,7 +107,7 @@ public class ImageUtils {
|
|||||||
* NOTE: Must be cleared when the case is changed.
|
* NOTE: Must be cleared when the case is changed.
|
||||||
*/
|
*/
|
||||||
@Messages({"ImageUtils.ffmpegLoadedError.title=OpenCV FFMpeg",
|
@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<>();
|
private static final ConcurrentHashMap<Long, File> cacheFileMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -218,7 +218,7 @@ public class ImageUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return VideoUtils.isVideoThumbnailSupported(file)
|
return VideoUtils.isVideoThumbnailSupported(file)
|
||||||
|| isImageThumbnailSupported(file);
|
|| isImageThumbnailSupported(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -413,7 +413,7 @@ public class ImageUtils {
|
|||||||
String cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
|
String cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
|
||||||
return Paths.get(cacheDirectory, "thumbnails", fileID + ".png").toFile(); //NON-NLS
|
return Paths.get(cacheDirectory, "thumbnails", fileID + ".png").toFile(); //NON-NLS
|
||||||
} catch (NoCurrentCaseException e) {
|
} 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;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -23,11 +23,13 @@ import org.openide.util.NbBundle;
|
|||||||
import static org.sleuthkit.autopsy.directorytree.Bundle.*;
|
import static org.sleuthkit.autopsy.directorytree.Bundle.*;
|
||||||
|
|
||||||
@NbBundle.Messages({"SelectionContext.dataSources=Data Sources",
|
@NbBundle.Messages({"SelectionContext.dataSources=Data Sources",
|
||||||
|
"SelectionContext.dataSourceFiles=Data Source Files",
|
||||||
"SelectionContext.views=Views"})
|
"SelectionContext.views=Views"})
|
||||||
enum SelectionContext {
|
enum SelectionContext {
|
||||||
DATA_SOURCES(SelectionContext_dataSources()),
|
DATA_SOURCES(SelectionContext_dataSources()),
|
||||||
VIEWS(SelectionContext_views()),
|
VIEWS(SelectionContext_views()),
|
||||||
OTHER(""); // Subnode of another node.
|
OTHER(""), // Subnode of another node.
|
||||||
|
DATA_SOURCE_FILES(SelectionContext_dataSourceFiles());
|
||||||
|
|
||||||
private final String displayName;
|
private final String displayName;
|
||||||
|
|
||||||
@ -36,7 +38,7 @@ enum SelectionContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static SelectionContext getContextFromName(String name) {
|
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;
|
return DATA_SOURCES;
|
||||||
} else if (name.equals(VIEWS.getName())) {
|
} else if (name.equals(VIEWS.getName())) {
|
||||||
return VIEWS;
|
return VIEWS;
|
||||||
@ -64,6 +66,16 @@ enum SelectionContext {
|
|||||||
// One level below root node. Should be one of DataSources, Views, or Results
|
// One level below root node. Should be one of DataSources, Views, or Results
|
||||||
return SelectionContext.getContextFromName(n.getDisplayName());
|
return SelectionContext.getContextFromName(n.getDisplayName());
|
||||||
} else {
|
} 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());
|
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
|
* Event published when analysis (ingest) of a data source included in an ingest
|
||||||
* job is completed.
|
* 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.
|
* 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
|
* Event published when analysis (ingest) of a data source included in an ingest
|
||||||
* job is started.
|
* job is started.
|
||||||
*/
|
*/
|
||||||
public class DataSourceAnalysisStartedEvent extends DataSourceAnalysisEvent implements Serializable {
|
public final class DataSourceAnalysisStartedEvent extends DataSourceAnalysisEvent implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2015-17 Basis Technology Corp.
|
* Copyright 2015-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -56,9 +56,7 @@ public final class PromptDialogManager {
|
|||||||
@NbBundle.Messages("PrompDialogManager.buttonType.update=Update DB")
|
@NbBundle.Messages("PrompDialogManager.buttonType.update=Update DB")
|
||||||
private static final ButtonType UPDATE = new ButtonType(Bundle.PrompDialogManager_buttonType_update(), ButtonBar.ButtonData.OK_DONE);
|
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;
|
private static final Image AUTOPSY_ICON;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@ -222,7 +220,7 @@ public final class PromptDialogManager {
|
|||||||
dialog.setHeaderText(Bundle.PromptDialogManager_showTooManyFiles_headerText());
|
dialog.setHeaderText(Bundle.PromptDialogManager_showTooManyFiles_headerText());
|
||||||
dialog.showAndWait();
|
dialog.showAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NbBundle.Messages({
|
@NbBundle.Messages({
|
||||||
"PromptDialogManager.showTimeLineDisabledMessage.contentText="
|
"PromptDialogManager.showTimeLineDisabledMessage.contentText="
|
||||||
+ "Timeline functionality is not available yet."
|
+ "Timeline functionality is not available yet."
|
||||||
|
@ -230,6 +230,7 @@
|
|||||||
<package>org.apache.commons.codec.digest</package>
|
<package>org.apache.commons.codec.digest</package>
|
||||||
<package>org.apache.commons.codec.language</package>
|
<package>org.apache.commons.codec.language</package>
|
||||||
<package>org.apache.commons.codec.net</package>
|
<package>org.apache.commons.codec.net</package>
|
||||||
|
<package>org.apache.commons.collections4</package>
|
||||||
<package>org.apache.commons.csv</package>
|
<package>org.apache.commons.csv</package>
|
||||||
<package>org.apache.commons.io</package>
|
<package>org.apache.commons.io</package>
|
||||||
<package>org.apache.commons.io.comparator</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.ListeningExecutorService;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
import java.beans.PropertyChangeEvent;
|
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
|
import javafx.beans.property.DoubleProperty;
|
||||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
|
||||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||||
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||||
|
import javafx.beans.property.ReadOnlyLongWrapper;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
@ -43,25 +46,14 @@ import javafx.beans.property.SimpleDoubleProperty;
|
|||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.concurrent.Worker;
|
import javafx.concurrent.Worker;
|
||||||
import javafx.geometry.Insets;
|
import javax.annotation.Nonnull;
|
||||||
import javafx.scene.Node;
|
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
||||||
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 org.netbeans.api.progress.ProgressHandle;
|
import org.netbeans.api.progress.ProgressHandle;
|
||||||
import org.openide.util.Cancellable;
|
import org.openide.util.Cancellable;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.Case.CaseType;
|
||||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
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.History;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
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.actions.UndoRedoManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
|
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.DrawableFile;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.HashSetManager;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.HashSetManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
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.ingest.IngestManager;
|
||||||
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
|
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
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;
|
||||||
|
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
import org.sleuthkit.datamodel.TskData;
|
import org.sleuthkit.datamodel.TskData;
|
||||||
|
|
||||||
@ -90,8 +82,7 @@ import org.sleuthkit.datamodel.TskData;
|
|||||||
*/
|
*/
|
||||||
public final class ImageGalleryController {
|
public final class ImageGalleryController {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ImageGalleryController.class.getName());
|
private static final Logger logger = Logger.getLogger(ImageGalleryController.class.getName());
|
||||||
private static ImageGalleryController instance;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* true if Image Gallery should listen to ingest events, false if it should
|
* 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 stale = new ReadOnlyBooleanWrapper(false);
|
||||||
|
|
||||||
private final ReadOnlyBooleanWrapper metaDataCollapsed = 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 ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false);
|
||||||
private final ReadOnlyIntegerWrapper dbTaskQueueSize = new ReadOnlyIntegerWrapper(0);
|
private final ReadOnlyIntegerWrapper dbTaskQueueSize = new ReadOnlyIntegerWrapper(0);
|
||||||
|
|
||||||
@ -111,36 +102,23 @@ public final class ImageGalleryController {
|
|||||||
|
|
||||||
private final History<GroupViewState> historyManager = new History<>();
|
private final History<GroupViewState> historyManager = new History<>();
|
||||||
private final UndoRedoManager undoManager = new UndoRedoManager();
|
private final UndoRedoManager undoManager = new UndoRedoManager();
|
||||||
private final GroupManager groupManager = new GroupManager(this);
|
private final ThumbnailCache thumbnailCache = new ThumbnailCache(this);
|
||||||
private final HashSetManager hashSetManager = new HashSetManager();
|
private final GroupManager groupManager;
|
||||||
private final CategoryManager categoryManager = new CategoryManager(this);
|
private final HashSetManager hashSetManager;
|
||||||
private final DrawableTagsManager tagsManager = new DrawableTagsManager(null);
|
private final CategoryManager categoryManager;
|
||||||
|
private final DrawableTagsManager tagsManager;
|
||||||
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 ListeningExecutorService dbExecutor;
|
private ListeningExecutorService dbExecutor;
|
||||||
|
|
||||||
private SleuthkitCase sleuthKitCase;
|
private final Case autopsyCase;
|
||||||
private DrawableDB db;
|
private final SleuthkitCase sleuthKitCase;
|
||||||
|
private final DrawableDB drawableDB;
|
||||||
|
|
||||||
public static synchronized ImageGalleryController getDefault() {
|
public Case getAutopsyCase() {
|
||||||
if (instance == null) {
|
return autopsyCase;
|
||||||
instance = new ImageGalleryController();
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty getMetaDataCollapsed() {
|
public ReadOnlyBooleanProperty metaDataCollapsedProperty() {
|
||||||
return metaDataCollapsed.getReadOnlyProperty();
|
return metaDataCollapsed.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,19 +126,19 @@ public final class ImageGalleryController {
|
|||||||
this.metaDataCollapsed.set(metaDataCollapsed);
|
this.metaDataCollapsed.set(metaDataCollapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyDoubleProperty thumbnailSizeProperty() {
|
public DoubleProperty thumbnailSizeProperty() {
|
||||||
return thumbnailSize.getReadOnlyProperty();
|
return thumbnailSizeProp;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GroupViewState getViewState() {
|
public GroupViewState getViewState() {
|
||||||
return historyManager.getCurrentState();
|
return historyManager.getCurrentState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty regroupDisabled() {
|
public ReadOnlyBooleanProperty regroupDisabledProperty() {
|
||||||
return regroupDisabled.getReadOnlyProperty();
|
return regroupDisabled.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<GroupViewState> viewState() {
|
public ReadOnlyObjectProperty<GroupViewState> viewStateProperty() {
|
||||||
return historyManager.currentState();
|
return historyManager.currentState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,8 +150,8 @@ public final class ImageGalleryController {
|
|||||||
return groupManager;
|
return groupManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public DrawableDB getDatabase() {
|
public DrawableDB getDatabase() {
|
||||||
return db;
|
return drawableDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setListeningEnabled(boolean enabled) {
|
public void setListeningEnabled(boolean enabled) {
|
||||||
@ -193,14 +171,9 @@ public final class ImageGalleryController {
|
|||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
stale.set(b);
|
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();
|
return stale.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,50 +182,57 @@ public final class ImageGalleryController {
|
|||||||
return stale.get();
|
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 {
|
try {
|
||||||
//if we just turned on listening and a case is open and that case is not up to date
|
// if we just turned on listening and a single-user case is open and that case is not up to date, then rebuild it
|
||||||
if (newValue && !oldValue && ImageGalleryModule.isDrawableDBStale(Case.getCurrentCaseThrows())) {
|
// 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
|
//populate the db
|
||||||
queueDBTask(new CopyAnalyzedFiles(instance, db, sleuthKitCase));
|
this.rebuildDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (NoCurrentCaseException ex) {
|
} 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) -> {
|
viewStateProperty().addListener((Observable observable) -> {
|
||||||
//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) -> {
|
|
||||||
//when the viewed group changes, clear the selection and the undo/redo history
|
//when the viewed group changes, clear the selection and the undo/redo history
|
||||||
selectionModel.clearSelection();
|
selectionModel.clearSelection();
|
||||||
undoManager.clear();
|
undoManager.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
regroupDisabled.addListener(observable -> checkForGroups());
|
|
||||||
|
|
||||||
IngestManager ingestManager = IngestManager.getInstance();
|
IngestManager ingestManager = IngestManager.getInstance();
|
||||||
PropertyChangeListener ingestEventHandler =
|
PropertyChangeListener ingestEventHandler
|
||||||
propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled);
|
= propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled);
|
||||||
|
|
||||||
ingestManager.addIngestModuleEventListener(ingestEventHandler);
|
ingestManager.addIngestModuleEventListener(ingestEventHandler);
|
||||||
ingestManager.addIngestJobEventListener(ingestEventHandler);
|
ingestManager.addIngestJobEventListener(ingestEventHandler);
|
||||||
|
|
||||||
dbTaskQueueSize.addListener(obs -> this.updateRegroupDisabled());
|
dbTaskQueueSize.addListener(obs -> this.updateRegroupDisabled());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyBooleanProperty getCanAdvance() {
|
public ReadOnlyBooleanProperty getCanAdvance() {
|
||||||
@ -264,10 +244,7 @@ public final class ImageGalleryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
|
||||||
public void advance(GroupViewState newState, boolean forceShowTree) {
|
public void advance(GroupViewState newState) {
|
||||||
if (forceShowTree && showTree != null) {
|
|
||||||
showTree.run();
|
|
||||||
}
|
|
||||||
historyManager.advance(newState);
|
historyManager.advance(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,131 +261,92 @@ public final class ImageGalleryController {
|
|||||||
regroupDisabled.set((dbTaskQueueSize.get() > 0) || IngestManager.getInstance().isIngestRunning());
|
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.
|
* configure the controller for a specific case.
|
||||||
*
|
*
|
||||||
* @param theNewCase the case to configure the controller for
|
* @param theNewCase the case to configure the controller for
|
||||||
|
*
|
||||||
|
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||||
*/
|
*/
|
||||||
public synchronized void setCase(Case theNewCase) {
|
/**
|
||||||
if (null == theNewCase) {
|
* Rebuilds the DrawableDB database.
|
||||||
reset();
|
*
|
||||||
} else {
|
*/
|
||||||
this.sleuthKitCase = theNewCase.getSleuthkitCase();
|
public void rebuildDB() {
|
||||||
this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), this);
|
// queue a rebuild task for each stale data source
|
||||||
|
getStaleDataSourceIds().forEach(dataSourceObjId -> queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* reset the state of the controller (eg if the case is closed)
|
* reset the state of the controller (eg if the case is closed)
|
||||||
*/
|
*/
|
||||||
public synchronized void reset() {
|
public synchronized void reset() {
|
||||||
LOGGER.info("resetting ImageGalleryControler to initial state."); //NON-NLS
|
logger.info("Closing ImageGalleryControler for case."); //NON-NLS
|
||||||
|
|
||||||
selectionModel.clearSelection();
|
selectionModel.clearSelection();
|
||||||
setListeningEnabled(false);
|
thumbnailCache.clearCache();
|
||||||
ThumbnailCache.getDefault().clearCache();
|
|
||||||
historyManager.clear();
|
historyManager.clear();
|
||||||
groupManager.clear();
|
groupManager.reset();
|
||||||
tagsManager.clearFollowUpTagName();
|
|
||||||
tagsManager.unregisterListener(groupManager);
|
|
||||||
tagsManager.unregisterListener(categoryManager);
|
|
||||||
shutDownDBExecutor();
|
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) {
|
try {
|
||||||
db.closeDBCon();
|
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() {
|
synchronized private void shutDownDBExecutor() {
|
||||||
@ -417,7 +355,7 @@ public final class ImageGalleryController {
|
|||||||
try {
|
try {
|
||||||
dbExecutor.awaitTermination(30, TimeUnit.SECONDS);
|
dbExecutor.awaitTermination(30, TimeUnit.SECONDS);
|
||||||
} catch (InterruptedException ex) {
|
} 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));
|
Platform.runLater(() -> dbTaskQueueSize.set(dbTaskQueueSize.get() - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
public DrawableFile getFileFromID(Long fileID) throws TskCoreException {
|
||||||
synchronized public DrawableFile getFileFromId(Long fileID) throws TskCoreException {
|
return drawableDB.getFileFromID(fileID);
|
||||||
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 ReadOnlyDoubleProperty regroupProgress() {
|
public ReadOnlyDoubleProperty regroupProgress() {
|
||||||
return groupManager.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() {
|
public HashSetManager getHashSetManager() {
|
||||||
return hashSetManager;
|
return hashSetManager;
|
||||||
}
|
}
|
||||||
@ -501,10 +407,6 @@ public final class ImageGalleryController {
|
|||||||
return tagsManager;
|
return tagsManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setShowTree(Runnable showTree) {
|
|
||||||
this.showTree = showTree;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UndoRedoManager getUndoManager() {
|
public UndoRedoManager getUndoManager() {
|
||||||
return undoManager;
|
return undoManager;
|
||||||
}
|
}
|
||||||
@ -515,6 +417,12 @@ public final class ImageGalleryController {
|
|||||||
|
|
||||||
public synchronized SleuthkitCase getSleuthKitCase() {
|
public synchronized SleuthkitCase getSleuthKitCase() {
|
||||||
return sleuthKitCase;
|
return sleuthKitCase;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThumbnailCache getThumbsCache() {
|
||||||
|
return thumbnailCache;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -594,7 +502,7 @@ public final class ImageGalleryController {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileTask(AbstractFile f, DrawableDB taskDB) {
|
FileTask(AbstractFile f, DrawableDB taskDB) {
|
||||||
super();
|
super();
|
||||||
this.file = f;
|
this.file = f;
|
||||||
this.taskDB = taskDB;
|
this.taskDB = taskDB;
|
||||||
@ -604,7 +512,7 @@ public final class ImageGalleryController {
|
|||||||
/**
|
/**
|
||||||
* task that updates one file in database with results from ingest
|
* 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) {
|
UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||||
super(f, taskDB);
|
super(f, taskDB);
|
||||||
@ -631,7 +539,7 @@ public final class ImageGalleryController {
|
|||||||
/**
|
/**
|
||||||
* task that updates one file in database with results from ingest
|
* 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) {
|
RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
|
||||||
super(f, 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",
|
@NbBundle.Messages({"BulkTask.committingDb.status=committing image/video database",
|
||||||
"BulkTask.stopCopy.status=Stopping copy to drawable db task.",
|
"BulkTask.stopCopy.status=Stopping copy to drawable db task.",
|
||||||
"BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."})
|
"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 class BulkTransferTask extends BackgroundTask {
|
||||||
abstract static private class BulkTransferTask extends BackgroundTask {
|
|
||||||
|
|
||||||
static private final String FILE_EXTENSION_CLAUSE =
|
static private final String FILE_EXTENSION_CLAUSE
|
||||||
"(name LIKE '%." //NON-NLS
|
= "(extension LIKE '" //NON-NLS
|
||||||
+ String.join("' OR name LIKE '%.", FileTypeUtils.getAllSupportedExtensions()) //NON-NLS
|
+ String.join("' OR extension LIKE '", FileTypeUtils.getAllSupportedExtensions()) //NON-NLS
|
||||||
+ "')";
|
+ "') ";
|
||||||
|
|
||||||
static private final String MIMETYPE_CLAUSE =
|
static private final String MIMETYPE_CLAUSE
|
||||||
"(mime_type LIKE '" //NON-NLS
|
= "(mime_type LIKE '" //NON-NLS
|
||||||
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
|
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
|
||||||
+ "') ";
|
+ "') ";
|
||||||
|
|
||||||
static final String DRAWABLE_QUERY =
|
private final String DRAWABLE_QUERY;
|
||||||
//grab files with supported extension
|
private final String DATASOURCE_CLAUSE;
|
||||||
"(" + 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
|
|
||||||
|
|
||||||
final ImageGalleryController controller;
|
protected final ImageGalleryController controller;
|
||||||
final DrawableDB taskDB;
|
protected final DrawableDB taskDB;
|
||||||
final SleuthkitCase tskCase;
|
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.controller = controller;
|
||||||
this.taskDB = taskDB;
|
this.taskDB = controller.getDatabase();
|
||||||
this.tskCase = tskCase;
|
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
|
* @param success true if the transfer was successful
|
||||||
*/
|
*/
|
||||||
abstract void cleanup(boolean success);
|
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
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@ -706,24 +637,32 @@ public final class ImageGalleryController {
|
|||||||
progressHandle.start();
|
progressHandle.start();
|
||||||
updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status());
|
updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status());
|
||||||
|
|
||||||
|
DrawableDB.DrawableTransaction drawableDbTransaction = null;
|
||||||
|
CaseDbTransaction caseDbTransaction = null;
|
||||||
try {
|
try {
|
||||||
//grab all files with supported extension or detected mime types
|
//grab all files with supported extension or detected mime types
|
||||||
final List<AbstractFile> files = getFiles();
|
final List<AbstractFile> files = getFiles();
|
||||||
progressHandle.switchToDeterminate(files.size());
|
progressHandle.switchToDeterminate(files.size());
|
||||||
|
|
||||||
|
taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS);
|
||||||
|
|
||||||
updateProgress(0.0);
|
updateProgress(0.0);
|
||||||
|
taskCompletionStatus = true;
|
||||||
|
int workDone = 0;
|
||||||
|
|
||||||
//do in transaction
|
//do in transaction
|
||||||
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
|
drawableDbTransaction = taskDB.beginTransaction();
|
||||||
int workDone = 0;
|
caseDbTransaction = tskCase.beginTransaction();
|
||||||
for (final AbstractFile f : files) {
|
for (final AbstractFile f : files) {
|
||||||
if (isCancelled() || Thread.interrupted()) {
|
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();
|
progressHandle.finish();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processFile(f, tr);
|
processFile(f, drawableDbTransaction, caseDbTransaction);
|
||||||
|
|
||||||
workDone++;
|
workDone++;
|
||||||
progressHandle.progress(f.getName(), workDone);
|
progressHandle.progress(f.getName(), workDone);
|
||||||
@ -737,23 +676,42 @@ public final class ImageGalleryController {
|
|||||||
updateProgress(1.0);
|
updateProgress(1.0);
|
||||||
|
|
||||||
progressHandle.start();
|
progressHandle.start();
|
||||||
taskDB.commitTransaction(tr, true);
|
caseDbTransaction.commit();
|
||||||
|
taskDB.commitTransaction(drawableDbTransaction, true);
|
||||||
|
|
||||||
} catch (TskCoreException ex) {
|
} 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());
|
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());
|
MessageNotifyUtil.Notify.warn(Bundle.BulkTask_errPopulating_errMsg(), ex.getMessage());
|
||||||
cleanup(false);
|
cleanup(false);
|
||||||
return;
|
return;
|
||||||
} finally {
|
} finally {
|
||||||
progressHandle.finish();
|
progressHandle.finish();
|
||||||
|
if (taskCompletionStatus) {
|
||||||
|
taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.COMPLETE);
|
||||||
|
}
|
||||||
updateMessage("");
|
updateMessage("");
|
||||||
updateProgress(-1.0);
|
updateProgress(-1.0);
|
||||||
}
|
}
|
||||||
cleanup(true);
|
cleanup(taskCompletionStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract ProgressHandle getInitialProgressHandle();
|
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",
|
@NbBundle.Messages({"CopyAnalyzedFiles.committingDb.status=committing image/video database",
|
||||||
"CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.",
|
"CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.",
|
||||||
"CopyAnalyzedFiles.errPopulating.errMsg=There was an error populating Image Gallery database."})
|
"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) {
|
CopyAnalyzedFiles(long dataSourceObjId, ImageGalleryController controller) {
|
||||||
super(controller, taskDB, tskCase);
|
super(dataSourceObjId, controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cleanup(boolean success) {
|
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
|
@Override
|
||||||
List<AbstractFile> getFiles() throws TskCoreException {
|
void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDbTransaction) throws TskCoreException {
|
||||||
return tskCase.findAllFilesWhere(DRAWABLE_QUERY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr) {
|
|
||||||
final boolean known = f.getKnown() == TskData.FileKnown.KNOWN;
|
final boolean known = f.getKnown() == TskData.FileKnown.KNOWN;
|
||||||
|
|
||||||
if (known) {
|
if (known) {
|
||||||
@ -791,13 +746,21 @@ public final class ImageGalleryController {
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (FileTypeUtils.hasDrawableMIMEType(f)) { //supported mimetype => analyzed
|
//supported mimetype => analyzed
|
||||||
taskDB.updateFile(DrawableFile.create(f, true, false), tr);
|
if (null != f.getMIMEType() && FileTypeUtils.hasDrawableMIMEType(f)) {
|
||||||
} else { //unsupported mimtype => analyzed but shouldn't include
|
taskDB.updateFile(DrawableFile.create(f, true, false), tr, caseDbTransaction);
|
||||||
taskDB.removeFile(f.getId(), tr);
|
} 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) {
|
} 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"
|
* 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
|
* 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
|
* 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"})
|
@NbBundle.Messages({"PrePopulateDataSourceFiles.committingDb.status=committing image/video database"})
|
||||||
static private class PrePopulateDataSourceFiles extends BulkTransferTask {
|
static class PrePopulateDataSourceFiles extends BulkTransferTask {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(PrePopulateDataSourceFiles.class.getName());
|
|
||||||
|
|
||||||
private final Content dataSource;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @param dataSourceObjId The object ID of the DataSource that is being
|
||||||
* @param dataSourceId Data source object ID
|
* pre-populated into the DrawableDB.
|
||||||
|
* @param controller The controller for this task.
|
||||||
*/
|
*/
|
||||||
PrePopulateDataSourceFiles(Content dataSource, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
|
PrePopulateDataSourceFiles(long dataSourceObjId, ImageGalleryController controller) {
|
||||||
super(controller, taskDB, tskCase);
|
super(dataSourceObjId, controller);
|
||||||
this.dataSource = dataSource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -838,14 +794,8 @@ public final class ImageGalleryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) {
|
void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) {
|
||||||
taskDB.insertFile(DrawableFile.create(f, false, false), tr);
|
taskDB.insertFile(DrawableFile.create(f, false, false), tr, caseDBTransaction);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
List<AbstractFile> getFiles() throws TskCoreException {
|
|
||||||
long datasourceID = dataSource.getDataSource().getId();
|
|
||||||
return tskCase.findAllFilesWhere("data_source_obj_id = " + datasourceID + " AND " + DRAWABLE_QUERY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -854,116 +804,4 @@ public final class ImageGalleryController {
|
|||||||
return ProgressHandle.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this);
|
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
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-15 Basis Technology Corp.
|
* Copyright 2013-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,32 +18,77 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery;
|
package org.sleuthkit.autopsy.imagegallery;
|
||||||
|
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
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 static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
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.Logger;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
|
||||||
|
import org.sleuthkit.autopsy.events.AutopsyEvent;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
|
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.autopsy.modules.filetypeid.FileTypeDetector;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
import org.sleuthkit.datamodel.TskData;
|
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"})
|
@NbBundle.Messages({"ImageGalleryModule.moduleName=Image Gallery"})
|
||||||
public class ImageGalleryModule {
|
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 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() {
|
static String getModuleName() {
|
||||||
return MODULE_NAME;
|
return MODULE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the Path to the Case's ImageGallery ModuleOutput subfolder; ie
|
* get the Path to the Case's ImageGallery ModuleOutput subfolder; ie
|
||||||
* ".../[CaseName]/ModuleOutput/Image Gallery/"
|
* ".../[CaseName]/ModuleOutput/Image Gallery/"
|
||||||
@ -53,7 +98,7 @@ public class ImageGalleryModule {
|
|||||||
*
|
*
|
||||||
* @return the Path to the ModuleOuput subfolder for Image Gallery
|
* @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());
|
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
|
* @return true if the drawable db is out of date for the given case, false
|
||||||
* otherwise
|
* otherwise
|
||||||
*/
|
*/
|
||||||
public static boolean isDrawableDBStale(Case c) {
|
public static boolean isDrawableDBStale(Case c) throws TskCoreException {
|
||||||
if (c != null) {
|
return new ImageGalleryController(c).isDataSourcesTableStale();
|
||||||
String stale = new PerCaseProperties(c).getConfigSetting(ImageGalleryModule.MODULE_NAME, PerCaseProperties.STALE);
|
|
||||||
return StringUtils.isNotBlank(stale) ? Boolean.valueOf(stale) : true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the given file 'supported' and not 'known'(nsrl hash hit). If so we
|
* Is the given file 'supported' and not 'known'(nsrl hash hit). If so we
|
||||||
* should include it in {@link DrawableDB} and UI
|
* 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
|
* @return true if the given {@link AbstractFile} is "drawable" and not
|
||||||
* 'known', else false
|
* '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);
|
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;
|
package org.sleuthkit.autopsy.imagegallery;
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Image/Video Gallery panel in the NetBeans provided Options Dialogs
|
* 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);
|
enabledForCaseBox.setEnabled(Case.isCaseOpen() && IngestManager.getInstance().isIngestRunning() == false);
|
||||||
});
|
});
|
||||||
|
|
||||||
enabledByDefaultBox.addActionListener((ActionEvent e) -> {
|
enabledByDefaultBox.addActionListener(actionEvent -> controller.changed());
|
||||||
controller.changed();
|
enabledForCaseBox.addActionListener(actionEvent -> controller.changed());
|
||||||
});
|
|
||||||
|
|
||||||
enabledForCaseBox.addActionListener((ActionEvent e) -> {
|
|
||||||
controller.changed();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -204,19 +196,18 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
|
|||||||
|
|
||||||
void store() {
|
void store() {
|
||||||
ImageGalleryPreferences.setEnabledByDefault(enabledByDefaultBox.isSelected());
|
ImageGalleryPreferences.setEnabledByDefault(enabledByDefaultBox.isSelected());
|
||||||
ImageGalleryController.getDefault().setListeningEnabled(enabledForCaseBox.isSelected());
|
|
||||||
ImageGalleryPreferences.setGroupCategorizationWarningDisabled(groupCategorizationWarningBox.isSelected());
|
ImageGalleryPreferences.setGroupCategorizationWarningDisabled(groupCategorizationWarningBox.isSelected());
|
||||||
|
|
||||||
// If a case is open, save the per case setting
|
// If a case is open, save the per case setting
|
||||||
try {
|
try {
|
||||||
Case openCase = Case.getCurrentCaseThrows();
|
Case openCase = Case.getCurrentCaseThrows();
|
||||||
|
ImageGalleryModule.getController().setListeningEnabled(enabledForCaseBox.isSelected());
|
||||||
new PerCaseProperties(openCase).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.ENABLED, Boolean.toString(enabledForCaseBox.isSelected()));
|
new PerCaseProperties(openCase).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.ENABLED, Boolean.toString(enabledForCaseBox.isSelected()));
|
||||||
} catch (NoCurrentCaseException ex) {
|
} catch (NoCurrentCaseException ex) {
|
||||||
// It's not an error if there's no case open
|
// 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.
|
* @return true if new cases should have image analyzer enabled.
|
||||||
*/
|
*/
|
||||||
public static boolean isEnabledByDefault() {
|
public static boolean isEnabledByDefault() {
|
||||||
final boolean aBoolean = preferences.getBoolean(ENABLED_BY_DEFAULT, true);
|
return preferences.getBoolean(ENABLED_BY_DEFAULT, true);
|
||||||
return aBoolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setEnabledByDefault(boolean b) {
|
public static void setEnabledByDefault(boolean b) {
|
||||||
|
@ -18,27 +18,52 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery;
|
package org.sleuthkit.autopsy.imagegallery;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.Observable;
|
||||||
import javafx.embed.swing.JFXPanel;
|
import javafx.embed.swing.JFXPanel;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.ChoiceDialog;
|
||||||
|
import javafx.scene.control.ProgressIndicator;
|
||||||
import javafx.scene.control.SplitPane;
|
import javafx.scene.control.SplitPane;
|
||||||
import javafx.scene.control.TabPane;
|
import javafx.scene.control.TabPane;
|
||||||
|
import javafx.scene.layout.Background;
|
||||||
|
import javafx.scene.layout.BackgroundFill;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.CornerRadii;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.VBox;
|
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.ExplorerManager;
|
||||||
import org.openide.explorer.ExplorerUtils;
|
import org.openide.explorer.ExplorerUtils;
|
||||||
import org.openide.util.Lookup;
|
import org.openide.util.Lookup;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
import org.openide.util.NbBundle.Messages;
|
import org.openide.util.NbBundle.Messages;
|
||||||
import org.openide.windows.Mode;
|
import org.openide.windows.Mode;
|
||||||
import org.openide.windows.RetainLocation;
|
import org.openide.windows.RetainLocation;
|
||||||
import org.openide.windows.TopComponent;
|
import org.openide.windows.TopComponent;
|
||||||
import org.openide.windows.WindowManager;
|
import org.openide.windows.WindowManager;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
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.StatusBar;
|
||||||
import org.sleuthkit.autopsy.imagegallery.gui.SummaryTablePane;
|
import org.sleuthkit.autopsy.imagegallery.gui.SummaryTablePane;
|
||||||
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
|
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.drawableviews.MetaDataPane;
|
||||||
import org.sleuthkit.autopsy.imagegallery.gui.navpanel.GroupTree;
|
import org.sleuthkit.autopsy.imagegallery.gui.navpanel.GroupTree;
|
||||||
import org.sleuthkit.autopsy.imagegallery.gui.navpanel.HashHitGroupList;
|
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.
|
* 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 class ImageGalleryTopComponent extends TopComponent implements ExplorerManager.Provider, Lookup.Provider {
|
||||||
|
|
||||||
public final static String PREFERRED_ID = "ImageGalleryTopComponent"; // NON-NLS
|
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 static volatile boolean topComponentInitialized = false;
|
||||||
|
|
||||||
private final ExplorerManager em = new ExplorerManager();
|
private final ExplorerManager em = new ExplorerManager();
|
||||||
private final Lookup lookup = (ExplorerUtils.createLookup(em, getActionMap()));
|
private final Lookup lookup = (ExplorerUtils.createLookup(em, getActionMap()));
|
||||||
|
|
||||||
private final ImageGalleryController controller = ImageGalleryController.getDefault();
|
private ImageGalleryController controller;
|
||||||
|
|
||||||
private SplitPane splitPane;
|
private SplitPane splitPane;
|
||||||
private StackPane centralStack;
|
private StackPane centralStack;
|
||||||
private BorderPane borderPane = new BorderPane();
|
private final BorderPane borderPane = new BorderPane();
|
||||||
private StackPane fullUIStack;
|
private StackPane fullUIStack;
|
||||||
private MetaDataPane metaDataTable;
|
private MetaDataPane metaDataTable;
|
||||||
private GroupPane groupPane;
|
private GroupPane groupPane;
|
||||||
@ -88,24 +116,97 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
|||||||
private VBox leftPane;
|
private VBox leftPane;
|
||||||
private Scene myScene;
|
private Scene myScene;
|
||||||
|
|
||||||
public static void openTopComponent() {
|
private Node infoOverlay;
|
||||||
//TODO:eventually move to this model, throwing away everything and rebuilding controller groupmanager etc for each case.
|
private final Region infoOverLayBackground = new TranslucentRegion();
|
||||||
// synchronized (OpenTimelineAction.class) {
|
|
||||||
// if (timeLineController == null) {
|
/**
|
||||||
// timeLineController = new TimeLineController();
|
* Returns whether the ImageGallery window is open or not.
|
||||||
// LOGGER.log(Level.WARNING, "Failed to get TimeLineController from lookup. Instantiating one directly.S");
|
*
|
||||||
// }
|
* @return true, if Image gallery is opened, false otherwise
|
||||||
// }
|
*/
|
||||||
// timeLineController.openTimeLine();
|
public static boolean isImageGalleryOpen() {
|
||||||
final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
|
|
||||||
if (tc != null) {
|
final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
|
||||||
topComponentInitialized = true;
|
if (topComponent != null) {
|
||||||
if (tc.isOpened() == false) {
|
return topComponent.isOpened();
|
||||||
tc.open();
|
|
||||||
}
|
|
||||||
tc.toFront();
|
|
||||||
tc.requestActive();
|
|
||||||
}
|
}
|
||||||
|
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() {
|
public static void closeTopComponent() {
|
||||||
@ -115,48 +216,57 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
|||||||
try {
|
try {
|
||||||
etc.close();
|
etc.close();
|
||||||
} catch (Exception e) {
|
} 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());
|
setName(Bundle.CTL_ImageGalleryTopComponent());
|
||||||
initComponents();
|
initComponents();
|
||||||
|
setController(ImageGalleryModule.getController());
|
||||||
|
}
|
||||||
|
|
||||||
Platform.runLater(() -> {//initialize jfx ui
|
synchronized private void setController(ImageGalleryController controller) {
|
||||||
fullUIStack = new StackPane(); //this is passed into controller
|
if (this.controller != null && notEqual(this.controller, controller)) {
|
||||||
myScene = new Scene(fullUIStack);
|
this.controller.reset();
|
||||||
jfxPanel.setScene(myScene);
|
}
|
||||||
groupPane = new GroupPane(controller);
|
this.controller = controller;
|
||||||
centralStack = new StackPane(groupPane); //this is passed into controller
|
Platform.runLater(new Runnable() {
|
||||||
fullUIStack.getChildren().add(borderPane);
|
@Override
|
||||||
splitPane = new SplitPane();
|
public void run() {
|
||||||
borderPane.setCenter(splitPane);
|
//initialize jfx ui
|
||||||
Toolbar toolbar = new Toolbar(controller);
|
fullUIStack = new StackPane(); //this is passed into controller
|
||||||
borderPane.setTop(toolbar);
|
myScene = new Scene(fullUIStack);
|
||||||
borderPane.setBottom(new StatusBar(controller));
|
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);
|
Platform.runLater(() -> checkForGroups());
|
||||||
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));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,4 +322,100 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
|
|||||||
public Lookup getLookup() {
|
public Lookup getLookup() {
|
||||||
return lookup;
|
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
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-2018 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,26 +18,19 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery;
|
package org.sleuthkit.autopsy.imagegallery;
|
||||||
|
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* The org.openide.modules.OnStart annotation tells NetBeans to invoke this
|
||||||
* The {@link org.openide.modules.OnStart} annotation tells NetBeans to invoke
|
* class's run method.
|
||||||
* this class's {@link OnStart#run()} method
|
|
||||||
*/
|
*/
|
||||||
@org.openide.modules.OnStart
|
@org.openide.modules.OnStart
|
||||||
public class OnStart implements Runnable {
|
public class OnStart implements Runnable {
|
||||||
|
|
||||||
static private final Logger LOGGER = Logger.getLogger(OnStart.class.getName());
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* This method is invoked by virtue of the OnStart annotation on the this
|
||||||
*
|
* class
|
||||||
* This method is invoked by virtue of the {@link OnStart} annotation on the
|
|
||||||
* {@link ImageGalleryModule} class
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
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 ENABLED = "enabled"; //NON-NLS
|
||||||
|
|
||||||
public static final String STALE = "stale"; //NON-NLS
|
|
||||||
|
|
||||||
private final Case theCase;
|
private final Case theCase;
|
||||||
|
|
||||||
PerCaseProperties(Case c) {
|
PerCaseProperties(Case c) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-15 Basis Technology Corp.
|
* Copyright 2013-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -54,9 +54,13 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
* TODO: this was only a singleton for convenience, convert this to
|
* TODO: this was only a singleton for convenience, convert this to
|
||||||
* non-singleton class -jm?
|
* 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;
|
private static final int MAX_THUMBNAIL_SIZE = 300;
|
||||||
|
|
||||||
@ -71,10 +75,6 @@ public enum ThumbnailCache {
|
|||||||
.softValues()
|
.softValues()
|
||||||
.expireAfterAccess(10, TimeUnit.MINUTES).build();
|
.expireAfterAccess(10, TimeUnit.MINUTES).build();
|
||||||
|
|
||||||
public static ThumbnailCache getDefault() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* currently desired icon size. is bound in {@link Toolbar}
|
* currently desired icon size. is bound in {@link Toolbar}
|
||||||
*/
|
*/
|
||||||
@ -109,7 +109,7 @@ public enum ThumbnailCache {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public Image get(Long fileID) {
|
public Image get(Long fileID) {
|
||||||
try {
|
try {
|
||||||
return get(ImageGalleryController.getDefault().getFileFromId(fileID));
|
return get(controller.getFileFromID(fileID));
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
LOGGER.log(Level.WARNING, "Failed to load thumbnail for file: " + fileID, ex.getCause()); //NON-NLS
|
LOGGER.log(Level.WARNING, "Failed to load thumbnail for file: " + fileID, ex.getCause()); //NON-NLS
|
||||||
return null;
|
return null;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-2017 Basis Technology Corp.
|
* Copyright 2013-2018 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -36,7 +36,6 @@ import javax.swing.SwingWorker;
|
|||||||
import org.controlsfx.control.action.Action;
|
import org.controlsfx.control.action.Action;
|
||||||
import org.controlsfx.control.action.ActionUtils;
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.openide.util.NbBundle.Messages;
|
|
||||||
import org.openide.windows.TopComponent;
|
import org.openide.windows.TopComponent;
|
||||||
import org.openide.windows.WindowManager;
|
import org.openide.windows.WindowManager;
|
||||||
import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog;
|
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.autopsy.imagegallery.datamodel.DrawableTagsManager;
|
||||||
import org.sleuthkit.datamodel.ContentTag;
|
import org.sleuthkit.datamodel.ContentTag;
|
||||||
import org.sleuthkit.datamodel.TagName;
|
import org.sleuthkit.datamodel.TagName;
|
||||||
import org.sleuthkit.datamodel.TskData;
|
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
import org.sleuthkit.datamodel.TskData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instances of this Action allow users to apply tags to content.
|
* Instances of this Action allow users to apply tags to content.
|
||||||
@ -75,14 +74,14 @@ public class AddTagAction extends Action {
|
|||||||
setEventHandler(actionEvent -> addTagWithComment(""));
|
setEventHandler(actionEvent -> addTagWithComment(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Menu getTagMenu(ImageGalleryController controller) {
|
static public Menu getTagMenu(ImageGalleryController controller) throws TskCoreException {
|
||||||
return new TagMenu(controller);
|
return new TagMenu(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTagWithComment(String comment) {
|
private void addTagWithComment(String comment) {
|
||||||
addTagsToFiles(tagName, comment, selectedFileIDs);
|
addTagsToFiles(tagName, comment, selectedFileIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NbBundle.Messages({"# {0} - fileID",
|
@NbBundle.Messages({"# {0} - fileID",
|
||||||
"AddDrawableTagAction.addTagsToFiles.alert=Unable to tag file {0}."})
|
"AddDrawableTagAction.addTagsToFiles.alert=Unable to tag file {0}."})
|
||||||
private void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles) {
|
private void addTagsToFiles(TagName tagName, String comment, Set<Long> selectedFiles) {
|
||||||
@ -94,7 +93,7 @@ public class AddTagAction extends Action {
|
|||||||
DrawableTagsManager tagsManager = controller.getTagsManager();
|
DrawableTagsManager tagsManager = controller.getTagsManager();
|
||||||
for (Long fileID : selectedFiles) {
|
for (Long fileID : selectedFiles) {
|
||||||
try {
|
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
|
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);
|
List<ContentTag> contentTags = tagsManager.getContentTags(file);
|
||||||
@ -141,7 +140,7 @@ public class AddTagAction extends Action {
|
|||||||
"AddDrawableTagAction.displayName.singular=Tag File"})
|
"AddDrawableTagAction.displayName.singular=Tag File"})
|
||||||
private static class TagMenu extends Menu {
|
private static class TagMenu extends Menu {
|
||||||
|
|
||||||
TagMenu(ImageGalleryController controller) {
|
TagMenu(ImageGalleryController controller) throws TskCoreException {
|
||||||
setGraphic(new ImageView(DrawableAttribute.TAGS.getIcon()));
|
setGraphic(new ImageView(DrawableAttribute.TAGS.getIcon()));
|
||||||
ObservableSet<Long> selectedFileIDs = controller.getSelectionModel().getSelected();
|
ObservableSet<Long> selectedFileIDs = controller.getSelectionModel().getSelected();
|
||||||
setText(selectedFileIDs.size() > 1
|
setText(selectedFileIDs.size() > 1
|
||||||
@ -163,11 +162,10 @@ public class AddTagAction extends Action {
|
|||||||
empty.setDisable(true);
|
empty.setDisable(true);
|
||||||
quickTagMenu.getItems().add(empty);
|
quickTagMenu.getItems().add(empty);
|
||||||
} else {
|
} else {
|
||||||
for (final TagName tagName : tagNames) {
|
tagNames.stream()
|
||||||
AddTagAction addDrawableTagAction = new AddTagAction(controller, tagName, selectedFileIDs);
|
.map(tagName -> new AddTagAction(controller, tagName, selectedFileIDs))
|
||||||
MenuItem tagNameItem = ActionUtils.createMenuItem(addDrawableTagAction);
|
.map(ActionUtils::createMenuItem)
|
||||||
quickTagMenu.getItems().add(tagNameItem);
|
.forEachOrdered(quickTagMenu.getItems()::add);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -138,7 +138,7 @@ public class CategorizeAction extends Action {
|
|||||||
TagName catZeroTagName = categoryManager.getTagName(DhsImageCategory.ZERO);
|
TagName catZeroTagName = categoryManager.getTagName(DhsImageCategory.ZERO);
|
||||||
for (long fileID : fileIDs) {
|
for (long fileID : fileIDs) {
|
||||||
try {
|
try {
|
||||||
DrawableFile file = controller.getFileFromId(fileID); //drawable db access
|
DrawableFile file = controller.getFileFromID(fileID); //drawable db access
|
||||||
if (createUndo) {
|
if (createUndo) {
|
||||||
DhsImageCategory oldCat = file.getCategory(); //drawable db access
|
DhsImageCategory oldCat = file.getCategory(); //drawable db access
|
||||||
TagName oldCatTagName = categoryManager.getTagName(oldCat);
|
TagName oldCatTagName = categoryManager.getTagName(oldCat);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2015-16 Basis Technology Corp.
|
* Copyright 2015-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -34,11 +34,12 @@ import javafx.scene.control.Label;
|
|||||||
import javafx.scene.control.Separator;
|
import javafx.scene.control.Separator;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
import static org.apache.commons.lang.ObjectUtils.notEqual;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryPreferences;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryPreferences;
|
||||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,32 +53,35 @@ public class CategorizeGroupAction extends CategorizeAction {
|
|||||||
public CategorizeGroupAction(DhsImageCategory newCat, ImageGalleryController controller) {
|
public CategorizeGroupAction(DhsImageCategory newCat, ImageGalleryController controller) {
|
||||||
super(controller, newCat, null);
|
super(controller, newCat, null);
|
||||||
setEventHandler(actionEvent -> {
|
setEventHandler(actionEvent -> {
|
||||||
ObservableList<Long> fileIDs = controller.viewState().get().getGroup().getFileIDs();
|
controller.getViewState().getGroup().ifPresent(group -> {
|
||||||
|
ObservableList<Long> fileIDs = group.getFileIDs();
|
||||||
|
|
||||||
if (ImageGalleryPreferences.isGroupCategorizationWarningDisabled()) {
|
if (ImageGalleryPreferences.isGroupCategorizationWarningDisabled()) {
|
||||||
//if they have preveiously disabled the warning, just go ahead and apply categories.
|
//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.
|
|
||||||
addCatToFiles(ImmutableSet.copyOf(fileIDs));
|
addCatToFiles(ImmutableSet.copyOf(fileIDs));
|
||||||
} else {
|
} 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: "})
|
"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) {
|
private void showConfirmationDialog(final Map<DhsImageCategory, Long> catCountMap, DhsImageCategory newCat, ObservableList<Long> fileIDs) {
|
||||||
|
|
||||||
ButtonType categorizeButtonType =
|
ButtonType categorizeButtonType
|
||||||
new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY);
|
= new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY);
|
||||||
|
|
||||||
VBox textFlow = new VBox();
|
VBox textFlow = new VBox();
|
||||||
|
|
||||||
for (Map.Entry<DhsImageCategory, Long> entry : catCountMap.entrySet()) {
|
for (Map.Entry<DhsImageCategory, Long> entry : catCountMap.entrySet()) {
|
||||||
if (entry.getKey().equals(newCat) == false) {
|
if (entry.getValue() > 0
|
||||||
if (entry.getValue() > 0) {
|
&& notEqual(entry.getKey(), newCat)) {
|
||||||
Label label = new Label(Bundle.CategorizeGroupAction_fileCountMessage(entry.getValue(), entry.getKey().getDisplayName()),
|
Label label = new Label(Bundle.CategorizeGroupAction_fileCountMessage(entry.getValue(), entry.getKey().getDisplayName()),
|
||||||
entry.getKey().getGraphic());
|
entry.getKey().getGraphic());
|
||||||
label.setContentDisplay(ContentDisplay.RIGHT);
|
label.setContentDisplay(ContentDisplay.RIGHT);
|
||||||
textFlow.getChildren().add(label);
|
textFlow.getChildren().add(label);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2011-17 Basis Technology Corp.
|
* Copyright 2011-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,15 +18,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.actions;
|
package org.sleuthkit.autopsy.imagegallery.actions;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.ObjectExpression;
|
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.controlsfx.control.action.Action;
|
import org.controlsfx.control.action.Action;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
|
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
|
* Marks the currently displayed group as "seen" and advances to the next unseen
|
||||||
* group
|
* group
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({"NextUnseenGroup.markGroupSeen=Mark Group Seen",
|
@NbBundle.Messages({
|
||||||
"NextUnseenGroup.nextUnseenGroup=Next Unseen group"})
|
"NextUnseenGroup.markGroupSeen=Mark Group Seen",
|
||||||
|
"NextUnseenGroup.nextUnseenGroup=Next Unseen group",
|
||||||
|
"NextUnseenGroup.allGroupsSeen=All Groups Have Been Seen"})
|
||||||
public class NextUnseenGroup extends Action {
|
public class NextUnseenGroup extends Action {
|
||||||
|
|
||||||
private static final Image END =
|
private static final String IMAGE_PATH = "/org/sleuthkit/autopsy/imagegallery/images/"; //NON-NLS
|
||||||
new Image(NextUnseenGroup.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/control-stop.png")); //NON-NLS
|
private static final Image END_IMAGE = new Image(NextUnseenGroup.class.getResourceAsStream(
|
||||||
private static final Image ADVANCE =
|
IMAGE_PATH + "control-stop.png")); //NON-NLS
|
||||||
new Image(NextUnseenGroup.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/control-double.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 MARK_GROUP_SEEN = Bundle.NextUnseenGroup_markGroupSeen();
|
||||||
private static final String NEXT_UNSEEN_GROUP = Bundle.NextUnseenGroup_nextUnseenGroup();
|
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> unSeenGroups;
|
||||||
private final ObservableList<DrawableGroup> analyzedGroups;
|
private final GroupManager groupManager;
|
||||||
|
|
||||||
public NextUnseenGroup(ImageGalleryController controller) {
|
public NextUnseenGroup(ImageGalleryController controller) {
|
||||||
super(NEXT_UNSEEN_GROUP);
|
super(NEXT_UNSEEN_GROUP);
|
||||||
|
setGraphic(new ImageView(ADVANCE_IMAGE));
|
||||||
|
|
||||||
|
this.controller = controller;
|
||||||
groupManager = controller.getGroupManager();
|
groupManager = controller.getGroupManager();
|
||||||
unSeenGroups = groupManager.getUnSeenGroups();
|
unSeenGroups = groupManager.getUnSeenGroups();
|
||||||
analyzedGroups = groupManager.getAnalyzedGroups();
|
unSeenGroups.addListener((Observable observable) -> updateButton());
|
||||||
setGraphic(new ImageView(ADVANCE));
|
controller.viewStateProperty().addListener((Observable observable) -> updateButton());
|
||||||
|
|
||||||
//TODO: do we need both these listeners?
|
setEventHandler(event -> { //on fx-thread
|
||||||
analyzedGroups.addListener((Observable o) -> this.updateButton());
|
|
||||||
unSeenGroups.addListener((Observable o) -> this.updateButton());
|
|
||||||
|
|
||||||
setEventHandler(event -> {
|
|
||||||
//fx-thread
|
|
||||||
//if there is a group assigned to the view, mark it as seen
|
//if there is a group assigned to the view, mark it as seen
|
||||||
Optional.ofNullable(controller.viewState())
|
Optional.ofNullable(controller.getViewState())
|
||||||
.map(ObjectExpression<GroupViewState>::getValue)
|
.flatMap(GroupViewState::getGroup)
|
||||||
.map(GroupViewState::getGroup)
|
.ifPresent(group -> {
|
||||||
.ifPresent(group -> groupManager.markGroupSeen(group, true));
|
groupManager.markGroupSeen(group, true)
|
||||||
|
.addListener(this::advanceToNextUnseenGroup, MoreExecutors.newDirectExecutorService());
|
||||||
if (unSeenGroups.isEmpty() == false) {
|
});
|
||||||
controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true);
|
|
||||||
updateButton();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
updateButton();
|
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() {
|
private void updateButton() {
|
||||||
setDisabled(unSeenGroups.isEmpty());
|
int size = unSeenGroups.size();
|
||||||
if (unSeenGroups.size() <= 1) {
|
|
||||||
setText(MARK_GROUP_SEEN);
|
if (size < 1) {
|
||||||
setGraphic(new ImageView(END));
|
//there are no unseen groups.
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
setDisabled(true);
|
||||||
|
setText(ALL_GROUPS_SEEN);
|
||||||
|
setGraphic(null);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setText(NEXT_UNSEEN_GROUP);
|
DrawableGroup get = unSeenGroups.get(0);
|
||||||
setGraphic(new ImageView(ADVANCE));
|
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.awt.Component;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.scene.control.Dialog;
|
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
import javafx.stage.Stage;
|
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JMenuItem;
|
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.Installer;
|
||||||
import org.sleuthkit.autopsy.core.RuntimeProperties;
|
import org.sleuthkit.autopsy.core.RuntimeProperties;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.imagegallery.OpenAction")
|
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.imagegallery.OpenAction")
|
||||||
@ActionReferences(value = {
|
@ActionReferences(value = {
|
||||||
@ActionReference(path = "Menu/Tools", position = 101),
|
@ActionReference(path = "Menu/Tools", position = 101)
|
||||||
|
,
|
||||||
@ActionReference(path = "Toolbars/Case", position = 101)})
|
@ActionReference(path = "Toolbars/Case", position = 101)})
|
||||||
@ActionRegistration(displayName = "#CTL_OpenAction", lazy = false)
|
@ActionRegistration(displayName = "#CTL_OpenAction", lazy = false)
|
||||||
@Messages({"CTL_OpenAction=Images/Videos",
|
@Messages({"CTL_OpenAction=Images/Videos",
|
||||||
"OpenAction.stale.confDlg.msg=The image / video database may be out of date. " +
|
"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" +
|
+ "Do you want to update and listen for further ingest results?\n"
|
||||||
"Choosing 'yes' will update the database and enable listening to future ingests.",
|
+ "Choosing 'yes' will update the database and enable listening to future ingests.",
|
||||||
"OpenAction.stale.confDlg.title=Image Gallery"})
|
"OpenAction.stale.confDlg.title=Image Gallery"})
|
||||||
public final class OpenAction extends CallableSystemAction {
|
public final class OpenAction extends CallableSystemAction {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(OpenAction.class.getName());
|
private static final Logger logger = Logger.getLogger(OpenAction.class.getName());
|
||||||
private static final String VIEW_IMAGES_VIDEOS = Bundle.CTL_OpenAction();
|
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 static final long FILE_LIMIT = 6_000_000;
|
||||||
|
|
||||||
private final PropertyChangeListener pcl;
|
private final PropertyChangeListener pcl;
|
||||||
@ -145,10 +125,7 @@ public final class OpenAction extends CallableSystemAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("fallthrough")
|
@NbBundle.Messages({"OpenAction.dialogTitle=Image Gallery"})
|
||||||
@NbBundle.Messages({
|
|
||||||
"OpenAction.dialogTitle=Image Gallery"
|
|
||||||
})
|
|
||||||
public void performAction() {
|
public void performAction() {
|
||||||
|
|
||||||
//check case
|
//check case
|
||||||
@ -165,24 +142,49 @@ public final class OpenAction extends CallableSystemAction {
|
|||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ImageGalleryModule.isDrawableDBStale(currentCase)) {
|
try {
|
||||||
//drawable db is stale, ask what to do
|
ImageGalleryController controller = ImageGalleryModule.getController();
|
||||||
int answer = JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), Bundle.OpenAction_stale_confDlg_msg(),
|
if (controller.isDataSourcesTableStale()) {
|
||||||
Bundle.OpenAction_stale_confDlg_title(), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
|
//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) {
|
switch (answer) {
|
||||||
case JOptionPane.YES_OPTION:
|
case JOptionPane.YES_OPTION:
|
||||||
ImageGalleryController.getDefault().setListeningEnabled(true);
|
/* For a single-user case, we favor user experience, and
|
||||||
//fall through
|
* rebuild the database as soon as Image Gallery is
|
||||||
case JOptionPane.NO_OPTION:
|
* enabled for the case. For a multi-user case, we favor
|
||||||
ImageGalleryTopComponent.openTopComponent();
|
* 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;
|
break;
|
||||||
case JOptionPane.CANCEL_OPTION:
|
case JOptionPane.CANCEL_OPTION:
|
||||||
break; //do nothing
|
break; //do nothing
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//drawable db is not stale, just open it
|
||||||
|
ImageGalleryTopComponent.openTopComponent();
|
||||||
}
|
}
|
||||||
} else {
|
} catch (NoCurrentCaseException noCurrentCaseException) {
|
||||||
//drawable db is not stale, just open it
|
logger.log(Level.WARNING, "There was no case open when Image Gallery was opened.", noCurrentCaseException);
|
||||||
ImageGalleryTopComponent.openTopComponent();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,16 +200,6 @@ public final class OpenAction extends CallableSystemAction {
|
|||||||
return false;
|
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({
|
@NbBundle.Messages({
|
||||||
"ImageGallery.showTooManyFiles.contentText="
|
"ImageGallery.showTooManyFiles.contentText="
|
||||||
+ "There are too many files in the DB to ensure reasonable performance."
|
+ "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);
|
Bundle.ImageGallery_showTooManyFiles_contentText(), ButtonType.OK);
|
||||||
dialog.initModality(Modality.APPLICATION_MODAL);
|
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||||
dialog.setTitle(Bundle.OpenAction_dialogTitle());
|
dialog.setTitle(Bundle.OpenAction_dialogTitle());
|
||||||
setDialogIcons(dialog);
|
GuiUtils.setDialogIcons(dialog);
|
||||||
dialog.setHeaderText(Bundle.ImageGallery_showTooManyFiles_headerText());
|
dialog.setHeaderText(Bundle.ImageGallery_showTooManyFiles_headerText());
|
||||||
dialog.showAndWait();
|
dialog.showAndWait();
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,10 @@ public class TagGroupAction extends AddTagAction {
|
|||||||
|
|
||||||
public TagGroupAction(final TagName tagName, ImageGalleryController controller) {
|
public TagGroupAction(final TagName tagName, ImageGalleryController controller) {
|
||||||
super(controller, tagName, null);
|
super(controller, tagName, null);
|
||||||
setEventHandler(actionEvent ->
|
setEventHandler(actionEvent -> {
|
||||||
new AddTagAction(controller, tagName, ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs())).
|
controller.getViewState().getGroup().ifPresent(group -> {
|
||||||
handle(actionEvent)
|
new AddTagAction(controller, tagName, ImmutableSet.copyOf(group.getFileIDs())).handle(actionEvent);
|
||||||
);
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2015-16 Basis Technology Corp.
|
* Copyright 2015-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -34,20 +34,19 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
|||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
|
||||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.datamodel.ContentTag;
|
import org.sleuthkit.datamodel.ContentTag;
|
||||||
import org.sleuthkit.datamodel.TagName;
|
import org.sleuthkit.datamodel.TagName;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a cached view of the number of files per category, and fires
|
* 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
|
* To receive CategoryChangeEvents, a listener must register itself, and
|
||||||
* implement a public method annotated with {@link Subscribe} that accepts one
|
* implement a public method annotated with Subscribe that accepts one argument
|
||||||
* argument of type CategoryChangeEvent
|
* of type CategoryChangeEvent
|
||||||
*
|
*
|
||||||
* TODO: currently these two functions (cached counts and events) are separate
|
* TODO: currently these two functions (cached counts and events) are separate
|
||||||
* although they are related. Can they be integrated more?
|
* 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
|
* initialized from this, and the counting of CAT-0 is always delegated to
|
||||||
* this db.
|
* 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(
|
private final EventBus categoryEventBus = new AsyncEventBus(Executors.newSingleThreadExecutor(
|
||||||
new BasicThreadFactory.Builder().namingPattern("Category Event Bus").uncaughtExceptionHandler((Thread t, Throwable 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", e); //NON-NLS
|
LOGGER.log(Level.SEVERE, "Uncaught exception in category event bus handler", throwable); //NON-NLS
|
||||||
}).build()
|
}).build()
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -80,38 +79,29 @@ public class CategoryManager {
|
|||||||
* the count related methods go through this cache, which loads initial
|
* the count related methods go through this cache, which loads initial
|
||||||
* values from the database if needed.
|
* values from the database if needed.
|
||||||
*/
|
*/
|
||||||
private final LoadingCache<DhsImageCategory, LongAdder> categoryCounts =
|
private final LoadingCache<DhsImageCategory, LongAdder> categoryCounts
|
||||||
CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper));
|
= CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper));
|
||||||
/**
|
/**
|
||||||
* cached TagNames corresponding to Categories, looked up from
|
* cached TagNames corresponding to Categories, looked up from
|
||||||
* autopsyTagManager at initial request or if invalidated by case change.
|
* autopsyTagManager at initial request or if invalidated by case change.
|
||||||
*/
|
*/
|
||||||
private final LoadingCache<DhsImageCategory, TagName> catTagNameMap =
|
private final LoadingCache<DhsImageCategory, TagName> catTagNameMap
|
||||||
CacheBuilder.newBuilder().build(CacheLoader.from(
|
= CacheBuilder.newBuilder().build(new CacheLoader<DhsImageCategory, TagName>() {
|
||||||
cat -> getController().getTagsManager().getTagName(cat)
|
@Override
|
||||||
));
|
public TagName load(DhsImageCategory cat) throws TskCoreException {
|
||||||
|
return getController().getTagsManager().getTagName(cat);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
public CategoryManager(ImageGalleryController controller) {
|
public CategoryManager(ImageGalleryController controller) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
|
this.drawableDb = controller.getDatabase();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageGalleryController getController() {
|
private ImageGalleryController getController() {
|
||||||
return controller;
|
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() {
|
synchronized public void invalidateCaches() {
|
||||||
categoryCounts.invalidateAll();
|
categoryCounts.invalidateAll();
|
||||||
catTagNameMap.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
|
// 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.
|
// 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);
|
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 {
|
} else {
|
||||||
return categoryCounts.getUnchecked(cat).sum();
|
return categoryCounts.getUnchecked(cat).sum();
|
||||||
}
|
}
|
||||||
@ -151,7 +141,7 @@ public class CategoryManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* decrement the cached value for the number of files with the given
|
* decrement the cached value for the number of files with the given
|
||||||
* {@link DhsImageCategory}
|
* DhsImageCategory
|
||||||
*
|
*
|
||||||
* @param cat the Category to decrement
|
* @param cat the Category to decrement
|
||||||
*/
|
*/
|
||||||
@ -175,7 +165,7 @@ public class CategoryManager {
|
|||||||
LongAdder longAdder = new LongAdder();
|
LongAdder longAdder = new LongAdder();
|
||||||
longAdder.decrement();
|
longAdder.decrement();
|
||||||
try {
|
try {
|
||||||
longAdder.add(db.getCategoryCount(cat));
|
longAdder.add(drawableDb.getCategoryCount(cat));
|
||||||
longAdder.increment();
|
longAdder.increment();
|
||||||
} catch (IllegalStateException ex) {
|
} catch (IllegalStateException ex) {
|
||||||
LOGGER.log(Level.WARNING, "Case closed while getting files"); //NON-NLS
|
LOGGER.log(Level.WARNING, "Case closed while getting files"); //NON-NLS
|
||||||
@ -212,15 +202,14 @@ public class CategoryManager {
|
|||||||
try {
|
try {
|
||||||
categoryEventBus.unregister(listener);
|
categoryEventBus.unregister(listener);
|
||||||
} catch (IllegalArgumentException e) {
|
} 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
|
||||||
* We don't fully understand why we are getting this exception
|
* the groups should all be registered. To avoid cluttering the logs
|
||||||
* when the groups should all be registered. To avoid cluttering
|
* we have disabled recording this exception. This documented in
|
||||||
* the logs we have disabled recording this exception. This
|
* issues 738 and 802.
|
||||||
* 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
|
if (!e.getMessage().contains("missing event subscriber for an annotated method. Is " + listener + " registered?")) { //NON-NLS
|
||||||
} else {
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -258,7 +247,7 @@ public class CategoryManager {
|
|||||||
//remove old category tag(s) if necessary
|
//remove old category tag(s) if necessary
|
||||||
for (ContentTag ct : tagsManager.getContentTags(addedTag.getContent())) {
|
for (ContentTag ct : tagsManager.getContentTags(addedTag.getContent())) {
|
||||||
if (ct.getId() != addedTag.getId()
|
if (ct.getId() != addedTag.getId()
|
||||||
&& CategoryManager.isCategoryTagName(ct.getName())) {
|
&& CategoryManager.isCategoryTagName(ct.getName())) {
|
||||||
try {
|
try {
|
||||||
tagsManager.deleteContentTag(ct);
|
tagsManager.deleteContentTag(ct);
|
||||||
} catch (TskCoreException tskException) {
|
} catch (TskCoreException tskException) {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.datamodel;
|
package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||||
|
|
||||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
|
||||||
import java.lang.ref.SoftReference;
|
import java.lang.ref.SoftReference;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -32,6 +31,7 @@ import java.util.stream.Collectors;
|
|||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
|
import javafx.concurrent.Worker;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.util.Pair;
|
import javafx.util.Pair;
|
||||||
import javax.annotation.Nonnull;
|
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.Case;
|
||||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||||
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
|
import org.sleuthkit.autopsy.imagegallery.FileTypeUtils;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
|
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
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.
|
* 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
|
return isVideo
|
||||||
? new VideoFile(abstractFileById, analyzed)
|
? new VideoFile(file, analyzed)
|
||||||
: new ImageFile(abstractFileById, analyzed);
|
: new ImageFile(file, analyzed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DrawableFile create(Long id, boolean analyzed) throws TskCoreException, NoCurrentCaseException {
|
public static DrawableFile create(Long fileID, boolean analyzed) throws TskCoreException, NoCurrentCaseException {
|
||||||
return create(Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(id), analyzed);
|
return create(Case.getCurrentCaseThrows().getSleuthkitCase().getAbstractFileById(fileID), analyzed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SoftReference<Image> imageRef;
|
private SoftReference<Image> imageRef;
|
||||||
@ -149,8 +155,8 @@ public abstract class DrawableFile {
|
|||||||
return file.getSleuthkitCase();
|
return file.getSleuthkitCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pair<DrawableAttribute<?>, Collection<?>> makeAttributeValuePair(DrawableAttribute<?> t) {
|
private Pair<DrawableAttribute<?>, Collection<?>> makeAttributeValuePair(DrawableAttribute<?> attribute) {
|
||||||
return new Pair<>(t, t.getValue(DrawableFile.this));
|
return new Pair<>(attribute, attribute.getValue(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getModel() {
|
public String getModel() {
|
||||||
@ -254,42 +260,17 @@ public abstract class DrawableFile {
|
|||||||
return getSleuthkitCase().getContentTagsByContent(file);
|
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() {
|
public Task<Image> getReadFullSizeImageTask() {
|
||||||
Image image = (imageRef != null) ? imageRef.get() : null;
|
Image image = (imageRef != null) ? imageRef.get() : null;
|
||||||
if (image == null || image.isError()) {
|
if (image == null || image.isError()) {
|
||||||
Task<Image> readImageTask = getReadFullSizeImageTaskHelper();
|
Task<Image> readImageTask = getReadFullSizeImageTaskHelper();
|
||||||
readImageTask.stateProperty().addListener(stateProperty -> {
|
readImageTask.stateProperty().addListener(stateProperty -> {
|
||||||
switch (readImageTask.getState()) {
|
if (readImageTask.getState() == Worker.State.SUCCEEDED) {
|
||||||
case SUCCEEDED:
|
try {
|
||||||
try {
|
imageRef = new SoftReference<>(readImageTask.get());
|
||||||
imageRef = new SoftReference<>(readImageTask.get());
|
} catch (InterruptedException | ExecutionException exception) {
|
||||||
} catch (InterruptedException | ExecutionException exception) {
|
LOGGER.log(Level.WARNING, getMessageTemplate(exception), getContentPathSafe());
|
||||||
LOGGER.log(Level.WARNING, getMessageTemplate(exception), getContentPathSafe());
|
}
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return readImageTask;
|
return readImageTask;
|
||||||
@ -316,14 +297,14 @@ public abstract class DrawableFile {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the width of the visual content.
|
* Get the width of the visual content.
|
||||||
*
|
*
|
||||||
* @return The width.
|
* @return The width.
|
||||||
*/
|
*/
|
||||||
abstract Double getWidth();
|
abstract Double getWidth();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the height of the visual content.
|
* Get the height of the visual content.
|
||||||
*
|
*
|
||||||
* @return The height.
|
* @return The height.
|
||||||
*/
|
*/
|
||||||
abstract Double getHeight();
|
abstract Double getHeight();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-16 Basis Technology Corp.
|
* Copyright 2013-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,25 +18,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.datamodel;
|
package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||||
|
|
||||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
|
||||||
import com.google.common.eventbus.AsyncEventBus;
|
import com.google.common.eventbus.AsyncEventBus;
|
||||||
import com.google.common.eventbus.EventBus;
|
import com.google.common.eventbus.EventBus;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
import org.sleuthkit.autopsy.casemodule.services.TagsManager;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
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.Content;
|
||||||
import org.sleuthkit.datamodel.ContentTag;
|
import org.sleuthkit.datamodel.ContentTag;
|
||||||
import org.sleuthkit.datamodel.TagName;
|
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
|
* 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",
|
@NbBundle.Messages({"DrawableTagsManager.followUp=Follow Up",
|
||||||
"DrawableTagsManager.bookMark=Bookmark"})
|
"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 final Image FOLLOW_UP_IMAGE = new Image("/org/sleuthkit/autopsy/imagegallery/images/flag_red.png");
|
||||||
private static Image BOOKMARK_IMAGE;
|
private static final Image BOOKMARK_IMAGE = new Image("/org/sleuthkit/autopsy/images/star-bookmark-icon-16.png");
|
||||||
|
|
||||||
final private Object autopsyTagsManagerLock = new Object();
|
private final TagsManager autopsyTagsManager;
|
||||||
private 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(
|
private final EventBus tagsEventBus
|
||||||
Executors.newSingleThreadExecutor(
|
= new AsyncEventBus(
|
||||||
new BasicThreadFactory.Builder().namingPattern("Tags Event Bus").uncaughtExceptionHandler((Thread t, Throwable e) -> { //NON-NLS
|
Executors.newSingleThreadExecutor(
|
||||||
LOGGER.log(Level.SEVERE, "uncaught exception in event bus handler", e); //NON-NLS
|
new BasicThreadFactory.Builder()
|
||||||
}).build()
|
.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()));
|
||||||
* The tag name corresponding to the "built-in" tag "Follow Up"
|
|
||||||
*/
|
|
||||||
private TagName followUpTagName;
|
|
||||||
private TagName bookmarkTagName;
|
|
||||||
|
|
||||||
public DrawableTagsManager(TagsManager autopsyTagsManager) {
|
|
||||||
this.autopsyTagsManager = autopsyTagsManager;
|
|
||||||
|
|
||||||
|
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
|
* Get the follow up TagName.
|
||||||
* changes
|
|
||||||
*
|
*
|
||||||
* @param autopsyTagsManager
|
* @return The follow up TagName.
|
||||||
*/
|
*/
|
||||||
public void setAutopsyTagsManager(TagsManager autopsyTagsManager) {
|
public TagName getFollowUpTagName() {
|
||||||
synchronized (autopsyTagsManagerLock) {
|
return followUpTagName;
|
||||||
this.autopsyTagsManager = autopsyTagsManager;
|
|
||||||
clearFollowUpTagName();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use when closing a case to make sure everything is re-initialized in the
|
* Get the bookmark TagName.
|
||||||
* next case.
|
*
|
||||||
|
* @return The bookmark TagName.
|
||||||
*/
|
*/
|
||||||
public void clearFollowUpTagName() {
|
private TagName getBookmarkTagName() throws TskCoreException {
|
||||||
synchronized (autopsyTagsManagerLock) {
|
return bookmarkTagName;
|
||||||
followUpTagName = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 {
|
public List<TagName> getNonCategoryTagNames() throws TskCoreException {
|
||||||
synchronized (autopsyTagsManagerLock) {
|
return autopsyTagsManager.getAllTagNames().stream()
|
||||||
if (Objects.isNull(followUpTagName)) {
|
.filter(CategoryManager::isNotCategoryTagName)
|
||||||
followUpTagName = getTagName(NbBundle.getMessage(DrawableTagsManager.class, "DrawableTagsManager.followUp"));
|
.distinct().sorted()
|
||||||
}
|
.collect(Collectors.toList());
|
||||||
return followUpTagName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* @return All the TagNames that are categories, in alphabetical order by
|
||||||
* by displayName, or, an empty set if there was an exception
|
* displayName.
|
||||||
* looking them up from the db.
|
*
|
||||||
|
* @throws org.sleuthkit.datamodel.TskCoreException
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
public List<TagName> getCategoryTagNames() throws TskCoreException {
|
||||||
public List<TagName> getNonCategoryTagNames() {
|
return autopsyTagsManager.getAllTagNames().stream()
|
||||||
synchronized (autopsyTagsManagerLock) {
|
.filter(CategoryManager::isCategoryTagName)
|
||||||
try {
|
.distinct().sorted()
|
||||||
return autopsyTagsManager.getAllTagNames().stream()
|
.collect(Collectors.toList());
|
||||||
.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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,9 +161,7 @@ public class DrawableTagsManager {
|
|||||||
* @throws TskCoreException if there was an error reading from the db
|
* @throws TskCoreException if there was an error reading from the db
|
||||||
*/
|
*/
|
||||||
public List<ContentTag> getContentTags(Content content) throws TskCoreException {
|
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 {
|
public TagName getTagName(String displayName) throws TskCoreException {
|
||||||
synchronized (autopsyTagsManagerLock) {
|
|
||||||
try {
|
TagName returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
|
||||||
TagName returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
|
if (returnTagName != null) {
|
||||||
if (returnTagName != null) {
|
return returnTagName;
|
||||||
return returnTagName;
|
}
|
||||||
}
|
try {
|
||||||
try {
|
return autopsyTagsManager.addTagName(displayName);
|
||||||
return autopsyTagsManager.addTagName(displayName);
|
} catch (TagsManager.TagNameAlreadyExistsException ex) {
|
||||||
} catch (TagsManager.TagNameAlreadyExistsException ex) {
|
returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
|
||||||
returnTagName = autopsyTagsManager.getDisplayNamesToTagNamesMap().get(displayName);
|
if (returnTagName != null) {
|
||||||
if (returnTagName != null) {
|
return returnTagName;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
throw new TskCoreException("Tag name exists but an error occured in retrieving it", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TagName getTagName(DhsImageCategory cat) {
|
public TagName getTagName(DhsImageCategory cat) throws TskCoreException {
|
||||||
try {
|
return getTagName(cat.getDisplayName());
|
||||||
return getTagName(cat.getDisplayName());
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContentTag addContentTag(DrawableFile file, TagName tagName, String comment) throws TskCoreException {
|
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 {
|
public List<ContentTag> getContentTagsByTagName(TagName tagName) throws TskCoreException {
|
||||||
synchronized (autopsyTagsManagerLock) {
|
return autopsyTagsManager.getContentTagsByTagName(tagName);
|
||||||
return autopsyTagsManager.getContentTagsByTagName(t);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TagName> getAllTagNames() throws TskCoreException {
|
public List<TagName> getAllTagNames() throws TskCoreException {
|
||||||
synchronized (autopsyTagsManagerLock) {
|
return autopsyTagsManager.getAllTagNames();
|
||||||
return autopsyTagsManager.getAllTagNames();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<TagName> getTagNamesInUse() throws TskCoreException {
|
public List<TagName> getTagNamesInUse() throws TskCoreException {
|
||||||
synchronized (autopsyTagsManagerLock) {
|
return autopsyTagsManager.getTagNamesInUse();
|
||||||
return autopsyTagsManager.getTagNamesInUse();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteContentTag(ContentTag ct) throws TskCoreException {
|
public void deleteContentTag(ContentTag contentTag) throws TskCoreException {
|
||||||
synchronized (autopsyTagsManagerLock) {
|
autopsyTagsManager.deleteContentTag(contentTag);
|
||||||
autopsyTagsManager.deleteContentTag(ct);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Node getGraphic(TagName tagname) {
|
public Node getGraphic(TagName tagname) {
|
||||||
try {
|
try {
|
||||||
if (tagname.equals(getFollowUpTagName())) {
|
if (tagname.equals(getFollowUpTagName())) {
|
||||||
return new ImageView(getFollowUpImage());
|
return new ImageView(FOLLOW_UP_IMAGE);
|
||||||
} else if (tagname.equals(getBookmarkTagName())) {
|
} else if (tagname.equals(getBookmarkTagName())) {
|
||||||
return new ImageView(getBookmarkImage());
|
return new ImageView(BOOKMARK_IMAGE);
|
||||||
}
|
}
|
||||||
} catch (TskCoreException ex) {
|
} 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);
|
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.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@ -15,26 +33,18 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
*/
|
*/
|
||||||
public class HashSetManager {
|
public class HashSetManager {
|
||||||
|
|
||||||
/**
|
/** The db that initial values are loaded from. */
|
||||||
* The db that initial values are loaded from.
|
private final DrawableDB drawableDB;
|
||||||
*/
|
|
||||||
private DrawableDB db = null;
|
public HashSetManager(DrawableDB drawableDB) {
|
||||||
|
this.drawableDB = drawableDB;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the internal cache from fileID to a set of hashset names.
|
* 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));
|
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
|
* 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) {
|
private Set<String> getHashSetsForFileHelper(long fileID) {
|
||||||
try {
|
try {
|
||||||
return db.getHashSetsForFile(fileID);
|
if (drawableDB.isClosed()) {
|
||||||
} catch (TskCoreException ex) {
|
Logger.getLogger(HashSetManager.class.getName()).log(Level.WARNING, "Failed to get Hash Sets for file. The Db connection was already closed."); //NON-NLS
|
||||||
Logger.getLogger(HashSetManager.class.getName()).log(Level.SEVERE, "Failed to get Hash Sets for file", ex); //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();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-16 Basis Technology Corp.
|
* Copyright 2013-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -26,16 +26,19 @@ import java.util.logging.Level;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.DoubleBinding;
|
import javafx.beans.binding.DoubleBinding;
|
||||||
import javafx.beans.binding.IntegerBinding;
|
import javafx.beans.binding.IntegerBinding;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||||
import javafx.beans.property.ReadOnlyLongProperty;
|
import javafx.beans.property.ReadOnlyLongProperty;
|
||||||
import javafx.beans.property.ReadOnlyLongWrapper;
|
import javafx.beans.property.ReadOnlyLongWrapper;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
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.CategoryManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
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
|
* 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")
|
@SuppressWarnings("ReturnOfCollectionOrArrayField")
|
||||||
public synchronized ObservableList<Long> getFileIDs() {
|
public ObservableList<Long> getFileIDs() {
|
||||||
return unmodifiableFileIDS;
|
return unmodifiableFileIDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,11 +124,11 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
|
|||||||
if (hashSetHitsCount.get() < 0) {
|
if (hashSetHitsCount.get() < 0) {
|
||||||
try {
|
try {
|
||||||
hashSetHitsCount.set(fileIDs.stream()
|
hashSetHitsCount.set(fileIDs.stream()
|
||||||
.map(fileID -> ImageGalleryController.getDefault().getHashSetManager().isInAnyHashSet(fileID))
|
.map(ImageGalleryModule.getController().getHashSetManager()::isInAnyHashSet)
|
||||||
.filter(Boolean::booleanValue)
|
.filter(Boolean::booleanValue)
|
||||||
.count());
|
.count());
|
||||||
} catch (IllegalStateException | NullPointerException ex) {
|
} catch (NoCurrentCaseException ex) {
|
||||||
LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS
|
LOGGER.log(Level.WARNING, "Could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hashSetHitsCount.get();
|
return hashSetHitsCount.get();
|
||||||
@ -139,10 +142,10 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
|
|||||||
public final synchronized long getUncategorizedCount() {
|
public final synchronized long getUncategorizedCount() {
|
||||||
if (uncatCount.get() < 0) {
|
if (uncatCount.get() < 0) {
|
||||||
try {
|
try {
|
||||||
uncatCount.set(ImageGalleryController.getDefault().getDatabase().getUncategorizedCount(fileIDs));
|
uncatCount.set(ImageGalleryModule.getController().getDatabase().getUncategorizedCount(fileIDs));
|
||||||
|
|
||||||
} catch (IllegalStateException | NullPointerException ex) {
|
} catch (TskCoreException | NoCurrentCaseException ex) {
|
||||||
LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()"); //NON-NLS
|
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();
|
return seen.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyBooleanWrapper seenProperty() {
|
public ReadOnlyBooleanProperty seenProperty() {
|
||||||
return seen;
|
return seen.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-16 Basis Technology Corp.
|
* Copyright 2013-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,27 +18,31 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
|
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||||
|
import org.sleuthkit.datamodel.DataSource;
|
||||||
import org.sleuthkit.datamodel.TagName;
|
import org.sleuthkit.datamodel.TagName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* key identifying information of a {@link Grouping}. Used to look up groups in
|
* Key identifying information of a DrawableGroup. Used to look up groups in
|
||||||
* {@link Map}s and from the db.
|
* Maps and from the db.
|
||||||
|
*
|
||||||
|
* @param <T> The type of the values of the attribute this key uses.
|
||||||
*/
|
*/
|
||||||
@Immutable
|
@Immutable
|
||||||
public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>> {
|
public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>> {
|
||||||
|
|
||||||
private final T val;
|
private final T val;
|
||||||
|
|
||||||
private final DrawableAttribute<T> attr;
|
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.attr = attr;
|
||||||
this.val = val;
|
this.val = val;
|
||||||
|
this.dataSource = dataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
public T getValue() {
|
public T getValue() {
|
||||||
@ -49,6 +53,10 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
|
|||||||
return attr;
|
return attr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Optional< DataSource> getDataSource() {
|
||||||
|
return Optional.ofNullable(dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
public String getValueDisplayName() {
|
public String getValueDisplayName() {
|
||||||
return Objects.equals(attr, DrawableAttribute.TAGS)
|
return Objects.equals(attr, DrawableAttribute.TAGS)
|
||||||
? ((TagName) getValue()).getDisplayName()
|
? ((TagName) getValue()).getDisplayName()
|
||||||
@ -63,13 +71,19 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
|
|||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int hash = 5;
|
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;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -77,11 +91,14 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final GroupKey<?> other = (GroupKey<?>) obj;
|
final GroupKey<?> other = (GroupKey<?>) obj;
|
||||||
if (this.attr != other.attr) {
|
if (!Objects.equals(this.val, other.val)) {
|
||||||
return false;
|
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
|
@Override
|
||||||
@ -92,4 +109,8 @@ public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>
|
|||||||
public Node getGraphic() {
|
public Node getGraphic() {
|
||||||
return attr.getGraphicForValue(val);
|
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.
|
* 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.groupName=Group Name",
|
||||||
"GroupSortBy.none=None",
|
"GroupSortBy.none=None",
|
||||||
"GroupSortBy.priority=Priority"})
|
"GroupSortBy.priority=Priority"})
|
||||||
@ -37,40 +38,35 @@ public class GroupSortBy implements Comparator<DrawableGroup> {
|
|||||||
/**
|
/**
|
||||||
* sort the groups by the number of files in each
|
* sort the groups by the number of files in each
|
||||||
*/
|
*/
|
||||||
public final static GroupSortBy FILE_COUNT =
|
public final static GroupSortBy FILE_COUNT
|
||||||
new GroupSortBy(Bundle.GroupSortBy_groupSize(), "folder-open-image.png",
|
= new GroupSortBy(Bundle.GroupSortBy_groupSize(), "folder-open-image.png",
|
||||||
Comparator.comparing(DrawableGroup::getSize));
|
Comparator.comparing(DrawableGroup::getSize));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sort the groups by the natural order of the grouping value ( eg group
|
* sort the groups by the natural order of the grouping value ( eg group
|
||||||
* them by path alphabetically )
|
* them by path alphabetically )
|
||||||
*/
|
*/
|
||||||
public final static GroupSortBy GROUP_BY_VALUE =
|
public final static GroupSortBy GROUP_BY_VALUE
|
||||||
new GroupSortBy(Bundle.GroupSortBy_groupName(), "folder-rename.png",
|
= new GroupSortBy(Bundle.GroupSortBy_groupName(), "folder-rename.png",
|
||||||
Comparator.comparing(DrawableGroup::getGroupByValueDislpayName));
|
Comparator.comparing(DrawableGroup::getGroupByValueDislpayName));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* don't sort the groups just use what ever order they come in (ingest
|
* don't sort the groups just use what ever order they come in (ingest
|
||||||
* order)
|
* order)
|
||||||
*/
|
*/
|
||||||
public final static GroupSortBy NONE =
|
public final static GroupSortBy NONE
|
||||||
new GroupSortBy(Bundle.GroupSortBy_none(), "prohibition.png",
|
= new GroupSortBy(Bundle.GroupSortBy_none(), "prohibition.png",
|
||||||
new AllEqualComparator<>());
|
new AllEqualComparator<>());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sort the groups by some priority metric to be determined and implemented
|
* sort the groups by some priority metric to be determined and implemented
|
||||||
*/
|
*/
|
||||||
public final static GroupSortBy PRIORITY =
|
public final static GroupSortBy PRIORITY
|
||||||
new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png",
|
= new GroupSortBy(Bundle.GroupSortBy_priority(), "hashset_hits.png",
|
||||||
Comparator.comparing(DrawableGroup::getHashHitDensity)
|
Comparator.comparing(DrawableGroup::getHashHitDensity)
|
||||||
.thenComparing(Comparator.comparing(DrawableGroup::getUncategorizedCount))
|
.thenComparing(Comparator.comparing(DrawableGroup::getUncategorizedCount))
|
||||||
.reversed());
|
.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));
|
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;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(DrawableGroup o1, DrawableGroup o2) {
|
||||||
|
return delegate.compare(o1, o2);
|
||||||
|
}
|
||||||
|
|
||||||
static class AllEqualComparator<A> implements Comparator<A> {
|
static class AllEqualComparator<A> implements Comparator<A> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -22,9 +22,9 @@ import java.util.Objects;
|
|||||||
import java.util.Optional;
|
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;
|
private final DrawableGroup group;
|
||||||
|
|
||||||
@ -32,8 +32,8 @@ public class GroupViewState {
|
|||||||
|
|
||||||
private final Optional<Long> slideShowfileID;
|
private final Optional<Long> slideShowfileID;
|
||||||
|
|
||||||
public DrawableGroup getGroup() {
|
public Optional<DrawableGroup> getGroup() {
|
||||||
return group;
|
return Optional.ofNullable(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GroupViewMode getMode() {
|
public GroupViewMode getMode() {
|
||||||
@ -44,18 +44,18 @@ public class GroupViewState {
|
|||||||
return slideShowfileID;
|
return slideShowfileID;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GroupViewState(DrawableGroup g, GroupViewMode mode, Long slideShowfileID) {
|
private GroupViewState(DrawableGroup group, GroupViewMode mode, Long slideShowfileID) {
|
||||||
this.group = g;
|
this.group = group;
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
this.slideShowfileID = Optional.ofNullable(slideShowfileID);
|
this.slideShowfileID = Optional.ofNullable(slideShowfileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GroupViewState tile(DrawableGroup g) {
|
public static GroupViewState tile(DrawableGroup group) {
|
||||||
return new GroupViewState(g, GroupViewMode.TILE, null);
|
return new GroupViewState(group, GroupViewMode.TILE, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GroupViewState slideShow(DrawableGroup g, Long fileID) {
|
public static GroupViewState slideShow(DrawableGroup group, Long fileID) {
|
||||||
return new GroupViewState(g, GroupViewMode.SLIDE_SHOW, fileID);
|
return new GroupViewState(group, GroupViewMode.SLIDE_SHOW, fileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -82,10 +82,7 @@ public class GroupViewState {
|
|||||||
if (this.mode != other.mode) {
|
if (this.mode != other.mode) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!Objects.equals(this.slideShowfileID, other.slideShowfileID)) {
|
return Objects.equals(this.slideShowfileID, other.slideShowfileID);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,20 +18,43 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
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.ButtonBase;
|
||||||
|
import javafx.scene.control.Dialog;
|
||||||
import javafx.scene.control.MenuItem;
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.stage.Stage;
|
||||||
import org.controlsfx.control.action.Action;
|
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
|
* 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() {
|
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
|
* as the action for the given Button. Usefull to have a SplitMenuButton
|
||||||
* remember the last chosen menu item as its action.
|
* remember the last chosen menu item as its action.
|
||||||
*
|
*
|
||||||
@ -51,4 +74,14 @@ public class GuiUtils {
|
|||||||
});
|
});
|
||||||
return menuItem;
|
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())));
|
Platform.runLater(() -> staleLabel.setTooltip(new Tooltip(Bundle.StatuBar_toolTip())));
|
||||||
staleLabel.visibleProperty().bind(controller.stale());
|
staleLabel.visibleProperty().bind(controller.staleProperty());
|
||||||
}
|
}
|
||||||
|
|
||||||
public StatusBar(ImageGalleryController controller) {
|
public StatusBar(ImageGalleryController controller) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-15 Basis Technology Corp.
|
* Copyright 2013-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -34,9 +34,10 @@ import javafx.scene.layout.VBox;
|
|||||||
import javafx.util.Pair;
|
import javafx.util.Pair;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
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
|
* Displays summary statistics (counts) for each group
|
||||||
@ -51,11 +52,13 @@ public class SummaryTablePane extends AnchorPane {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableView<Pair<DhsImageCategory, Long>> tableView;
|
private TableView<Pair<DhsImageCategory, Long>> tableView;
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@NbBundle.Messages({"SummaryTablePane.catColumn=Category",
|
@NbBundle.Messages({
|
||||||
"SummaryTablePane.countColumn=# Files"})
|
"SummaryTablePane.catColumn=Category",
|
||||||
|
"SummaryTablePane.countColumn=# Files"})
|
||||||
void initialize() {
|
void initialize() {
|
||||||
assert catColumn != null : "fx:id=\"catColumn\" was not injected: check your FXML file 'SummaryTablePane.fxml'.";
|
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'.";
|
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);
|
tableView.prefHeightProperty().set(7 * 25);
|
||||||
|
|
||||||
//set up columns
|
//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.setPrefWidth(USE_COMPUTED_SIZE);
|
||||||
catColumn.setText(Bundle.SummaryTablePane_catColumn());
|
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.setPrefWidth(USE_COMPUTED_SIZE);
|
||||||
countColumn.setText(Bundle.SummaryTablePane_countColumn());
|
countColumn.setText(Bundle.SummaryTablePane_countColumn());
|
||||||
|
|
||||||
@ -85,14 +88,15 @@ public class SummaryTablePane extends AnchorPane {
|
|||||||
public SummaryTablePane(ImageGalleryController controller) {
|
public SummaryTablePane(ImageGalleryController controller) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
FXMLConstructor.construct(this, "SummaryTablePane.fxml"); //NON-NLS
|
FXMLConstructor.construct(this, "SummaryTablePane.fxml"); //NON-NLS
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* listen to Category updates and rebuild the table
|
* listen to Category updates and rebuild the table
|
||||||
|
*
|
||||||
|
* @param evt The change event.
|
||||||
*/
|
*/
|
||||||
@Subscribe
|
@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();
|
final ObservableList<Pair<DhsImageCategory, Long>> data = FXCollections.observableArrayList();
|
||||||
if (Case.isCaseOpen()) {
|
if (Case.isCaseOpen()) {
|
||||||
for (DhsImageCategory cat : DhsImageCategory.values()) {
|
for (DhsImageCategory cat : DhsImageCategory.values()) {
|
||||||
|
@ -12,29 +12,41 @@
|
|||||||
<?import javafx.scene.image.ImageView?>
|
<?import javafx.scene.image.ImageView?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?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">
|
<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>
|
<items>
|
||||||
<HBox alignment="CENTER" spacing="5.0">
|
<HBox alignment="CENTER" spacing="5.0">
|
||||||
<children>
|
<children>
|
||||||
<Label fx:id="groupByLabel" text="Group By:">
|
<Label fx:id="groupByLabel" text="Group By:">
|
||||||
<labelFor>
|
|
||||||
<ComboBox fx:id="groupByBox" editable="false" />
|
|
||||||
</labelFor>
|
|
||||||
</Label>
|
</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>
|
</children>
|
||||||
</HBox>
|
</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">
|
<HBox alignment="CENTER" spacing="5.0">
|
||||||
<children>
|
<children>
|
||||||
<Label fx:id="tagImageViewLabel" text="Tag Group's Files:">
|
<Label fx:id="tagImageViewLabel" text="Tag Group's Files:">
|
||||||
@ -76,7 +88,7 @@
|
|||||||
<Insets left="5.0" />
|
<Insets left="5.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</HBox>
|
</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">
|
<HBox alignment="CENTER" spacing="5.0">
|
||||||
<children>
|
<children>
|
||||||
<Label fx:id="thumbnailSizeLabel" text="Thumbnail Size (px):">
|
<Label fx:id="thumbnailSizeLabel" text="Thumbnail Size (px):">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-16 Basis Technology Corp.
|
* Copyright 2013-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,22 +18,33 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
package org.sleuthkit.autopsy.imagegallery.gui;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import 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.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.property.DoubleProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.scene.Cursor;
|
import javafx.scene.Cursor;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ListCell;
|
||||||
import javafx.scene.control.MenuItem;
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.scene.control.SingleSelectionModel;
|
||||||
import javafx.scene.control.Slider;
|
import javafx.scene.control.Slider;
|
||||||
import javafx.scene.control.SplitMenuButton;
|
import javafx.scene.control.SplitMenuButton;
|
||||||
import javafx.scene.control.ToolBar;
|
import javafx.scene.control.ToolBar;
|
||||||
@ -42,63 +53,70 @@ import javafx.scene.image.ImageView;
|
|||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.Pane;
|
import javafx.scene.layout.Pane;
|
||||||
import javafx.scene.text.Text;
|
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.controlsfx.control.PopOver;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
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.Logger;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeGroupAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeGroupAction;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.TagGroupAction;
|
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.DrawableAttribute;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
|
||||||
|
import org.sleuthkit.datamodel.DataSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for the ToolBar
|
* Controller for the ToolBar
|
||||||
*/
|
*/
|
||||||
public class Toolbar extends 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;
|
private static final int SIZE_SLIDER_DEFAULT = 100;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ComboBox<Optional<DataSource>> dataSourceComboBox;
|
||||||
@FXML
|
@FXML
|
||||||
private ImageView sortHelpImageView;
|
private ImageView sortHelpImageView;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ComboBox<DrawableAttribute<?>> groupByBox;
|
private ComboBox<DrawableAttribute<?>> groupByBox;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Slider sizeSlider;
|
private Slider sizeSlider;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private SplitMenuButton catGroupMenuButton;
|
private SplitMenuButton catGroupMenuButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private SplitMenuButton tagGroupMenuButton;
|
private SplitMenuButton tagGroupMenuButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label groupByLabel;
|
private Label groupByLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label tagImageViewLabel;
|
private Label tagImageViewLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label categoryImageViewLabel;
|
private Label categoryImageViewLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label thumbnailSizeLabel;
|
private Label thumbnailSizeLabel;
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
|
||||||
private SortChooser<DrawableGroup, GroupSortBy> sortChooser;
|
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() {
|
private final InvalidationListener queryInvalidationListener = new InvalidationListener() {
|
||||||
public void invalidated(Observable o) {
|
@Override
|
||||||
controller.getGroupManager().regroup(
|
public void invalidated(Observable invalidated) {
|
||||||
|
controller.getGroupManager().regroup(getSelectedDataSource(),
|
||||||
groupByBox.getSelectionModel().getSelectedItem(),
|
groupByBox.getSelectionModel().getSelectedItem(),
|
||||||
sortChooser.getComparator(),
|
sortChooser.getComparator(),
|
||||||
sortChooser.getSortOrder(),
|
sortChooser.getSortOrder(),
|
||||||
@ -106,61 +124,94 @@ public class Toolbar extends ToolBar {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public DoubleProperty thumbnailSizeProperty() {
|
|
||||||
return sizeSlider.valueProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@NbBundle.Messages({"Toolbar.groupByLabel=Group By:",
|
@NbBundle.Messages(
|
||||||
"Toolbar.sortByLabel=Sort By:",
|
{"Toolbar.groupByLabel=Group By:",
|
||||||
"Toolbar.ascRadio=Ascending",
|
"Toolbar.sortByLabel=Sort By:",
|
||||||
"Toolbar.descRadio=Descending",
|
"Toolbar.ascRadio=Ascending",
|
||||||
"Toolbar.tagImageViewLabel=Tag Group's Files:",
|
"Toolbar.descRadio=Descending",
|
||||||
"Toolbar.categoryImageViewLabel=Categorize Group's Files:",
|
"Toolbar.tagImageViewLabel=Tag Group's Files:",
|
||||||
"Toolbar.thumbnailSizeLabel=Thumbnail Size (px):",
|
"Toolbar.categoryImageViewLabel=Categorize Group's Files:",
|
||||||
"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.thumbnailSizeLabel=Thumbnail Size (px):",
|
||||||
"Toolbar.sortHelpTitle=Group Sorting",})
|
"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() {
|
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 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 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) -> {
|
//set internationalized label text
|
||||||
Platform.runLater(() -> syncGroupControlsEnabledState(newViewState));
|
groupByLabel.setText(Bundle.Toolbar_groupByLabel());
|
||||||
});
|
tagImageViewLabel.setText(Bundle.Toolbar_tagImageViewLabel());
|
||||||
syncGroupControlsEnabledState(controller.viewState().get());
|
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 {
|
initDataSourceComboBox();
|
||||||
TagGroupAction followUpGroupAction = new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller);
|
groupByBox.setItems(FXCollections.observableList(DrawableAttribute.getGroupableAttrs()));
|
||||||
tagGroupMenuButton.setOnAction(followUpGroupAction);
|
groupByBox.getSelectionModel().select(DrawableAttribute.PATH);
|
||||||
tagGroupMenuButton.setText(followUpGroupAction.getText());
|
groupByBox.disableProperty().bind(controller.regroupDisabledProperty());
|
||||||
tagGroupMenuButton.setGraphic(followUpGroupAction.getGraphic());
|
groupByBox.setCellFactory(listView -> new AttributeListCell());
|
||||||
} catch (TskCoreException ex) {
|
groupByBox.setButtonCell(new AttributeListCell());
|
||||||
/*
|
groupByBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
* The problem appears to be a timing issue where a case is closed
|
if (oldValue == DrawableAttribute.PATH
|
||||||
* before this initialization is completed, which It appears to be
|
&& newValue != DrawableAttribute.PATH
|
||||||
* harmless, so we are temporarily changing this log message to a
|
&& getSelectedDataSource() != null) {
|
||||||
* WARNING.
|
|
||||||
*
|
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, Bundle.Toolbar_nonPathGroupingWarning_content());
|
||||||
* TODO (JIRA-3010): SEVERE error logged by image Gallery UI
|
alert.setHeaderText(Bundle.Toolbar_nonPathGroupingWarning_header());
|
||||||
*/
|
alert.setTitle(Bundle.Toolbar_nonPathGroupingWarning_title());
|
||||||
if (Case.isCaseOpen()) {
|
alert.initModality(Modality.APPLICATION_MODAL);
|
||||||
LOGGER.log(Level.WARNING, "Could not create Follow Up tag menu item", ex); //NON-NLS
|
alert.initOwner(getScene().getWindow());
|
||||||
}
|
GuiUtils.setDialogIcons(alert);
|
||||||
else {
|
if (alert.showAndWait().orElse(ButtonType.CANCEL) == ButtonType.OK) {
|
||||||
// don't add stack trace to log because it makes looking for real errors harder
|
queryInvalidationListener.invalidated(observable);
|
||||||
LOGGER.log(Level.INFO, "Unable to get tag name. Case is closed."); //NON-NLS
|
} else {
|
||||||
}
|
Platform.runLater(() -> groupByBox.getSelectionModel().select(DrawableAttribute.PATH));
|
||||||
}
|
}
|
||||||
tagGroupMenuButton.showingProperty().addListener(showing -> {
|
} else {
|
||||||
if (tagGroupMenuButton.isShowing()) {
|
queryInvalidationListener.invalidated(observable);
|
||||||
List<MenuItem> selTagMenues = Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
|
|
||||||
tn -> GuiUtils.createAutoAssigningMenuItem(tagGroupMenuButton, new TagGroupAction(tn, controller)));
|
|
||||||
tagGroupMenuButton.getItems().setAll(selTagMenues);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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);
|
CategorizeGroupAction cat5GroupAction = new CategorizeGroupAction(DhsImageCategory.FIVE, controller);
|
||||||
catGroupMenuButton.setOnAction(cat5GroupAction);
|
catGroupMenuButton.setOnAction(cat5GroupAction);
|
||||||
catGroupMenuButton.setText(cat5GroupAction.getText());
|
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()));
|
private DataSource getSelectedDataSource() {
|
||||||
groupByBox.getSelectionModel().select(DrawableAttribute.PATH);
|
Optional<DataSource> empty = Optional.empty();
|
||||||
groupByBox.getSelectionModel().selectedItemProperty().addListener(queryInvalidationListener);
|
return defaultIfNull(dataSourceSelectionModel.getSelectedItem(), empty).orElse(null);
|
||||||
groupByBox.disableProperty().bind(ImageGalleryController.getDefault().regroupDisabled());
|
}
|
||||||
groupByBox.setCellFactory(listView -> new AttributeListCell());
|
|
||||||
groupByBox.setButtonCell(new AttributeListCell());
|
|
||||||
|
|
||||||
sortChooser = new SortChooser<>(GroupSortBy.getValues());
|
private void initDataSourceComboBox() {
|
||||||
sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> {
|
dataSourceComboBox.setCellFactory(param -> new DataSourceCell());
|
||||||
final boolean orderDisabled = newComparator == GroupSortBy.NONE || newComparator == GroupSortBy.PRIORITY;
|
dataSourceComboBox.setButtonCell(new DataSourceCell());
|
||||||
sortChooser.setSortOrderDisabled(orderDisabled);
|
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;
|
@Override
|
||||||
sortChooser.setValueType(valueType);
|
public Optional<DataSource> fromString(String string) {
|
||||||
queryInvalidationListener.invalidated(observable);
|
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
dataSourceComboBox.setItems(dataSources);
|
||||||
|
|
||||||
sortChooser.sortOrderProperty().addListener(queryInvalidationListener);
|
Case.addEventTypeSubscriber(ImmutableSet.of(DATA_SOURCE_ADDED, DATA_SOURCE_DELETED),
|
||||||
sortChooser.setComparator(controller.getGroupManager().getSortBy());
|
evt -> {
|
||||||
getItems().add(1, sortChooser);
|
Platform.runLater(() -> {
|
||||||
sortHelpImageView.setCursor(Cursor.HAND);
|
Optional<DataSource> selectedItem = dataSourceSelectionModel.getSelectedItem();
|
||||||
|
syncDataSources().addListener(() -> dataSourceSelectionModel.select(selectedItem), Platform::runLater);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
syncDataSources();
|
||||||
|
|
||||||
sortHelpImageView.setOnMouseClicked(clicked -> {
|
controller.getGroupManager().getDataSourceProperty()
|
||||||
Text text = new Text(Bundle.Toolbar_sortHelp());
|
.addListener((observable, oldDataSource, newDataSource)
|
||||||
text.setWrappingWidth(480); //This is a hack to fix the layout.
|
-> dataSourceSelectionModel.select(Optional.ofNullable(newDataSource)));
|
||||||
showPopoverHelp(sortHelpImageView,
|
dataSourceSelectionModel.select(Optional.ofNullable(controller.getGroupManager().getDataSource()));
|
||||||
Bundle.Toolbar_sortHelpTitle(),
|
dataSourceComboBox.disableProperty().bind(groupByBox.getSelectionModel().selectedItemProperty().isNotEqualTo(DrawableAttribute.PATH));
|
||||||
sortHelpImageView.getImage(), text);
|
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);
|
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) {
|
private void syncGroupControlsEnabledState(GroupViewState newViewState) {
|
||||||
boolean noGroupSelected = newViewState == null
|
boolean noGroupSelected = (null == newViewState)
|
||||||
? true
|
|| (null == newViewState.getGroup());
|
||||||
: newViewState.getGroup() == null;
|
Platform.runLater(() -> {
|
||||||
|
tagGroupMenuButton.setDisable(noGroupSelected);
|
||||||
tagGroupMenuButton.setDisable(noGroupSelected);
|
catGroupMenuButton.setDisable(noGroupSelected);
|
||||||
catGroupMenuButton.setDisable(noGroupSelected);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
@ -256,5 +386,22 @@ public class Toolbar extends ToolBar {
|
|||||||
public Toolbar(ImageGalleryController controller) {
|
public Toolbar(ImageGalleryController controller) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS
|
FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
@Override
|
||||||
Task<Image> newReadImageTask(DrawableFile file) {
|
Task<Image> newReadImageTask(DrawableFile file) {
|
||||||
return file.getThumbnailTask();
|
return getController().getThumbsCache().getThumbnailTask(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-2017 Basis Technology Corp.
|
* Copyright 2013-2018 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -79,9 +79,9 @@ import org.sleuthkit.datamodel.TagName;
|
|||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract base class for {@link DrawableTile} and {@link SlideShowView},
|
* An abstract base class for DrawableTile and SlideShowView, since they share a
|
||||||
* since they share a similar node tree and many behaviors, other implementors
|
* similar node tree and many behaviors, other implementors of DrawableViews
|
||||||
* of {@link DrawableView}s should implement the interface directly
|
* should implement the interface directly
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* TODO: refactor ExternalViewerAction to supply its own name
|
* 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"})
|
@NbBundle.Messages({"DrawableTileBase.externalViewerAction.text=Open in External Viewer"})
|
||||||
public abstract class DrawableTileBase extends DrawableUIBase {
|
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 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)));
|
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<>();
|
final ArrayList<MenuItem> menuItems = new ArrayList<>();
|
||||||
|
|
||||||
menuItems.add(CategorizeAction.getCategoriesMenu(getController()));
|
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));
|
final Collection<AbstractFile> selectedFilesList = new HashSet<>(Utilities.actionsGlobalContext().lookupAll(AbstractFile.class));
|
||||||
if(selectedFilesList.size() == 1) {
|
if (selectedFilesList.size() == 1) {
|
||||||
menuItems.add(DeleteTagAction.getTagMenu(getController()));
|
menuItems.add(DeleteTagAction.getTagMenu(getController()));
|
||||||
}
|
}
|
||||||
|
|
||||||
final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles());
|
final MenuItem extractMenuItem = new MenuItem(Bundle.DrawableTileBase_menuItem_extractFiles());
|
||||||
extractMenuItem.setOnAction(actionEvent -> {
|
extractMenuItem.setOnAction(actionEvent
|
||||||
SwingUtilities.invokeLater(() -> {
|
-> SwingUtilities.invokeLater(() -> {
|
||||||
TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
|
TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
|
||||||
ExtractAction.getInstance().actionPerformed(new java.awt.event.ActionEvent(etc, 0, null));
|
ExtractAction.getInstance().actionPerformed(new java.awt.event.ActionEvent(etc, 0, null));
|
||||||
});
|
}));
|
||||||
});
|
|
||||||
menuItems.add(extractMenuItem);
|
menuItems.add(extractMenuItem);
|
||||||
|
|
||||||
MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer());
|
MenuItem contentViewer = new MenuItem(Bundle.DrawableTileBase_menuItem_showContentViewer());
|
||||||
contentViewer.setOnAction(actionEvent -> {
|
contentViewer.setOnAction(actionEvent
|
||||||
SwingUtilities.invokeLater(() -> {
|
-> SwingUtilities.invokeLater(() -> {
|
||||||
new NewWindowViewAction(Bundle.DrawableTileBase_menuItem_showContentViewer(), new FileNode(file.getAbstractFile())).actionPerformed(null);
|
new NewWindowViewAction(Bundle.DrawableTileBase_menuItem_showContentViewer(), new FileNode(file.getAbstractFile()))
|
||||||
});
|
.actionPerformed(null);
|
||||||
});
|
}));
|
||||||
menuItems.add(contentViewer);
|
menuItems.add(contentViewer);
|
||||||
|
|
||||||
OpenExternalViewerAction openExternalViewerAction = new OpenExternalViewerAction(file);
|
OpenExternalViewerAction openExternalViewerAction = new OpenExternalViewerAction(file);
|
||||||
@ -243,35 +247,32 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
|||||||
protected abstract String getTextForLabel();
|
protected abstract String getTextForLabel();
|
||||||
|
|
||||||
protected void initialize() {
|
protected void initialize() {
|
||||||
followUpToggle.setOnAction(actionEvent -> {
|
|
||||||
getFile().ifPresent(file -> {
|
followUpToggle.setOnAction(
|
||||||
if (followUpToggle.isSelected() == true) {
|
actionEvent -> getFile().ifPresent(
|
||||||
try {
|
file -> {
|
||||||
selectionModel.clearAndSelect(file.getId());
|
if (followUpToggle.isSelected() == true) {
|
||||||
new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent);
|
selectionModel.clearAndSelect(file.getId());
|
||||||
} catch (TskCoreException ex) {
|
new AddTagAction(getController(), getController().getTagsManager().getFollowUpTagName(), selectionModel.getSelected()).handle(actionEvent);
|
||||||
LOGGER.log(Level.SEVERE, "Failed to add Follow Up tag. Could not load TagName.", ex); //NON-NLS
|
} else {
|
||||||
}
|
new DeleteFollowUpTagAction(getController(), file).handle(actionEvent);
|
||||||
} else {
|
}
|
||||||
new DeleteFollowUpTagAction(getController(), file).handle(actionEvent);
|
})
|
||||||
}
|
);
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean hasFollowUp() {
|
protected boolean hasFollowUp() {
|
||||||
if (getFileID().isPresent()) {
|
if (getFileID().isPresent()) {
|
||||||
try {
|
|
||||||
TagName followUpTagName = getController().getTagsManager().getFollowUpTagName();
|
TagName followUpTagName = getController().getTagsManager().getFollowUpTagName();
|
||||||
|
if (getFile().isPresent()) {
|
||||||
return DrawableAttribute.TAGS.getValue(getFile().get()).stream()
|
return DrawableAttribute.TAGS.getValue(getFile().get()).stream()
|
||||||
.anyMatch(followUpTagName::equals);
|
.anyMatch(followUpTagName::equals);
|
||||||
} catch (TskCoreException ex) {
|
} else {
|
||||||
LOGGER.log(Level.WARNING, "failed to get follow up tag name ", ex); //NON-NLS
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -342,18 +343,14 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
|||||||
@Override
|
@Override
|
||||||
public void handleTagAdded(ContentTagAddedEvent evt) {
|
public void handleTagAdded(ContentTagAddedEvent evt) {
|
||||||
getFileID().ifPresent(fileID -> {
|
getFileID().ifPresent(fileID -> {
|
||||||
try {
|
final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS
|
||||||
final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName();
|
final ContentTag addedTag = evt.getAddedTag();
|
||||||
final ContentTag addedTag = evt.getAddedTag();
|
if (fileID == addedTag.getContent().getId()
|
||||||
if (fileID == addedTag.getContent().getId()
|
&& addedTag.getName().equals(followUpTagName)) {
|
||||||
&& addedTag.getName().equals(followUpTagName)) {
|
Platform.runLater(() -> {
|
||||||
Platform.runLater(() -> {
|
followUpImageView.setImage(followUpIcon);
|
||||||
followUpImageView.setImage(followUpIcon);
|
followUpToggle.setSelected(true);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -362,15 +359,11 @@ public abstract class DrawableTileBase extends DrawableUIBase {
|
|||||||
@Override
|
@Override
|
||||||
public void handleTagDeleted(ContentTagDeletedEvent evt) {
|
public void handleTagDeleted(ContentTagDeletedEvent evt) {
|
||||||
getFileID().ifPresent(fileID -> {
|
getFileID().ifPresent(fileID -> {
|
||||||
try {
|
final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName(); //NON-NLS
|
||||||
final TagName followUpTagName = getController().getTagsManager().getFollowUpTagName();
|
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo();
|
||||||
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo();
|
if (fileID == deletedTagInfo.getContentID()
|
||||||
if (fileID == deletedTagInfo.getContentID()
|
&& deletedTagInfo.getName().equals(followUpTagName)) {
|
||||||
&& deletedTagInfo.getName().equals(followUpTagName)) {
|
updateFollowUpIcon();
|
||||||
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
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2015 Basis Technology Corp.
|
* Copyright 2015-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -25,7 +25,6 @@ import java.util.concurrent.CancellationException;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.logging.Level;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
@ -41,7 +40,6 @@ import javafx.scene.layout.BorderPane;
|
|||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.controlsfx.control.action.ActionUtils;
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
|
||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction;
|
||||||
@ -55,10 +53,10 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
"DrawableUIBase.errorLabel.OOMText=Insufficent memory"})
|
"DrawableUIBase.errorLabel.OOMText=Insufficent memory"})
|
||||||
abstract public class DrawableUIBase extends AnchorPane implements DrawableView {
|
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();
|
static final Executor exec = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(DrawableUIBase.class.getName());
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
BorderPane imageBorder;
|
BorderPane imageBorder;
|
||||||
@FXML
|
@FXML
|
||||||
@ -96,20 +94,17 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
|||||||
@Override
|
@Override
|
||||||
synchronized public Optional<DrawableFile> getFile() {
|
synchronized public Optional<DrawableFile> getFile() {
|
||||||
if (fileIDOpt.isPresent()) {
|
if (fileIDOpt.isPresent()) {
|
||||||
if (fileOpt.isPresent() && fileOpt.get().getId() == fileIDOpt.get()) {
|
if (!fileOpt.isPresent() || fileOpt.get().getId() != fileIDOpt.get()) {
|
||||||
return fileOpt;
|
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
fileOpt = Optional.ofNullable(getController().getFileFromId(fileIDOpt.get()));
|
fileOpt = Optional.ofNullable(getController().getFileFromID(fileIDOpt.get()));
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
Logger.getAnonymousLogger().log(Level.WARNING, "failed to get DrawableFile for obj_id" + fileIDOpt.get(), ex); //NON-NLS
|
|
||||||
fileOpt = Optional.empty();
|
fileOpt = Optional.empty();
|
||||||
}
|
}
|
||||||
return fileOpt;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Optional.empty();
|
fileOpt = Optional.empty();
|
||||||
}
|
}
|
||||||
|
return fileOpt;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void setFileHelper(Long newFileID);
|
protected abstract void setFileHelper(Long newFileID);
|
||||||
@ -126,9 +121,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
|||||||
}
|
}
|
||||||
|
|
||||||
synchronized protected void updateContent() {
|
synchronized protected void updateContent() {
|
||||||
if (getFile().isPresent()) {
|
getFile().ifPresent(this::doReadImageTask);
|
||||||
doReadImageTask(getFile().get());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized Node doReadImageTask(DrawableFile file) {
|
synchronized Node doReadImageTask(DrawableFile file) {
|
||||||
@ -138,16 +131,16 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
|||||||
Platform.runLater(() -> imageBorder.setCenter(progressNode));
|
Platform.runLater(() -> imageBorder.setCenter(progressNode));
|
||||||
|
|
||||||
//called on fx thread
|
//called on fx thread
|
||||||
myTask.setOnSucceeded(succeeded -> {
|
myTask.setOnSucceeded(succeeded -> { //on fx thread
|
||||||
showImage(file, myTask);
|
showImage(file, myTask);
|
||||||
synchronized (DrawableUIBase.this) {
|
synchronized (DrawableUIBase.this) {
|
||||||
imageTask = null;
|
imageTask = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
myTask.setOnFailed(failed -> {
|
myTask.setOnFailed(failed -> { //on fx thread
|
||||||
Throwable exception = myTask.getException();
|
Throwable exception = myTask.getException();
|
||||||
if (exception instanceof OutOfMemoryError
|
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);
|
showErrorNode(Bundle.DrawableUIBase_errorLabel_OOMText(), file);
|
||||||
} else {
|
} else {
|
||||||
showErrorNode(Bundle.DrawableUIBase_errorLabel_text(), file);
|
showErrorNode(Bundle.DrawableUIBase_errorLabel_text(), file);
|
||||||
@ -156,7 +149,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
|||||||
imageTask = null;
|
imageTask = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
myTask.setOnCancelled(cancelled -> {
|
myTask.setOnCancelled(cancelled -> { //on fx thread
|
||||||
synchronized (DrawableUIBase.this) {
|
synchronized (DrawableUIBase.this) {
|
||||||
imageTask = null;
|
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 imageTask to get a progress indicator for.
|
||||||
* @param imageTask the value of imageTask
|
*
|
||||||
|
* @return The new Node to use as a progress indicator.
|
||||||
*/
|
*/
|
||||||
Node newProgressIndicator(final Task<?> imageTask) {
|
Node newProgressIndicator(final Task<?> imageTask) {
|
||||||
ProgressIndicator loadingProgressIndicator = new ProgressIndicator(-1);
|
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;
|
package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
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.casemodule.events.ContentTagDeletedEvent;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
|
||||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
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.CategoryManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
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.
|
* update the visual representation of the category of the assigned file.
|
||||||
* Implementations of {@link DrawableView} must register themselves with
|
* Implementations of DrawableView } must register themselves with
|
||||||
* {@link CategoryManager#registerListener(java.lang.Object)} to ahve this
|
* CategoryManager.registerListener()} to have this method invoked
|
||||||
* method invoked
|
|
||||||
*
|
*
|
||||||
* @param evt the CategoryChangeEvent to handle
|
* @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
|
Logger.getLogger(DrawableView.class.getName()).log(Level.WARNING, "Error looking up hash set hits"); //NON-NLS
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Border getCategoryBorder(DhsImageCategory category) {
|
static Border getCategoryBorder(DhsImageCategory category) {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.CheckBox?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.MenuItem?>
|
<?import javafx.scene.control.MenuItem?>
|
||||||
<?import javafx.scene.control.RadioButton?>
|
<?import javafx.scene.control.RadioButton?>
|
||||||
@ -11,6 +12,7 @@
|
|||||||
<?import javafx.scene.control.ToolBar?>
|
<?import javafx.scene.control.ToolBar?>
|
||||||
<?import javafx.scene.image.Image?>
|
<?import javafx.scene.image.Image?>
|
||||||
<?import javafx.scene.image.ImageView?>
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
<?import javafx.scene.layout.BorderPane?>
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
@ -18,7 +20,7 @@
|
|||||||
<?import org.controlsfx.control.GridView?>
|
<?import org.controlsfx.control.GridView?>
|
||||||
<?import org.controlsfx.control.SegmentedButton?>
|
<?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>
|
<center>
|
||||||
<GridView fx:id="gridView" BorderPane.alignment="CENTER" />
|
<GridView fx:id="gridView" BorderPane.alignment="CENTER" />
|
||||||
@ -29,7 +31,7 @@
|
|||||||
<HBox alignment="CENTER_LEFT" spacing="5.0" BorderPane.alignment="TOP_LEFT">
|
<HBox alignment="CENTER_LEFT" spacing="5.0" BorderPane.alignment="TOP_LEFT">
|
||||||
<children>
|
<children>
|
||||||
<Label fx:id="bottomLabel" text="Group Viewing History: " />
|
<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>
|
<graphic>
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
@ -38,7 +40,7 @@
|
|||||||
</ImageView>
|
</ImageView>
|
||||||
</graphic>
|
</graphic>
|
||||||
</Button>
|
</Button>
|
||||||
<Button fx:id="forwardButton" mnemonicParsing="false" text="forward">
|
<Button fx:id="forwardButton" mnemonicParsing="false" text="Forward">
|
||||||
<graphic>
|
<graphic>
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
@ -56,18 +58,23 @@
|
|||||||
<right>
|
<right>
|
||||||
<HBox alignment="CENTER_RIGHT" spacing="5.0" BorderPane.alignment="TOP_RIGHT">
|
<HBox alignment="CENTER_RIGHT" spacing="5.0" BorderPane.alignment="TOP_RIGHT">
|
||||||
<children>
|
<children>
|
||||||
<Button fx:id="nextButton" contentDisplay="RIGHT" mnemonicParsing="false" text="next unseen group" BorderPane.alignment="CENTER_RIGHT">
|
<CheckBox fx:id="seenByOtherExaminersCheckBox" mnemonicParsing="false" text="Don't show groups seen by other examiners" />
|
||||||
<BorderPane.margin>
|
<AnchorPane fx:id="nextButtonPane" BorderPane.alignment="CENTER_RIGHT">
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<BorderPane.margin>
|
||||||
</BorderPane.margin>
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
<graphic>
|
</BorderPane.margin>
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
<children>
|
||||||
<image>
|
<Button fx:id="nextButton" contentDisplay="RIGHT" minWidth="175.0" mnemonicParsing="false" text="All Groups Gave Been Seen">
|
||||||
<Image url="@../../images/control-double.png" />
|
<graphic>
|
||||||
</image>
|
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||||
</ImageView>
|
<image>
|
||||||
</graphic>
|
<Image url="@../../images/control-double.png" />
|
||||||
</Button>
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</AnchorPane>
|
||||||
</children>
|
</children>
|
||||||
<BorderPane.margin>
|
<BorderPane.margin>
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-16 Basis Technology Corp.
|
* Copyright 2013-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -21,6 +21,10 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
|
|||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import 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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -53,7 +57,9 @@ import javafx.event.ActionEvent;
|
|||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.geometry.Bounds;
|
import javafx.geometry.Bounds;
|
||||||
|
import javafx.scene.Cursor;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.ContextMenu;
|
import javafx.scene.control.ContextMenu;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.MenuItem;
|
import javafx.scene.control.MenuItem;
|
||||||
@ -81,6 +87,7 @@ import static javafx.scene.input.KeyCode.RIGHT;
|
|||||||
import static javafx.scene.input.KeyCode.UP;
|
import static javafx.scene.input.KeyCode.UP;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.layout.Border;
|
import javafx.scene.layout.Border;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.BorderStroke;
|
import javafx.scene.layout.BorderStroke;
|
||||||
@ -90,8 +97,8 @@ import javafx.scene.layout.CornerRadii;
|
|||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import javax.swing.Action;
|
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.controlsfx.control.GridCell;
|
import org.controlsfx.control.GridCell;
|
||||||
import org.controlsfx.control.GridView;
|
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.Logger;
|
||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||||
import org.sleuthkit.autopsy.directorytree.ExtractAction;
|
import org.sleuthkit.autopsy.directorytree.ExtractAction;
|
||||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
|
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.SwingMenuItemAdapter;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.TagSelectedFilesAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.TagSelectedFilesAction;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.UndoAction;
|
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.DrawableFile;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
||||||
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
|
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A GroupPane displays the contents of a {@link DrawableGroup}. It supports
|
* A GroupPane displays the contents of a DrawableGroup. It supports both
|
||||||
* both a {@link GridView} based view and a {@link SlideShowView} view by
|
* GridView and SlideShowView modes by swapping out its internal components.
|
||||||
* swapping out its internal components.
|
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* TODO: Extract the The GridView instance to a separate class analogous to the
|
* 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
|
* https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview
|
||||||
*/
|
*/
|
||||||
public class GroupPane extends BorderPane {
|
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 BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2);
|
||||||
private static final CornerRadii CORNER_RADII_2 = new CornerRadii(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 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))
|
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(
|
||||||
private static final List<KeyCode> categoryKeyCodes = Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5,
|
NUMPAD0, NUMPAD1, NUMPAD2, NUMPAD3, NUMPAD4, NUMPAD5,
|
||||||
KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5);
|
DIGIT0, DIGIT1, DIGIT2, DIGIT3, DIGIT4, DIGIT5);
|
||||||
|
|
||||||
private final Back backAction;
|
|
||||||
|
|
||||||
private final Forward forwardAction;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button undoButton;
|
private Button undoButton;
|
||||||
@FXML
|
@FXML
|
||||||
private Button redoButton;
|
private Button redoButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private SplitMenuButton catSelectedSplitMenu;
|
private SplitMenuButton catSelectedSplitMenu;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private SplitMenuButton tagSelectedSplitMenu;
|
private SplitMenuButton tagSelectedSplitMenu;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToolBar headerToolBar;
|
private ToolBar headerToolBar;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleButton cat0Toggle;
|
private ToggleButton cat0Toggle;
|
||||||
@FXML
|
@FXML
|
||||||
@ -189,30 +191,29 @@ public class GroupPane extends BorderPane {
|
|||||||
private ToggleButton cat4Toggle;
|
private ToggleButton cat4Toggle;
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleButton cat5Toggle;
|
private ToggleButton cat5Toggle;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private SegmentedButton segButton;
|
private SegmentedButton segButton;
|
||||||
|
|
||||||
private SlideShowView slideShowPane;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleButton slideShowToggle;
|
private ToggleButton slideShowToggle;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private GridView<Long> gridView;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleButton tileToggle;
|
private ToggleButton tileToggle;
|
||||||
|
|
||||||
|
private SlideShowView slideShowPane;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private GridView<Long> gridView;
|
||||||
@FXML
|
@FXML
|
||||||
private Button nextButton;
|
private Button nextButton;
|
||||||
|
@FXML
|
||||||
|
private AnchorPane nextButtonPane;
|
||||||
|
@FXML
|
||||||
|
private CheckBox seenByOtherExaminersCheckBox;
|
||||||
@FXML
|
@FXML
|
||||||
private Button backButton;
|
private Button backButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button forwardButton;
|
private Button forwardButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label groupLabel;
|
private Label groupLabel;
|
||||||
@FXML
|
@FXML
|
||||||
@ -223,36 +224,33 @@ public class GroupPane extends BorderPane {
|
|||||||
private Label catContainerLabel;
|
private Label catContainerLabel;
|
||||||
@FXML
|
@FXML
|
||||||
private Label catHeadingLabel;
|
private Label catHeadingLabel;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private HBox catSegmentedContainer;
|
private HBox catSegmentedContainer;
|
||||||
@FXML
|
@FXML
|
||||||
private HBox catSplitMenuContainer;
|
private HBox catSplitMenuContainer;
|
||||||
|
|
||||||
private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler();
|
private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(GroupPane.class);
|
||||||
|
|
||||||
private final NextUnseenGroup nextGroupAction;
|
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
|
|
||||||
private ContextMenu contextMenu;
|
private final FileIDSelectionModel selectionModel;
|
||||||
|
|
||||||
private Integer selectionAnchorIndex;
|
private Integer selectionAnchorIndex;
|
||||||
|
|
||||||
private final UndoAction undoAction;
|
private final UndoAction undoAction;
|
||||||
private final RedoAction redoAction;
|
private final RedoAction redoAction;
|
||||||
|
private final Back backAction;
|
||||||
GroupViewMode getGroupViewMode() {
|
private final Forward forwardAction;
|
||||||
return groupViewMode.get();
|
private final NextUnseenGroup nextGroupAction;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler();
|
||||||
* the current GroupViewMode of this GroupPane
|
|
||||||
*/
|
private ContextMenu contextMenu;
|
||||||
|
|
||||||
|
/** the current GroupViewMode of this GroupPane */
|
||||||
private final SimpleObjectProperty<GroupViewMode> groupViewMode = new SimpleObjectProperty<>(GroupViewMode.TILE);
|
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<>();
|
private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -263,7 +261,7 @@ public class GroupPane extends BorderPane {
|
|||||||
*/
|
*/
|
||||||
@ThreadConfined(type = ThreadType.JFX)
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
private final Map<Long, DrawableCell> cellMap = new HashMap<>();
|
private final Map<Long, DrawableCell> cellMap = new HashMap<>();
|
||||||
|
|
||||||
private final InvalidationListener filesSyncListener = (observable) -> {
|
private final InvalidationListener filesSyncListener = (observable) -> {
|
||||||
final String header = getHeaderString();
|
final String header = getHeaderString();
|
||||||
final List<Long> fileIds = getGroup().getFileIDs();
|
final List<Long> fileIds = getGroup().getFileIDs();
|
||||||
@ -273,7 +271,7 @@ public class GroupPane extends BorderPane {
|
|||||||
groupLabel.setText(header);
|
groupLabel.setText(header);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
public GroupPane(ImageGalleryController controller) {
|
public GroupPane(ImageGalleryController controller) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.selectionModel = controller.getSelectionModel();
|
this.selectionModel = controller.getSelectionModel();
|
||||||
@ -282,10 +280,14 @@ public class GroupPane extends BorderPane {
|
|||||||
forwardAction = new Forward(controller);
|
forwardAction = new Forward(controller);
|
||||||
undoAction = new UndoAction(controller);
|
undoAction = new UndoAction(controller);
|
||||||
redoAction = new RedoAction(controller);
|
redoAction = new RedoAction(controller);
|
||||||
|
|
||||||
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
|
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GroupViewMode getGroupViewMode() {
|
||||||
|
return groupViewMode.get();
|
||||||
|
}
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadType.JFX)
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
public void activateSlideShowViewer(Long slideShowFileID) {
|
public void activateSlideShowViewer(Long slideShowFileID) {
|
||||||
groupViewMode.set(GroupViewMode.SLIDE_SHOW);
|
groupViewMode.set(GroupViewMode.SLIDE_SHOW);
|
||||||
@ -301,16 +303,16 @@ public class GroupPane extends BorderPane {
|
|||||||
} else {
|
} else {
|
||||||
slideShowPane.setFile(slideShowFileID);
|
slideShowPane.setFile(slideShowFileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
setCenter(slideShowPane);
|
setCenter(slideShowPane);
|
||||||
slideShowPane.requestFocus();
|
slideShowPane.requestFocus();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void syncCatToggle(DrawableFile file) {
|
void syncCatToggle(DrawableFile file) {
|
||||||
getToggleForCategory(file.getCategory()).setSelected(true);
|
getToggleForCategory(file.getCategory()).setSelected(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void activateTileViewer() {
|
public void activateTileViewer() {
|
||||||
groupViewMode.set(GroupViewMode.TILE);
|
groupViewMode.set(GroupViewMode.TILE);
|
||||||
tileToggle.setSelected(true);
|
tileToggle.setSelected(true);
|
||||||
@ -322,17 +324,19 @@ public class GroupPane extends BorderPane {
|
|||||||
slideShowPane = null;
|
slideShowPane = null;
|
||||||
this.scrollToFileID(selectionModel.lastSelectedProperty().get());
|
this.scrollToFileID(selectionModel.lastSelectedProperty().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
public DrawableGroup getGroup() {
|
public DrawableGroup getGroup() {
|
||||||
return grouping.get();
|
return grouping.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectAllFiles() {
|
private void selectAllFiles() {
|
||||||
selectionModel.clearAndSelectAll(getGroup().getFileIDs());
|
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",
|
@NbBundle.Messages({"# {0} - default group name",
|
||||||
"# {1} - hashset hits count",
|
"# {1} - hashset hits count",
|
||||||
@ -343,15 +347,15 @@ public class GroupPane extends BorderPane {
|
|||||||
: Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()),
|
: Bundle.GroupPane_headerString(StringUtils.defaultIfBlank(getGroup().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()),
|
||||||
getGroup().getHashSetHitsCount(), getGroup().getSize());
|
getGroup().getHashSetHitsCount(), getGroup().getSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextMenu getContextMenu() {
|
ContextMenu getContextMenu() {
|
||||||
return contextMenu;
|
return contextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlyObjectProperty<DrawableGroup> grouping() {
|
ReadOnlyObjectProperty<DrawableGroup> grouping() {
|
||||||
return grouping.getReadOnlyProperty();
|
return grouping.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ToggleButton getToggleForCategory(DhsImageCategory category) {
|
private ToggleButton getToggleForCategory(DhsImageCategory category) {
|
||||||
switch (category) {
|
switch (category) {
|
||||||
case ZERO:
|
case ZERO:
|
||||||
@ -383,20 +387,21 @@ public class GroupPane extends BorderPane {
|
|||||||
"GroupPane.catContainerLabel.displayText=Categorize Selected File:",
|
"GroupPane.catContainerLabel.displayText=Categorize Selected File:",
|
||||||
"GroupPane.catHeadingLabel.displayText=Category:"})
|
"GroupPane.catHeadingLabel.displayText=Category:"})
|
||||||
void initialize() {
|
void initialize() {
|
||||||
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" 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 'SlideShowView.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 'SlideShowView.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 'SlideShowView.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 'SlideShowView.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 'SlideShowView.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 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 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 'GroupHeader.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 'GroupHeader.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 'GroupHeader.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 'GroupHeader.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 'GroupHeader.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()) {
|
for (DhsImageCategory cat : DhsImageCategory.values()) {
|
||||||
ToggleButton toggleForCategory = getToggleForCategory(cat);
|
ToggleButton toggleForCategory = getToggleForCategory(cat);
|
||||||
toggleForCategory.setBorder(new Border(new BorderStroke(cat.getColor(), BorderStrokeStyle.SOLID, CORNER_RADII_2, BORDER_WIDTHS_2)));
|
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.cellHeightProperty().bind(cellSize);
|
||||||
gridView.cellWidthProperty().bind(cellSize);
|
gridView.cellWidthProperty().bind(cellSize);
|
||||||
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell());
|
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell());
|
||||||
|
|
||||||
BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected());
|
BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected());
|
||||||
catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
|
catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
|
||||||
tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
|
tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
|
||||||
|
|
||||||
|
TagSelectedFilesAction followUpSelectedAction = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); //NON-NLS
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
try {
|
tagSelectedSplitMenu.setText(followUpSelectedAction.getText());
|
||||||
TagSelectedFilesAction followUpSelectedACtion = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller);
|
tagSelectedSplitMenu.setGraphic(followUpSelectedAction.getGraphic());
|
||||||
tagSelectedSplitMenu.setText(followUpSelectedACtion.getText());
|
tagSelectedSplitMenu.setOnAction(followUpSelectedAction);
|
||||||
tagSelectedSplitMenu.setGraphic(followUpSelectedACtion.getGraphic());
|
|
||||||
tagSelectedSplitMenu.setOnAction(followUpSelectedACtion);
|
|
||||||
} catch (TskCoreException tskCoreException) {
|
|
||||||
LOGGER.log(Level.WARNING, "failed to load FollowUpTagName", tskCoreException); //NON-NLS
|
|
||||||
}
|
|
||||||
tagSelectedSplitMenu.showingProperty().addListener(showing -> {
|
tagSelectedSplitMenu.showingProperty().addListener(showing -> {
|
||||||
if (tagSelectedSplitMenu.isShowing()) {
|
if (tagSelectedSplitMenu.isShowing()) {
|
||||||
List<MenuItem> selTagMenues = Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
|
|
||||||
tagName -> GuiUtils.createAutoAssigningMenuItem(tagSelectedSplitMenu, new TagSelectedFilesAction(tagName, controller)));
|
ListenableFuture<List<MenuItem>> getTagsFuture = exec.submit(()
|
||||||
tagSelectedSplitMenu.getItems().setAll(selTagMenues);
|
-> 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);
|
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller);
|
||||||
|
|
||||||
catSelectedSplitMenu.setOnAction(cat5SelectedAction);
|
catSelectedSplitMenu.setOnAction(cat5SelectedAction);
|
||||||
|
|
||||||
catSelectedSplitMenu.setText(cat5SelectedAction.getText());
|
catSelectedSplitMenu.setText(cat5SelectedAction.getText());
|
||||||
catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic());
|
catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic());
|
||||||
catSelectedSplitMenu.showingProperty().addListener(showing -> {
|
catSelectedSplitMenu.showingProperty().addListener(showing -> {
|
||||||
@ -456,12 +470,12 @@ public class GroupPane extends BorderPane {
|
|||||||
catSelectedSplitMenu.getItems().setAll(categoryMenues);
|
catSelectedSplitMenu.getItems().setAll(categoryMenues);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
slideShowToggle.getStyleClass().remove("radio-button");
|
slideShowToggle.getStyleClass().remove("radio-button");
|
||||||
slideShowToggle.getStyleClass().add("toggle-button");
|
slideShowToggle.getStyleClass().add("toggle-button");
|
||||||
tileToggle.getStyleClass().remove("radio-button");
|
tileToggle.getStyleClass().remove("radio-button");
|
||||||
tileToggle.getStyleClass().add("toggle-button");
|
tileToggle.getStyleClass().add("toggle-button");
|
||||||
|
|
||||||
bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText());
|
bottomLabel.setText(Bundle.GroupPane_bottomLabel_displayText());
|
||||||
headerLabel.setText(Bundle.GroupPane_hederLabel_displayText());
|
headerLabel.setText(Bundle.GroupPane_hederLabel_displayText());
|
||||||
catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText());
|
catContainerLabel.setText(Bundle.GroupPane_catContainerLabel_displayText());
|
||||||
@ -481,12 +495,12 @@ public class GroupPane extends BorderPane {
|
|||||||
//listen to toggles and update view state
|
//listen to toggles and update view state
|
||||||
slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get()));
|
slideShowToggle.setOnAction(onAction -> activateSlideShowViewer(selectionModel.lastSelectedProperty().get()));
|
||||||
tileToggle.setOnAction(onAction -> activateTileViewer());
|
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);
|
addEventFilter(KeyEvent.KEY_PRESSED, tileKeyboardNavigationHandler);
|
||||||
gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler());
|
gridView.addEventHandler(MouseEvent.MOUSE_CLICKED, new MouseHandler());
|
||||||
|
|
||||||
ActionUtils.configureButton(undoAction, undoButton);
|
ActionUtils.configureButton(undoAction, undoButton);
|
||||||
ActionUtils.configureButton(redoAction, redoButton);
|
ActionUtils.configureButton(redoAction, redoButton);
|
||||||
ActionUtils.configureButton(forwardAction, forwardButton);
|
ActionUtils.configureButton(forwardAction, forwardButton);
|
||||||
@ -502,7 +516,7 @@ public class GroupPane extends BorderPane {
|
|||||||
nextButton.setEffect(null);
|
nextButton.setEffect(null);
|
||||||
onAction.handle(actionEvent);
|
onAction.handle(actionEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
nextGroupAction.disabledProperty().addListener((Observable observable) -> {
|
nextGroupAction.disabledProperty().addListener((Observable observable) -> {
|
||||||
boolean newValue = nextGroupAction.isDisabled();
|
boolean newValue = nextGroupAction.isDisabled();
|
||||||
nextButton.setEffect(newValue ? null : DROP_SHADOW);
|
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
|
//listen to tile selection and make sure it is visible in scroll area
|
||||||
selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> {
|
selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> {
|
||||||
if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW
|
if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW
|
||||||
&& slideShowPane != null) {
|
&& slideShowPane != null) {
|
||||||
slideShowPane.setFile(newFileId);
|
slideShowPane.setFile(newFileId);
|
||||||
} else {
|
} else {
|
||||||
scrollToFileID(newFileId);
|
scrollToFileID(newFileId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setViewState(controller.viewState().get());
|
setViewState(controller.viewStateProperty().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: make sure we are testing complete visability not just bounds intersection
|
//TODO: make sure we are testing complete visability not just bounds intersection
|
||||||
@ -532,16 +556,16 @@ public class GroupPane extends BorderPane {
|
|||||||
if (newFileID == null) {
|
if (newFileID == null) {
|
||||||
return; //scrolling to no file doesn't make sense, so abort.
|
return; //scrolling to no file doesn't make sense, so abort.
|
||||||
}
|
}
|
||||||
|
|
||||||
final ObservableList<Long> fileIds = gridView.getItems();
|
final ObservableList<Long> fileIds = gridView.getItems();
|
||||||
|
|
||||||
int selectedIndex = fileIds.indexOf(newFileID);
|
int selectedIndex = fileIds.indexOf(newFileID);
|
||||||
if (selectedIndex == -1) {
|
if (selectedIndex == -1) {
|
||||||
//somehow we got passed a file id that isn't in the curent group.
|
//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.
|
//this should never happen, but if it does everything is going to fail, so abort.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getScrollBar().ifPresent(scrollBar -> {
|
getScrollBar().ifPresent(scrollBar -> {
|
||||||
DrawableCell cell = cellMap.get(newFileID);
|
DrawableCell cell = cellMap.get(newFileID);
|
||||||
|
|
||||||
@ -568,14 +592,14 @@ public class GroupPane extends BorderPane {
|
|||||||
}
|
}
|
||||||
cell = cellMap.get(newFileID);
|
cell = cellMap.get(newFileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal());
|
final Bounds gridViewBounds = gridView.localToScene(gridView.getBoundsInLocal());
|
||||||
Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal());
|
Bounds tileBounds = cell.localToScene(cell.getBoundsInLocal());
|
||||||
|
|
||||||
//while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates
|
//while the cell is not within the visisble bounds of the gridview, scroll based on screen coordinates
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) {
|
while (gridViewBounds.contains(tileBounds) == false && (i++ < 100)) {
|
||||||
|
|
||||||
if (tileBounds.getMinY() < gridViewBounds.getMinY()) {
|
if (tileBounds.getMinY() < gridViewBounds.getMinY()) {
|
||||||
scrollBar.decrement();
|
scrollBar.decrement();
|
||||||
} else if (tileBounds.getMaxY() > gridViewBounds.getMaxY()) {
|
} 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
|
* assigns a grouping for this pane to represent and initializes grouping
|
||||||
* specific properties and listeners
|
* specific properties and listeners
|
||||||
*
|
*
|
||||||
* @param grouping the new grouping assigned to this group
|
* @param newViewState
|
||||||
*/
|
*/
|
||||||
void setViewState(GroupViewState viewState) {
|
void setViewState(GroupViewState newViewState) {
|
||||||
|
|
||||||
if (isNull(viewState) || isNull(viewState.getGroup())) {
|
if (isNull(newViewState) || isNull(newViewState.getGroup().orElse(null))) {
|
||||||
if (nonNull(getGroup())) {
|
if (nonNull(getGroup())) {
|
||||||
getGroup().getFileIDs().removeListener(filesSyncListener);
|
getGroup().getFileIDs().removeListener(filesSyncListener);
|
||||||
}
|
}
|
||||||
this.grouping.set(null);
|
this.grouping.set(null);
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
gridView.getItems().setAll(Collections.emptyList());
|
gridView.getItems().setAll(Collections.emptyList());
|
||||||
setCenter(null);
|
setCenter(null);
|
||||||
@ -611,40 +635,37 @@ public class GroupPane extends BorderPane {
|
|||||||
cellMap.clear();
|
cellMap.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (getGroup() != viewState.getGroup()) {
|
if (nonNull(getGroup()) && getGroup() != newViewState.getGroup().get()) {
|
||||||
if (nonNull(getGroup())) {
|
getGroup().getFileIDs().removeListener(filesSyncListener);
|
||||||
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));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
private void resetScrollBar() {
|
private void resetScrollBar() {
|
||||||
getScrollBar().ifPresent((scrollBar) -> {
|
getScrollBar().ifPresent(scrollBar -> scrollBar.setValue(0));
|
||||||
scrollBar.setValue(0);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadType.JFX)
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
private Optional<ScrollBar> getScrollBar() {
|
private Optional<ScrollBar> getScrollBar() {
|
||||||
if (gridView == null || gridView.getSkin() == null) {
|
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
|
return Optional.ofNullable((ScrollBar) gridView.getSkin().getNode().lookup(".scroll-bar")); //NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
void makeSelection(Boolean shiftDown, Long newFileID) {
|
void makeSelection(Boolean shiftDown, Long newFileID) {
|
||||||
|
|
||||||
if (shiftDown) {
|
if (shiftDown) {
|
||||||
//TODO: do more hear to implement slicker multiselect
|
//TODO: do more hear to implement slicker multiselect
|
||||||
int endIndex = grouping.get().getFileIDs().indexOf(newFileID);
|
int endIndex = grouping.get().getFileIDs().indexOf(newFileID);
|
||||||
int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt();
|
int startIndex = IntStream.of(grouping.get().getFileIDs().size(), selectionAnchorIndex, endIndex).min().getAsInt();
|
||||||
endIndex = IntStream.of(0, selectionAnchorIndex, endIndex).max().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);
|
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.clearAndSelectAll(subList.toArray(new Long[subList.size()]));
|
||||||
selectionModel.select(newFileID);
|
selectionModel.select(newFileID);
|
||||||
} else {
|
} else {
|
||||||
selectionAnchorIndex = null;
|
selectionAnchorIndex = null;
|
||||||
selectionModel.clearAndSelect(newFileID);
|
selectionModel.clearAndSelect(newFileID);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DrawableCell extends GridCell<Long> {
|
private class DrawableCell extends GridCell<Long> {
|
||||||
|
|
||||||
private final DrawableTile tile = new DrawableTile(GroupPane.this, controller);
|
private final DrawableTile tile = new DrawableTile(GroupPane.this, controller);
|
||||||
|
|
||||||
DrawableCell() {
|
DrawableCell() {
|
||||||
itemProperty().addListener((ObservableValue<? extends Long> observable, Long oldValue, Long newValue) -> {
|
itemProperty().addListener((ObservableValue<? extends Long> observable, Long oldValue, Long newValue) -> {
|
||||||
if (oldValue != null) {
|
if (oldValue != null) {
|
||||||
@ -689,19 +711,18 @@ public class GroupPane extends BorderPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cellMap.put(newValue, DrawableCell.this);
|
cellMap.put(newValue, DrawableCell.this);
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setGraphic(tile);
|
setGraphic(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateItem(Long item, boolean empty) {
|
protected void updateItem(Long item, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
tile.setFile(item);
|
tile.setFile(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetItem() {
|
void resetItem() {
|
||||||
tile.setFile(null);
|
tile.setFile(null);
|
||||||
}
|
}
|
||||||
@ -712,10 +733,10 @@ public class GroupPane extends BorderPane {
|
|||||||
* arrows)
|
* arrows)
|
||||||
*/
|
*/
|
||||||
private class KeyboardHandler implements EventHandler<KeyEvent> {
|
private class KeyboardHandler implements EventHandler<KeyEvent> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(KeyEvent t) {
|
public void handle(KeyEvent t) {
|
||||||
|
|
||||||
if (t.getEventType() == KeyEvent.KEY_PRESSED) {
|
if (t.getEventType() == KeyEvent.KEY_PRESSED) {
|
||||||
switch (t.getCode()) {
|
switch (t.getCode()) {
|
||||||
case SHIFT:
|
case SHIFT:
|
||||||
@ -758,7 +779,7 @@ public class GroupPane extends BorderPane {
|
|||||||
t.consume();
|
t.consume();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) {
|
if (groupViewMode.get() == GroupViewMode.TILE && categoryKeyCodes.contains(t.getCode()) && t.isAltDown()) {
|
||||||
selectAllFiles();
|
selectAllFiles();
|
||||||
t.consume();
|
t.consume();
|
||||||
@ -772,7 +793,7 @@ public class GroupPane extends BorderPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DhsImageCategory keyCodeToCat(KeyCode t) {
|
private DhsImageCategory keyCodeToCat(KeyCode t) {
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
switch (t) {
|
switch (t) {
|
||||||
@ -798,16 +819,16 @@ public class GroupPane extends BorderPane {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleArrows(KeyEvent t) {
|
private void handleArrows(KeyEvent t) {
|
||||||
Long lastSelectFileId = selectionModel.lastSelectedProperty().get();
|
Long lastSelectFileId = selectionModel.lastSelectedProperty().get();
|
||||||
|
|
||||||
int lastSelectedIndex = lastSelectFileId != null
|
int lastSelectedIndex = lastSelectFileId != null
|
||||||
? grouping.get().getFileIDs().indexOf(lastSelectFileId)
|
? grouping.get().getFileIDs().indexOf(lastSelectFileId)
|
||||||
: Optional.ofNullable(selectionAnchorIndex).orElse(0);
|
: Optional.ofNullable(selectionAnchorIndex).orElse(0);
|
||||||
|
|
||||||
final int columns = Math.max((int) Math.floor((gridView.getWidth() - 18) / (gridView.getCellWidth() + gridView.getHorizontalCellSpacing() * 2)), 1);
|
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);
|
final Map<KeyCode, Integer> tileIndexMap = ImmutableMap.of(UP, -columns, DOWN, columns, LEFT, -1, RIGHT, 1);
|
||||||
|
|
||||||
// implement proper keyboard based multiselect
|
// implement proper keyboard based multiselect
|
||||||
@ -826,41 +847,44 @@ public class GroupPane extends BorderPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MouseHandler implements EventHandler<MouseEvent> {
|
private class MouseHandler implements EventHandler<MouseEvent> {
|
||||||
|
|
||||||
private ContextMenu buildContextMenu() {
|
private ContextMenu buildContextMenu() {
|
||||||
ArrayList<MenuItem> menuItems = new ArrayList<>();
|
ArrayList<MenuItem> menuItems = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
menuItems.add(CategorizeAction.getCategoriesMenu(controller));
|
menuItems.add(CategorizeAction.getCategoriesMenu(controller));
|
||||||
menuItems.add(AddTagAction.getTagMenu(controller));
|
try {
|
||||||
|
menuItems.add(AddTagAction.getTagMenu(controller));
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
Collection<? extends ContextMenuActionsProvider> menuProviders = Lookup.getDefault().lookupAll(ContextMenuActionsProvider.class);
|
logger.log(Level.SEVERE, "Error building tagging context menu.", ex);
|
||||||
|
|
||||||
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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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());
|
final MenuItem extractMenuItem = new MenuItem(Bundle.GroupPane_gridViewContextMenuItem_extractFiles());
|
||||||
extractMenuItem.setOnAction((ActionEvent t) -> {
|
|
||||||
|
extractMenuItem.setOnAction(actionEvent -> {
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
|
TopComponent etc = WindowManager.getDefault().findTopComponent(ImageGalleryTopComponent.PREFERRED_ID);
|
||||||
ExtractAction.getInstance().actionPerformed(new java.awt.event.ActionEvent(etc, 0, null));
|
ExtractAction.getInstance().actionPerformed(new java.awt.event.ActionEvent(etc, 0, null));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
menuItems.add(extractMenuItem);
|
menuItems.add(extractMenuItem);
|
||||||
|
|
||||||
ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{}));
|
ContextMenu contextMenu = new ContextMenu(menuItems.toArray(new MenuItem[]{}));
|
||||||
contextMenu.setAutoHide(true);
|
|
||||||
|
contextMenu.setAutoHide(
|
||||||
|
true);
|
||||||
return contextMenu;
|
return contextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(MouseEvent t) {
|
public void handle(MouseEvent t) {
|
||||||
switch (t.getButton()) {
|
switch (t.getButton()) {
|
||||||
@ -877,11 +901,11 @@ public class GroupPane extends BorderPane {
|
|||||||
if (t.getClickCount() == 1) {
|
if (t.getClickCount() == 1) {
|
||||||
selectAllFiles();
|
selectAllFiles();
|
||||||
}
|
}
|
||||||
if (selectionModel.getSelected().isEmpty() == false) {
|
if (isNotEmpty(selectionModel.getSelected())) {
|
||||||
if (contextMenu == null) {
|
if (contextMenu == null) {
|
||||||
contextMenu = buildContextMenu();
|
contextMenu = buildContextMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
contextMenu.hide();
|
contextMenu.hide();
|
||||||
contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY());
|
contextMenu.show(GroupPane.this, t.getScreenX(), t.getScreenY());
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-15 Basis Technology Corp.
|
* Copyright 2013-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -55,9 +55,9 @@ import org.openide.util.NbBundle;
|
|||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
||||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
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.CategoryManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||||
@ -67,13 +67,13 @@ import org.sleuthkit.datamodel.TagName;
|
|||||||
* Shows details of the selected file.
|
* Shows details of the selected file.
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({"MetaDataPane.tableView.placeholder=Select a file to show its details here.",
|
@NbBundle.Messages({"MetaDataPane.tableView.placeholder=Select a file to show its details here.",
|
||||||
"MetaDataPane.copyMenuItem.text=Copy",
|
"MetaDataPane.copyMenuItem.text=Copy",
|
||||||
"MetaDataPane.titledPane.displayName=Details",
|
"MetaDataPane.titledPane.displayName=Details",
|
||||||
"MetaDataPane.attributeColumn.headingName=Attribute",
|
"MetaDataPane.attributeColumn.headingName=Attribute",
|
||||||
"MetaDataPane.valueColumn.headingName=Value"})
|
"MetaDataPane.valueColumn.headingName=Value"})
|
||||||
public class MetaDataPane extends DrawableUIBase {
|
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);
|
private static final KeyCodeCombination COPY_KEY_COMBINATION = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_DOWN);
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ public class MetaDataPane extends DrawableUIBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
Task<Image> newReadImageTask(DrawableFile file) {
|
Task<Image> newReadImageTask(DrawableFile file) {
|
||||||
return file.getThumbnailTask();
|
return getController().getThumbsCache().getThumbnailTask(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateAttributesTable() {
|
public void updateAttributesTable() {
|
||||||
|
@ -173,7 +173,7 @@ class GroupCellFactory {
|
|||||||
private final InvalidationListener groupListener = new GroupListener<>(this);
|
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
|
* group when a new group is assigned to this Cell
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2016 Basis Technology Corp.
|
* Copyright 2016-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -38,6 +38,7 @@ import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
|||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
|
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 (
|
* 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);
|
groupTree.setShowRoot(false);
|
||||||
|
|
||||||
getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
|
getGroupManager().getAnalyzedGroups().addListener((ListChangeListener.Change<? extends DrawableGroup> change) -> {
|
||||||
|
GroupViewState oldState = getController().getViewState();
|
||||||
|
|
||||||
while (change.next()) {
|
while (change.next()) {
|
||||||
change.getAddedSubList().stream().forEach(this::insertGroup);
|
change.getAddedSubList().stream().forEach(this::insertGroup);
|
||||||
change.getRemoved().stream().forEach(this::removeFromTree);
|
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()) {
|
getGroupManager().getAnalyzedGroups().forEach(this::insertGroup);
|
||||||
insertGroup(g);
|
Platform.runLater(this::sortGroups);
|
||||||
}
|
|
||||||
sortGroups();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,12 +108,10 @@ final public class GroupTree extends NavPanel<TreeItem<GroupTreeNode>> {
|
|||||||
|
|
||||||
if (treeItemForGroup != null) {
|
if (treeItemForGroup != null) {
|
||||||
groupTree.getSelectionModel().select(treeItemForGroup);
|
groupTree.getSelectionModel().select(treeItemForGroup);
|
||||||
Platform.runLater(() -> {
|
int row = groupTree.getRow(treeItemForGroup);
|
||||||
int row = groupTree.getRow(treeItemForGroup);
|
if (row != -1) {
|
||||||
if (row != -1) {
|
groupTree.scrollTo(row - 2); //put newly selected row 3 from the top
|
||||||
groupTree.scrollTo(row - 2); //put newly selected row 3 from the top
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-16 Basis Technology Corp.
|
* Copyright 2013-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
|
package org.sleuthkit.autopsy.imagegallery.gui.navpanel;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
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
|
* A node in the nav/hash tree. Manages inserts and removals. Has parents and
|
||||||
* children. Does not have graphical properties these are configured in
|
* children. Does not have graphical properties these are configured in
|
||||||
* {@link GroupTreeCell}. Each GroupTreeItem has a TreeNode which has a path
|
* GroupTreeCell. Each GroupTreeItem has a TreeNode which has a path segment and
|
||||||
* segment and may or may not have a group
|
* may or may not have a group
|
||||||
*/
|
*/
|
||||||
class GroupTreeItem extends TreeItem<GroupTreeNode> {
|
class GroupTreeItem extends TreeItem<GroupTreeNode> {
|
||||||
|
|
||||||
@ -131,7 +130,6 @@ class GroupTreeItem extends TreeItem<GroupTreeNode> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
synchronized GroupTreeItem getTreeItemForPath(List<String> path) {
|
synchronized GroupTreeItem getTreeItemForPath(List<String> path) {
|
||||||
|
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
// end of recursion
|
// end of recursion
|
||||||
return this;
|
return this;
|
||||||
@ -154,9 +152,7 @@ class GroupTreeItem extends TreeItem<GroupTreeNode> {
|
|||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
parent.childMap.remove(getValue().getPath());
|
parent.childMap.remove(getValue().getPath());
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> parent.getChildren().remove(this));
|
||||||
parent.getChildren().removeAll(Collections.singleton(GroupTreeItem.this));
|
|
||||||
});
|
|
||||||
|
|
||||||
if (parent.childMap.isEmpty()) {
|
if (parent.childMap.isEmpty()) {
|
||||||
parent.removeFromParent();
|
parent.removeFromParent();
|
||||||
@ -173,8 +169,6 @@ class GroupTreeItem extends TreeItem<GroupTreeNode> {
|
|||||||
synchronized void resortChildren(Comparator<DrawableGroup> newComp) {
|
synchronized void resortChildren(Comparator<DrawableGroup> newComp) {
|
||||||
this.comp = newComp;
|
this.comp = newComp;
|
||||||
getChildren().sort(Comparator.comparing(treeItem -> treeItem.getValue().getGroup(), Comparator.nullsLast(comp)));
|
getChildren().sort(Comparator.comparing(treeItem -> treeItem.getValue().getGroup(), Comparator.nullsLast(comp)));
|
||||||
for (GroupTreeItem ti : childMap.values()) {
|
childMap.values().forEach(treeItem -> treeItem.resortChildren(comp));
|
||||||
ti.resortChildren(comp);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2016 Basis Technology Corp.
|
* Copyright 2016-18 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -22,6 +22,7 @@ import com.google.common.eventbus.Subscribe;
|
|||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.SelectionModel;
|
import javafx.scene.control.SelectionModel;
|
||||||
@ -74,9 +75,9 @@ abstract class NavPanel<X> extends Tab {
|
|||||||
|
|
||||||
sortChooser = new SortChooser<>(GroupComparators.getValues());
|
sortChooser = new SortChooser<>(GroupComparators.getValues());
|
||||||
sortChooser.setComparator(getDefaultComparator());
|
sortChooser.setComparator(getDefaultComparator());
|
||||||
sortChooser.sortOrderProperty().addListener(order -> sortGroups());
|
sortChooser.sortOrderProperty().addListener(order -> NavPanel.this.sortGroups());
|
||||||
sortChooser.comparatorProperty().addListener((observable, oldComparator, newComparator) -> {
|
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
|
//only need to listen to changes in category if we are sorting by/ showing the uncategorized count
|
||||||
if (newComparator == GroupComparators.UNCATEGORIZED_COUNT) {
|
if (newComparator == GroupComparators.UNCATEGORIZED_COUNT) {
|
||||||
categoryManager.registerListener(NavPanel.this);
|
categoryManager.registerListener(NavPanel.this);
|
||||||
@ -90,13 +91,20 @@ abstract class NavPanel<X> extends Tab {
|
|||||||
toolBar.getItems().add(sortChooser);
|
toolBar.getItems().add(sortChooser);
|
||||||
|
|
||||||
//keep selection in sync with controller
|
//keep selection in sync with controller
|
||||||
controller.viewState().addListener(observable -> {
|
controller.viewStateProperty().addListener(observable -> {
|
||||||
Optional.ofNullable(controller.viewState().get())
|
Platform.runLater(()
|
||||||
.map(GroupViewState::getGroup)
|
-> Optional.ofNullable(controller.getViewState())
|
||||||
.ifPresent(this::setFocusedGroup);
|
.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
|
@Subscribe
|
||||||
public void handleCategoryChange(CategoryManager.CategoryChangeEvent event) {
|
public void handleCategoryChange(CategoryManager.CategoryChangeEvent event) {
|
||||||
sortGroups();
|
Platform.runLater(this::sortGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,27 +129,24 @@ abstract class NavPanel<X> extends Tab {
|
|||||||
: comparator.reversed();
|
: 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
|
* Sort the groups in this view according to the currently selected sorting
|
||||||
* options. Attempts to maintain selection.
|
* options. Attempts to maintain selection.
|
||||||
*/
|
*/
|
||||||
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
|
||||||
void sortGroups() {
|
void sortGroups() {
|
||||||
|
sortGroups(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sortGroups(boolean preserveSelection) {
|
||||||
|
|
||||||
X selectedItem = getSelectionModel().getSelectedItem();
|
X selectedItem = getSelectionModel().getSelectedItem();
|
||||||
applyGroupComparator();
|
applyGroupComparator();
|
||||||
Optional.ofNullable(selectedItem)
|
if (preserveSelection) {
|
||||||
.map(getDataItemMapper())
|
Optional.ofNullable(selectedItem)
|
||||||
.ifPresent(this::setFocusedGroup);
|
.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;
|
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.Callable;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class TaskUtils {
|
public final class TaskUtils {
|
||||||
|
|
||||||
|
private TaskUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> Task<T> taskFrom(Callable<T> callable) {
|
public static <T> Task<T> taskFrom(Callable<T> callable) {
|
||||||
return new Task<T>() {
|
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
|
#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
|
LBL_splash_window_title=Starting Autopsy
|
||||||
SPLASH_HEIGHT=314
|
SPLASH_HEIGHT=314
|
||||||
SPLASH_WIDTH=538
|
SPLASH_WIDTH=538
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#Updated by build script
|
#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=Autopsy 4.8.0
|
||||||
CTL_MainWindow_Title_No_Project=Autopsy 4.8.0
|
CTL_MainWindow_Title_No_Project=Autopsy 4.8.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user