mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-15 09:17:42 +00:00
Merge branch 'develop' of github.com:sleuthkit/autopsy into develop
This commit is contained in:
commit
f1b3dd5a94
@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.imagegallery;
|
|||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
@ -58,8 +57,10 @@ import org.sleuthkit.autopsy.coreutils.History;
|
|||||||
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.datamodel.Category;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
||||||
|
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.DrawableFile;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.datamodel.HashSetManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.grouping.GroupManager;
|
import org.sleuthkit.autopsy.imagegallery.grouping.GroupManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.grouping.GroupViewState;
|
import org.sleuthkit.autopsy.imagegallery.grouping.GroupViewState;
|
||||||
import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog;
|
import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog;
|
||||||
@ -124,6 +125,8 @@ public final class ImageGalleryController {
|
|||||||
private DrawableDB db;
|
private DrawableDB db;
|
||||||
|
|
||||||
private final GroupManager groupManager = new GroupManager(this);
|
private final GroupManager groupManager = new GroupManager(this);
|
||||||
|
private final HashSetManager hashSetManager = new HashSetManager();
|
||||||
|
private final CategoryManager categoryManager = new CategoryManager();
|
||||||
|
|
||||||
private StackPane fullUIStackPane;
|
private StackPane fullUIStackPane;
|
||||||
|
|
||||||
@ -341,7 +344,7 @@ public final class ImageGalleryController {
|
|||||||
* @param theNewCase the case to configure the controller for
|
* @param theNewCase the case to configure the controller for
|
||||||
*/
|
*/
|
||||||
public synchronized void setCase(Case theNewCase) {
|
public synchronized void setCase(Case theNewCase) {
|
||||||
this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), this);
|
this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), getSleuthKitCase());
|
||||||
|
|
||||||
setListeningEnabled(ImageGalleryModule.isEnabledforCase(theNewCase));
|
setListeningEnabled(ImageGalleryModule.isEnabledforCase(theNewCase));
|
||||||
setStale(ImageGalleryModule.isDrawableDBStale(theNewCase));
|
setStale(ImageGalleryModule.isDrawableDBStale(theNewCase));
|
||||||
@ -351,8 +354,9 @@ public final class ImageGalleryController {
|
|||||||
restartWorker();
|
restartWorker();
|
||||||
historyManager.clear();
|
historyManager.clear();
|
||||||
groupManager.setDB(db);
|
groupManager.setDB(db);
|
||||||
db.initializeImageList();
|
hashSetManager.setDb(db);
|
||||||
SummaryTablePane.getDefault().handleCategoryChanged(Collections.emptyList());
|
categoryManager.setDb(db);
|
||||||
|
SummaryTablePane.getDefault().refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -381,7 +385,7 @@ public final class ImageGalleryController {
|
|||||||
*
|
*
|
||||||
* @param innerTask
|
* @param innerTask
|
||||||
*/
|
*/
|
||||||
public final void queueDBWorkerTask(InnerTask innerTask) {
|
public void queueDBWorkerTask(InnerTask innerTask) {
|
||||||
|
|
||||||
// @@@ We could make a lock for the worker thread
|
// @@@ We could make a lock for the worker thread
|
||||||
if (dbWorkerThread == null) {
|
if (dbWorkerThread == null) {
|
||||||
@ -400,7 +404,7 @@ public final class ImageGalleryController {
|
|||||||
Platform.runLater(this::checkForGroups);
|
Platform.runLater(this::checkForGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final ReadOnlyIntegerProperty getFileUpdateQueueSizeProperty() {
|
public ReadOnlyIntegerProperty getFileUpdateQueueSizeProperty() {
|
||||||
return queueSizeProperty.getReadOnlyProperty();
|
return queueSizeProperty.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,6 +480,14 @@ public final class ImageGalleryController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HashSetManager getHashSetManager() {
|
||||||
|
return hashSetManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CategoryManager getCategoryManager() {
|
||||||
|
return categoryManager;
|
||||||
|
}
|
||||||
|
|
||||||
// @@@ REVIEW IF THIS SHOLD BE STATIC...
|
// @@@ REVIEW IF THIS SHOLD BE STATIC...
|
||||||
//TODO: concept seems like the controller deal with how much work to do at a given time
|
//TODO: concept seems like the controller deal with how much work to do at a given time
|
||||||
// @@@ review this class for synchronization issues (i.e. reset and cancel being called, add, etc.)
|
// @@@ review this class for synchronization issues (i.e. reset and cancel being called, add, etc.)
|
||||||
@ -640,7 +652,7 @@ public final class ImageGalleryController {
|
|||||||
try {
|
try {
|
||||||
DrawableFile<?> drawableFile = DrawableFile.create(getFile(), true, db.isVideoFile(getFile()));
|
DrawableFile<?> drawableFile = DrawableFile.create(getFile(), true, db.isVideoFile(getFile()));
|
||||||
db.updateFile(drawableFile);
|
db.updateFile(drawableFile);
|
||||||
} catch (NullPointerException | TskCoreException ex) {
|
} catch (NullPointerException ex) {
|
||||||
// This is one of the places where we get many errors if the case is closed during processing.
|
// This is one of the places where we get many errors if the case is closed during processing.
|
||||||
// We don't want to print out a ton of exceptions if this is the case.
|
// We don't want to print out a ton of exceptions if this is the case.
|
||||||
if (Case.isCaseOpen()) {
|
if (Case.isCaseOpen()) {
|
||||||
|
@ -162,6 +162,8 @@ public class ImageGalleryModule {
|
|||||||
*
|
*
|
||||||
* @return true if the given file has a supported video mime type or
|
* @return true if the given file has a supported video mime type or
|
||||||
* extension, else false
|
* extension, else false
|
||||||
|
*
|
||||||
|
* //TODO: convert this to use the new FileTypeDetector?
|
||||||
*/
|
*/
|
||||||
public static boolean isVideoFile(AbstractFile file) {
|
public static boolean isVideoFile(AbstractFile file) {
|
||||||
try {
|
try {
|
||||||
|
@ -26,6 +26,7 @@ import java.util.logging.Level;
|
|||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.scene.control.Menu;
|
import javafx.scene.control.Menu;
|
||||||
import javafx.scene.control.MenuItem;
|
import javafx.scene.control.MenuItem;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.KeyCodeCombination;
|
import javafx.scene.input.KeyCodeCombination;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
@ -101,7 +102,7 @@ public class CategorizeAction extends AddTagAction {
|
|||||||
final CategorizeAction categorizeAction = new CategorizeAction();
|
final CategorizeAction categorizeAction = new CategorizeAction();
|
||||||
categorizeAction.addTag(cat.getTagName(), NO_COMMENT);
|
categorizeAction.addTag(cat.getTagName(), NO_COMMENT);
|
||||||
});
|
});
|
||||||
categoryItem.setAccelerator(new KeyCodeCombination(cat.getHotKeycode()));
|
categoryItem.setAccelerator(new KeyCodeCombination(KeyCode.getKeyCode(Integer.toString(cat.getCategoryNumber()))));
|
||||||
getItems().add(categoryItem);
|
getItems().add(categoryItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,11 +136,11 @@ public class CategorizeAction extends AddTagAction {
|
|||||||
//TODO: abandon using tags for categories and instead add a new column to DrawableDB
|
//TODO: abandon using tags for categories and instead add a new column to DrawableDB
|
||||||
if (ct.getName().getDisplayName().startsWith(Category.CATEGORY_PREFIX)) {
|
if (ct.getName().getDisplayName().startsWith(Category.CATEGORY_PREFIX)) {
|
||||||
Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(ct); //tsk db
|
Case.getCurrentCase().getServices().getTagsManager().deleteContentTag(ct); //tsk db
|
||||||
controller.getDatabase().decrementCategoryCount(Category.fromDisplayName(ct.getName().getDisplayName())); //memory/drawable db
|
controller.getCategoryManager().decrementCategoryCount(Category.fromDisplayName(ct.getName().getDisplayName())); //memory/drawable db
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
controller.getDatabase().incrementCategoryCount(Category.fromDisplayName(tagName.getDisplayName())); //memory/drawable db
|
controller.getCategoryManager().incrementCategoryCount(Category.fromDisplayName(tagName.getDisplayName())); //memory/drawable db
|
||||||
if (tagName != Category.ZERO.getTagName()) { // no tags for cat-0
|
if (tagName != Category.ZERO.getTagName()) { // no tags for cat-0
|
||||||
Case.getCurrentCase().getServices().getTagsManager().addContentTag(file, tagName, comment); //tsk db
|
Case.getCurrentCase().getServices().getTagsManager().addContentTag(file, tagName, comment); //tsk db
|
||||||
}
|
}
|
||||||
|
@ -18,30 +18,19 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.datamodel;
|
package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javafx.event.ActionEvent;
|
import java.util.stream.Collectors;
|
||||||
import javafx.event.EventHandler;
|
import java.util.stream.Stream;
|
||||||
import javafx.scene.control.MenuItem;
|
|
||||||
import javafx.scene.control.SplitMenuButton;
|
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javafx.scene.input.KeyCode;
|
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.imagegallery.TagUtils;
|
import org.sleuthkit.autopsy.imagegallery.TagUtils;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
|
|
||||||
import org.sleuthkit.datamodel.TagName;
|
import org.sleuthkit.datamodel.TagName;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Enum to represent the six categories in the DHs image categorization scheme.
|
||||||
*/
|
*/
|
||||||
public enum Category implements Comparable<Category> {
|
public enum Category implements Comparable<Category> {
|
||||||
|
|
||||||
@ -52,59 +41,47 @@ public enum Category implements Comparable<Category> {
|
|||||||
FOUR(Color.BISQUE, 4, "CAT-4, Exemplar/Comparison (Internal Use Only)"),
|
FOUR(Color.BISQUE, 4, "CAT-4, Exemplar/Comparison (Internal Use Only)"),
|
||||||
FIVE(Color.GREEN, 5, "CAT-5, Non-pertinent");
|
FIVE(Color.GREEN, 5, "CAT-5, Non-pertinent");
|
||||||
|
|
||||||
final static private Map<String, Category> nameMap = new HashMap<>();
|
/** map from displayName to enum value */
|
||||||
|
private static final Map<String, Category> nameMap
|
||||||
private static final List<Category> valuesList = Arrays.asList(values());
|
= Stream.of(values()).collect(Collectors.toMap(Category::getDisplayName,
|
||||||
|
Function.identity()));
|
||||||
static {
|
|
||||||
for (Category cat : values()) {
|
|
||||||
nameMap.put(cat.displayName, cat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GuardedBy("listeners")
|
|
||||||
private final static Set<CategoryListener> listeners = new HashSet<>();
|
|
||||||
|
|
||||||
public static void fireChange(Collection<Long> ids) {
|
|
||||||
Set<CategoryListener> listenersCopy = new HashSet<>();
|
|
||||||
synchronized (listeners) {
|
|
||||||
listenersCopy.addAll(listeners);
|
|
||||||
}
|
|
||||||
for (CategoryListener list : listenersCopy) {
|
|
||||||
list.handleCategoryChanged(ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void registerListener(CategoryListener aThis) {
|
|
||||||
synchronized (listeners) {
|
|
||||||
listeners.add(aThis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void unregisterListener(CategoryListener aThis) {
|
|
||||||
synchronized (listeners) {
|
|
||||||
listeners.remove(aThis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyCode getHotKeycode() {
|
|
||||||
return KeyCode.getKeyCode(Integer.toString(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final String CATEGORY_PREFIX = "CAT-";
|
public static final String CATEGORY_PREFIX = "CAT-";
|
||||||
|
|
||||||
private TagName tagName;
|
public static Category fromDisplayName(String displayName) {
|
||||||
|
return nameMap.get(displayName);
|
||||||
public static List<Category> valuesList() {
|
|
||||||
return valuesList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color color;
|
/**
|
||||||
|
* Use when closing a case to make sure everything is re-initialized in the
|
||||||
|
* next case.
|
||||||
|
*/
|
||||||
|
public static void clearTagNames() {
|
||||||
|
Category.ZERO.tagName = null;
|
||||||
|
Category.ONE.tagName = null;
|
||||||
|
Category.TWO.tagName = null;
|
||||||
|
Category.THREE.tagName = null;
|
||||||
|
Category.FOUR.tagName = null;
|
||||||
|
Category.FIVE.tagName = null;
|
||||||
|
}
|
||||||
|
|
||||||
private String displayName;
|
private TagName tagName;
|
||||||
|
|
||||||
private int id;
|
private final Color color;
|
||||||
|
|
||||||
|
private final String displayName;
|
||||||
|
|
||||||
|
private final int id;
|
||||||
|
|
||||||
|
private Category(Color color, int id, String name) {
|
||||||
|
this.color = color;
|
||||||
|
this.displayName = name;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCategoryNumber() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
public Color getColor() {
|
public Color getColor() {
|
||||||
return color;
|
return color;
|
||||||
@ -119,18 +96,12 @@ public enum Category implements Comparable<Category> {
|
|||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Category fromDisplayName(String displayName) {
|
/**
|
||||||
return nameMap.get(displayName);
|
* get the TagName used to store this Category in the main autopsy db.
|
||||||
}
|
*
|
||||||
|
* @return the TagName used for this Category
|
||||||
private Category(Color color, int id, String name) {
|
*/
|
||||||
this.color = color;
|
|
||||||
this.displayName = name;
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TagName getTagName() {
|
public TagName getTagName() {
|
||||||
|
|
||||||
if (tagName == null) {
|
if (tagName == null) {
|
||||||
try {
|
try {
|
||||||
tagName = TagUtils.getTagName(displayName);
|
tagName = TagUtils.getTagName(displayName);
|
||||||
@ -140,35 +111,4 @@ public enum Category implements Comparable<Category> {
|
|||||||
}
|
}
|
||||||
return tagName;
|
return tagName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MenuItem createSelCatMenuItem(final SplitMenuButton catSelectedMenuButton) {
|
|
||||||
final MenuItem menuItem = new MenuItem(this.getDisplayName(), new ImageView(DrawableAttribute.CATEGORY.getIcon()));
|
|
||||||
menuItem.setOnAction(new EventHandler<ActionEvent>() {
|
|
||||||
@Override
|
|
||||||
public void handle(ActionEvent t) {
|
|
||||||
new CategorizeAction().addTag(Category.this.getTagName(), "");
|
|
||||||
catSelectedMenuButton.setText(Category.this.getDisplayName());
|
|
||||||
catSelectedMenuButton.setOnAction(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return menuItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use when closing a case to make sure everything is re-initialized in the next case.
|
|
||||||
*/
|
|
||||||
public static void clearTagNames(){
|
|
||||||
Category.ZERO.tagName = null;
|
|
||||||
Category.ONE.tagName = null;
|
|
||||||
Category.TWO.tagName = null;
|
|
||||||
Category.THREE.tagName = null;
|
|
||||||
Category.FOUR.tagName = null;
|
|
||||||
Category.FIVE.tagName = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static interface CategoryListener {
|
|
||||||
|
|
||||||
public void handleCategoryChanged(Collection<Long> ids);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2015 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 java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event broadcast to various UI componenets when one or more files' category
|
||||||
|
* has been changed
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
public class CategoryChangeEvent {
|
||||||
|
|
||||||
|
private final Collection<Long> ids;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the fileIDs of the files whose categories have changed
|
||||||
|
*/
|
||||||
|
public Collection<Long> getIds() {
|
||||||
|
return Collections.unmodifiableCollection(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CategoryChangeEvent(Collection<Long> ids) {
|
||||||
|
this.ids = ids;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2015 Basis Technology Corp.
|
||||||
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.google.common.eventbus.EventBus;
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.atomic.LongAdder;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a cached view of the number of files per category, and fires
|
||||||
|
* {@link CategoryChangeEvent}s when files are categorized.
|
||||||
|
*
|
||||||
|
* To receive CategoryChangeEvents, a listener must register itself, and
|
||||||
|
* implement a method annotated with {@link Subscribe} that accepts one argument
|
||||||
|
* of type CategoryChangeEvent
|
||||||
|
*
|
||||||
|
* TODO: currently these two functions (cached counts and events) are separate
|
||||||
|
* although they are related. Can they be integrated more?
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class CategoryManager {
|
||||||
|
|
||||||
|
private static final java.util.logging.Logger LOGGER = Logger.getLogger(CategoryManager.class.getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the DrawableDB that backs the category counts cache. The counts are
|
||||||
|
* initialized from this, and the counting of CAT-0 is always delegated to
|
||||||
|
* this db.
|
||||||
|
*/
|
||||||
|
private DrawableDB db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to distribute {@link CategoryChangeEvent}s
|
||||||
|
*/
|
||||||
|
private final EventBus categoryEventBus = new EventBus("Category Event Bus");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For performance reasons, keep current category counts in memory. All of
|
||||||
|
* the count related methods go through this cache, which loads initial
|
||||||
|
* values from the database if needed.
|
||||||
|
*/
|
||||||
|
private final LoadingCache<Category, LongAdder> categoryCounts
|
||||||
|
= CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* assign a new db. the counts cache is invalidated and all subsequent db
|
||||||
|
* lookups go to the new db.
|
||||||
|
*
|
||||||
|
* Also clears the Category TagNames (should this happen here?)
|
||||||
|
*
|
||||||
|
* @param db
|
||||||
|
*/
|
||||||
|
public void setDb(DrawableDB db) {
|
||||||
|
this.db = db;
|
||||||
|
categoryCounts.invalidateAll();
|
||||||
|
Category.clearTagNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the number of file with the given {@link Category}
|
||||||
|
*
|
||||||
|
* @param cat get the number of files with Category = cat
|
||||||
|
*
|
||||||
|
* @return the long the number of files with the given Category
|
||||||
|
*/
|
||||||
|
public long getCategoryCount(Category cat) {
|
||||||
|
if (cat == Category.ZERO) {
|
||||||
|
// Keeping track of the uncategorized files is a bit tricky while ingest
|
||||||
|
// is going on, so always use the list of file IDs we already have along with the
|
||||||
|
// other category counts instead of trying to track it separately.
|
||||||
|
long allOtherCatCount = getCategoryCount(Category.ONE) + getCategoryCount(Category.TWO) + getCategoryCount(Category.THREE) + getCategoryCount(Category.FOUR) + getCategoryCount(Category.FIVE);
|
||||||
|
return db.getNumberOfImageFilesInList() - allOtherCatCount;
|
||||||
|
} else {
|
||||||
|
return categoryCounts.getUnchecked(cat).sum();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* increment the cached value for the number of files with the given
|
||||||
|
* {@link Category}
|
||||||
|
*
|
||||||
|
* @param cat the Category to increment
|
||||||
|
*/
|
||||||
|
public void incrementCategoryCount(Category cat) {
|
||||||
|
if (cat != Category.ZERO) {
|
||||||
|
categoryCounts.getUnchecked(cat).increment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* decrement the cached value for the number of files with the given
|
||||||
|
* {@link Category}
|
||||||
|
*
|
||||||
|
* @param cat the Category to decrement
|
||||||
|
*/
|
||||||
|
public void decrementCategoryCount(Category cat) {
|
||||||
|
if (cat != Category.ZERO) {
|
||||||
|
categoryCounts.getUnchecked(cat).decrement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper method that looks up the number of files with the given Category
|
||||||
|
* from the db and wraps it in a long adder to use in the cache
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param cat the Category to count
|
||||||
|
*
|
||||||
|
* @return a LongAdder whose value is set to the number of file with the
|
||||||
|
* given Category
|
||||||
|
*/
|
||||||
|
private LongAdder getCategoryCountHelper(Category cat) {
|
||||||
|
LongAdder longAdder = new LongAdder();
|
||||||
|
longAdder.decrement();
|
||||||
|
try {
|
||||||
|
longAdder.add(db.getCategoryCount(cat));
|
||||||
|
longAdder.increment();
|
||||||
|
} catch (IllegalStateException ex) {
|
||||||
|
LOGGER.log(Level.WARNING, "Case closed while getting files");
|
||||||
|
}
|
||||||
|
return longAdder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fire a CategoryChangeEvent with the given fileIDs
|
||||||
|
*
|
||||||
|
* @param fileIDs
|
||||||
|
*/
|
||||||
|
public void fireChange(Collection<Long> fileIDs) {
|
||||||
|
categoryEventBus.post(new CategoryChangeEvent(fileIDs));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* register an object to receive CategoryChangeEvents
|
||||||
|
*
|
||||||
|
* @param listner
|
||||||
|
*/
|
||||||
|
public void registerListener(Object listner) {
|
||||||
|
categoryEventBus.register(listner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unregister an object from receiving CategoryChangeEvents
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
public void unregisterListener(Object listener) {
|
||||||
|
categoryEventBus.unregister(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-14 Basis Technology Corp.
|
* Copyright 2013-15 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");
|
||||||
@ -35,9 +35,11 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import javax.swing.SortOrder;
|
import javax.swing.SortOrder;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@ -54,6 +56,7 @@ import static org.sleuthkit.autopsy.imagegallery.grouping.GroupSortBy.GROUP_BY_V
|
|||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
|
import org.sleuthkit.datamodel.Content;
|
||||||
import org.sleuthkit.datamodel.ContentTag;
|
import org.sleuthkit.datamodel.ContentTag;
|
||||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
import org.sleuthkit.datamodel.TagName;
|
import org.sleuthkit.datamodel.TagName;
|
||||||
@ -65,10 +68,10 @@ import org.sqlite.SQLiteJDBCLoader;
|
|||||||
* database. This class borrows a lot of ideas and techniques (for good or ill)
|
* database. This class borrows a lot of ideas and techniques (for good or ill)
|
||||||
* from {@link SleuthkitCase}.
|
* from {@link SleuthkitCase}.
|
||||||
*
|
*
|
||||||
* TODO: Creating an abstract base class for sqlite databases* may make sense in
|
* TODO: Creating an abstract base class for sqlite databases may make sense in
|
||||||
* the future. see also {@link EventsDB}
|
* the future. see also {@link EventsDB} in the timeline viewer.
|
||||||
*/
|
*/
|
||||||
public class DrawableDB {
|
public final class DrawableDB {
|
||||||
|
|
||||||
private static final java.util.logging.Logger LOGGER = Logger.getLogger(DrawableDB.class.getName());
|
private static final java.util.logging.Logger LOGGER = Logger.getLogger(DrawableDB.class.getName());
|
||||||
|
|
||||||
@ -127,9 +130,7 @@ public class DrawableDB {
|
|||||||
*/
|
*/
|
||||||
private final HashSet<FileUpdateEvent.FileUpdateListener> updateListeners = new HashSet<>();
|
private final HashSet<FileUpdateEvent.FileUpdateListener> updateListeners = new HashSet<>();
|
||||||
|
|
||||||
private GroupManager manager;
|
private GroupManager groupManager;
|
||||||
|
|
||||||
private ImageGalleryController controller;
|
|
||||||
|
|
||||||
private final Path dbPath;
|
private final Path dbPath;
|
||||||
|
|
||||||
@ -146,6 +147,7 @@ public class DrawableDB {
|
|||||||
LOGGER.log(Level.SEVERE, "Failed to load sqlite JDBC driver", ex);
|
LOGGER.log(Level.SEVERE, "Failed to load sqlite JDBC driver", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private final SleuthkitCase tskCase;
|
||||||
|
|
||||||
//////////////general database logic , mostly borrowed from sleuthkitcase
|
//////////////general database logic , mostly borrowed from sleuthkitcase
|
||||||
/**
|
/**
|
||||||
@ -194,11 +196,11 @@ public class DrawableDB {
|
|||||||
*
|
*
|
||||||
* @throws SQLException if there is problem creating or configuring the db
|
* @throws SQLException if there is problem creating or configuring the db
|
||||||
*/
|
*/
|
||||||
private DrawableDB(Path dbPath) throws SQLException, ExceptionInInitializerError, IOException {
|
private DrawableDB(Path dbPath, SleuthkitCase tskCase) throws SQLException, ExceptionInInitializerError, IOException {
|
||||||
|
|
||||||
this.dbPath = dbPath;
|
this.dbPath = dbPath;
|
||||||
|
this.tskCase = tskCase;
|
||||||
Files.createDirectories(dbPath.getParent());
|
Files.createDirectories(dbPath.getParent());
|
||||||
if (initializeDB()) {
|
if (initializeDBSchema()) {
|
||||||
updateFileStmt = prepareStatement(
|
updateFileStmt = prepareStatement(
|
||||||
"INSERT OR REPLACE INTO drawable_files (obj_id , path, name, created_time, modified_time, make, model, analyzed) "
|
"INSERT OR REPLACE INTO drawable_files (obj_id , path, name, created_time, modified_time, make, model, analyzed) "
|
||||||
+ "VALUES (?,?,?,?,?,?,?,?)");
|
+ "VALUES (?,?,?,?,?,?,?,?)");
|
||||||
@ -228,9 +230,11 @@ public class DrawableDB {
|
|||||||
|
|
||||||
insertHashHitStmt = prepareStatement("insert or ignore into hash_set_hits (hash_set_id, obj_id) values (?,?)");
|
insertHashHitStmt = prepareStatement("insert or ignore into hash_set_hits (hash_set_id, obj_id) values (?,?)");
|
||||||
|
|
||||||
|
initializeImageList();
|
||||||
} else {
|
} else {
|
||||||
throw new ExceptionInInitializerError();
|
throw new ExceptionInInitializerError();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -280,14 +284,10 @@ public class DrawableDB {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static DrawableDB getDrawableDB(Path dbPath, ImageGalleryController controller) {
|
public static DrawableDB getDrawableDB(Path dbPath, SleuthkitCase tskCase) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Path dbFilePath = dbPath.resolve("drawable.db");
|
return new DrawableDB(dbPath.resolve("drawable.db"), tskCase);
|
||||||
|
|
||||||
DrawableDB drawableDB = new DrawableDB(dbFilePath);
|
|
||||||
drawableDB.controller = controller;
|
|
||||||
return drawableDB;
|
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "sql error creating database connection", ex);
|
LOGGER.log(Level.SEVERE, "sql error creating database connection", ex);
|
||||||
return null;
|
return null;
|
||||||
@ -339,7 +339,7 @@ public class DrawableDB {
|
|||||||
* @return the number of rows in the table , count > 0 indicating an
|
* @return the number of rows in the table , count > 0 indicating an
|
||||||
* existing table
|
* existing table
|
||||||
*/
|
*/
|
||||||
private boolean initializeDB() {
|
private boolean initializeDBSchema() {
|
||||||
try {
|
try {
|
||||||
if (isClosed()) {
|
if (isClosed()) {
|
||||||
openDBCon();
|
openDBCon();
|
||||||
@ -954,7 +954,7 @@ public class DrawableDB {
|
|||||||
*/
|
*/
|
||||||
private DrawableFile<?> getFileFromID(Long id, boolean analyzed) throws TskCoreException {
|
private DrawableFile<?> getFileFromID(Long id, boolean analyzed) throws TskCoreException {
|
||||||
try {
|
try {
|
||||||
AbstractFile f = controller.getSleuthKitCase().getAbstractFileById(id);
|
AbstractFile f = tskCase.getAbstractFileById(id);
|
||||||
return DrawableFile.create(f, analyzed, isVideoFile(f));
|
return DrawableFile.create(f, analyzed, isVideoFile(f));
|
||||||
} catch (IllegalStateException ex) {
|
} catch (IllegalStateException ex) {
|
||||||
LOGGER.log(Level.SEVERE, "there is no case open; failed to load file with id: " + id, ex);
|
LOGGER.log(Level.SEVERE, "there is no case open; failed to load file with id: " + id, ex);
|
||||||
@ -972,7 +972,7 @@ public class DrawableDB {
|
|||||||
*/
|
*/
|
||||||
public DrawableFile<?> getFileFromID(Long id) throws TskCoreException {
|
public DrawableFile<?> getFileFromID(Long id) throws TskCoreException {
|
||||||
try {
|
try {
|
||||||
AbstractFile f = controller.getSleuthKitCase().getAbstractFileById(id);
|
AbstractFile f = tskCase.getAbstractFileById(id);
|
||||||
return DrawableFile.create(f,
|
return DrawableFile.create(f,
|
||||||
areFilesAnalyzed(Collections.singleton(id)), isVideoFile(f));
|
areFilesAnalyzed(Collections.singleton(id)), isVideoFile(f));
|
||||||
} catch (IllegalStateException ex) {
|
} catch (IllegalStateException ex) {
|
||||||
@ -986,9 +986,9 @@ public class DrawableDB {
|
|||||||
if (groupKey.getAttribute().isDBColumn) {
|
if (groupKey.getAttribute().isDBColumn) {
|
||||||
switch (groupKey.getAttribute().attrName) {
|
switch (groupKey.getAttribute().attrName) {
|
||||||
case CATEGORY:
|
case CATEGORY:
|
||||||
return manager.getFileIDsWithCategory((Category) groupKey.getValue());
|
return groupManager.getFileIDsWithCategory((Category) groupKey.getValue());
|
||||||
case TAGS:
|
case TAGS:
|
||||||
return manager.getFileIDsWithTag((TagName) groupKey.getValue());
|
return groupManager.getFileIDsWithTag((TagName) groupKey.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<Long> files = new ArrayList<>();
|
List<Long> files = new ArrayList<>();
|
||||||
@ -1129,45 +1129,24 @@ public class DrawableDB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* The following groups of functions are used to store information in memory
|
* For the given fileID, get the names of all the hashsets that the file is
|
||||||
* instead of in the database. Due to the change listeners in the GUI,
|
* in.
|
||||||
* this data is requested many, many times when browsing the images, and
|
|
||||||
* especially when making any changes to things like categories.
|
|
||||||
*
|
*
|
||||||
* I don't like having multiple copies of the data, but these were causing
|
* @param fileID the fileID to file all the hash sets for
|
||||||
* major bottlenecks when they were all database lookups.
|
|
||||||
*
|
*
|
||||||
* TODO: factor these out to seperate classes such as HashSetHitCache or
|
* @return a set of names, each of which is a hashset that the given file is
|
||||||
* CategoryCountCache
|
* in.
|
||||||
*
|
*
|
||||||
* TODO: use guava Caches for this instead of lower level HashMaps
|
*
|
||||||
|
* //TODO: why does this go to the SKC? don't we already have this in =fo
|
||||||
|
* in the drawable db?
|
||||||
*/
|
*/
|
||||||
@GuardedBy("hashSetMap")
|
@Nonnull
|
||||||
private final Map<Long, Set<String>> hashSetMap = new HashMap<>();
|
Set<String> getHashSetsForFile(long fileID) {
|
||||||
|
|
||||||
@GuardedBy("hashSetMap")
|
|
||||||
public boolean isInHashSet(Long id) {
|
|
||||||
if (!hashSetMap.containsKey(id)) {
|
|
||||||
updateHashSetsForFile(id);
|
|
||||||
}
|
|
||||||
return (!hashSetMap.get(id).isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@GuardedBy("hashSetMap")
|
|
||||||
public Set<String> getHashSetsForFile(Long id) {
|
|
||||||
if (!isInHashSet(id)) {
|
|
||||||
updateHashSetsForFile(id);
|
|
||||||
}
|
|
||||||
return hashSetMap.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GuardedBy("hashSetMap")
|
|
||||||
public void updateHashSetsForFile(Long id) {
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<BlackboardArtifact> arts = ImageGalleryController.getDefault().getSleuthKitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, id);
|
|
||||||
Set<String> hashNames = new HashSet<>();
|
Set<String> hashNames = new HashSet<>();
|
||||||
|
List<BlackboardArtifact> arts = ImageGalleryController.getDefault().getSleuthKitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, fileID);
|
||||||
for (BlackboardArtifact a : arts) {
|
for (BlackboardArtifact a : arts) {
|
||||||
List<BlackboardAttribute> attrs = a.getAttributes();
|
List<BlackboardAttribute> attrs = a.getAttributes();
|
||||||
for (BlackboardAttribute attr : attrs) {
|
for (BlackboardAttribute attr : attrs) {
|
||||||
@ -1175,48 +1154,48 @@ public class DrawableDB {
|
|||||||
hashNames.add(attr.getValueString());
|
hashNames.add(attr.getValueString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return hashNames;
|
||||||
}
|
}
|
||||||
hashSetMap.put(id, hashNames);
|
} catch (TskCoreException tskCoreException) {
|
||||||
} catch (IllegalStateException | TskCoreException ex) {
|
throw new IllegalStateException(tskCoreException);
|
||||||
LOGGER.log(Level.WARNING, "could not access case during updateHashSetsForFile()", ex);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For performance reasons, keep a list of all file IDs currently in the
|
* For performance reasons, keep a list of all file IDs currently in the
|
||||||
* drawable database.
|
* drawable database. Otherwise the database is queried many times to
|
||||||
* Otherwise the database is queried many times to retrieve the same data.
|
* retrieve the same data.
|
||||||
*/
|
*/
|
||||||
@GuardedBy("fileIDlist")
|
@GuardedBy("fileIDlist")
|
||||||
private final Set<Long> fileIDlist = new HashSet<>();
|
private final Set<Long> fileIDsInDB = new HashSet<>();
|
||||||
|
|
||||||
public boolean isDrawableFile(Long id) {
|
public boolean isInDB(Long id) {
|
||||||
synchronized (fileIDlist) {
|
synchronized (fileIDsInDB) {
|
||||||
return fileIDlist.contains(id);
|
return fileIDsInDB.contains(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addImageFileToList(Long id) {
|
private void addImageFileToList(Long id) {
|
||||||
synchronized (fileIDlist) {
|
synchronized (fileIDsInDB) {
|
||||||
fileIDlist.add(id);
|
fileIDsInDB.add(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeImageFileFromList(Long id) {
|
private void removeImageFileFromList(Long id) {
|
||||||
synchronized (fileIDlist) {
|
synchronized (fileIDsInDB) {
|
||||||
fileIDlist.remove(id);
|
fileIDsInDB.remove(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNumberOfImageFilesInList() {
|
public int getNumberOfImageFilesInList() {
|
||||||
synchronized (fileIDlist) {
|
synchronized (fileIDsInDB) {
|
||||||
return fileIDlist.size();
|
return fileIDsInDB.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initializeImageList() {
|
private void initializeImageList() {
|
||||||
synchronized (fileIDlist) {
|
synchronized (fileIDsInDB) {
|
||||||
dbReadLock();
|
dbReadLock();
|
||||||
try {
|
try {
|
||||||
Statement stmt = con.createStatement();
|
Statement stmt = con.createStatement();
|
||||||
@ -1224,7 +1203,6 @@ public class DrawableDB {
|
|||||||
while (analyzedQuery.next()) {
|
while (analyzedQuery.next()) {
|
||||||
addImageFileToList(analyzedQuery.getLong(OBJ_ID));
|
addImageFileToList(analyzedQuery.getLong(OBJ_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
LOGGER.log(Level.WARNING, "problem loading file IDs: ", ex);
|
LOGGER.log(Level.WARNING, "problem loading file IDs: ", ex);
|
||||||
} finally {
|
} finally {
|
||||||
@ -1234,79 +1212,43 @@ public class DrawableDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For performance reasons, keep current category counts in memory
|
* For performance reasons, keep the file type in memory
|
||||||
*/
|
*/
|
||||||
@GuardedBy("categoryCounts")
|
private final Map<AbstractFile, Boolean> videoFileMap = new ConcurrentHashMap<>();
|
||||||
private final Map<Category, Integer> categoryCounts = new HashMap<>();
|
|
||||||
|
|
||||||
public void incrementCategoryCount(Category cat) throws TskCoreException {
|
public boolean isVideoFile(AbstractFile f) {
|
||||||
if (cat != Category.ZERO) {
|
return videoFileMap.computeIfAbsent(f, ImageGalleryModule::isVideoFile);
|
||||||
synchronized (categoryCounts) {
|
|
||||||
int count = getCategoryCount(cat);
|
|
||||||
count++;
|
|
||||||
categoryCounts.put(cat, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decrementCategoryCount(Category cat) throws TskCoreException {
|
|
||||||
if (cat != Category.ZERO) {
|
|
||||||
synchronized (categoryCounts) {
|
|
||||||
int count = getCategoryCount(cat);
|
|
||||||
count--;
|
|
||||||
categoryCounts.put(cat, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCategoryCount(Category cat) throws TskCoreException {
|
|
||||||
synchronized (categoryCounts) {
|
|
||||||
if (cat == Category.ZERO) {
|
|
||||||
// Keeping track of the uncategorized files is a bit tricky while ingest
|
|
||||||
// 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.
|
|
||||||
int allOtherCatCount = getCategoryCount(Category.ONE) + getCategoryCount(Category.TWO) + getCategoryCount(Category.THREE)
|
|
||||||
+ getCategoryCount(Category.FOUR) + getCategoryCount(Category.FIVE);
|
|
||||||
return getNumberOfImageFilesInList() - allOtherCatCount;
|
|
||||||
} else if (categoryCounts.containsKey(cat)) {
|
|
||||||
return categoryCounts.get(cat);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
int fileCount = 0;
|
|
||||||
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(cat.getTagName());
|
|
||||||
for (ContentTag ct : contentTags) {
|
|
||||||
if (ct.getContent() instanceof AbstractFile) {
|
|
||||||
AbstractFile f = (AbstractFile) ct.getContent();
|
|
||||||
if (this.isDrawableFile(f.getId())) {
|
|
||||||
fileCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
categoryCounts.put(cat, fileCount);
|
|
||||||
return fileCount;
|
|
||||||
} catch (IllegalStateException ex) {
|
|
||||||
throw new TskCoreException("Case closed while getting files");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For performance reasons, keep the file type in memory
|
* get the number of files with the given category.
|
||||||
|
*
|
||||||
|
* NOTE: although the category data is stored in autopsy as Tags, this
|
||||||
|
* method is provided on DrawableDb to provide a single point of access for
|
||||||
|
* ImageGallery data.
|
||||||
|
*
|
||||||
|
* //TODO: think about moving this and similar methods that don't actually
|
||||||
|
* get their data form the drawabledb to a layer wrapping the drawable db:
|
||||||
|
* something like ImageGalleryCaseData?
|
||||||
|
*
|
||||||
|
* @param cat the category to count the number of files for
|
||||||
|
*
|
||||||
|
* @return the number of the with the given category
|
||||||
*/
|
*/
|
||||||
@GuardedBy("videoFileMap")
|
public long getCategoryCount(Category cat) {
|
||||||
private final Map<Long, Boolean> videoFileMap = new HashMap<>();
|
try {
|
||||||
|
return Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(cat.getTagName()).stream()
|
||||||
|
.map(ContentTag::getContent)
|
||||||
|
.map(Content::getId)
|
||||||
|
.filter(this::isInDB)
|
||||||
|
.count();
|
||||||
|
|
||||||
public boolean isVideoFile(AbstractFile f) throws TskCoreException {
|
} catch (IllegalStateException ex) {
|
||||||
synchronized (videoFileMap) {
|
LOGGER.log(Level.WARNING, "Case closed while getting files");
|
||||||
if (videoFileMap.containsKey(f.getId())) {
|
} catch (TskCoreException ex1) {
|
||||||
return videoFileMap.get(f.getId());
|
LOGGER.log(Level.SEVERE, "Failed to get content tags by tag name.", ex1);
|
||||||
}
|
|
||||||
|
|
||||||
boolean isVideo = ImageGalleryModule.isVideoFile(f);
|
|
||||||
videoFileMap.put(f.getId(), isVideo);
|
|
||||||
return isVideo;
|
|
||||||
}
|
}
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,7 +68,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
|||||||
return new ImageFile<>(abstractFileById, analyzed);
|
return new ImageFile<>(abstractFileById, analyzed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skip the database query if we have already determined the file type.
|
* Skip the database query if we have already determined the file type.
|
||||||
*/
|
*/
|
||||||
@ -78,7 +78,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
|||||||
} else {
|
} else {
|
||||||
return new ImageFile<>(abstractFileById, analyzed);
|
return new ImageFile<>(abstractFileById, analyzed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DrawableFile<?> create(Long id, boolean analyzed) throws TskCoreException, IllegalStateException {
|
public static DrawableFile<?> create(Long id, boolean analyzed) throws TskCoreException, IllegalStateException {
|
||||||
|
|
||||||
@ -105,7 +105,8 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
|||||||
protected DrawableFile(T file, Boolean analyzed) {
|
protected DrawableFile(T file, Boolean analyzed) {
|
||||||
/* @TODO: the two 'new Integer(0).shortValue()' values and null are
|
/* @TODO: the two 'new Integer(0).shortValue()' values and null are
|
||||||
* placeholders because the super constructor expects values i can't get
|
* placeholders because the super constructor expects values i can't get
|
||||||
* easily at the moment. I assume this is related to why ReadContentInputStream can't read from DrawableFiles.*/
|
* easily at the moment. I assume this is related to why
|
||||||
|
* ReadContentInputStream can't read from DrawableFiles. */
|
||||||
|
|
||||||
super(file.getSleuthkitCase(), file.getId(), file.getAttrType(), file.getAttrId(), file.getName(), file.getType(), file.getMetaAddr(), (int) file.getMetaSeq(), file.getDirType(), file.getMetaType(), null, new Integer(0).shortValue(), file.getSize(), file.getCtime(), file.getCrtime(), file.getAtime(), file.getMtime(), new Integer(0).shortValue(), file.getUid(), file.getGid(), file.getMd5Hash(), file.getKnown(), file.getParentPath());
|
super(file.getSleuthkitCase(), file.getId(), file.getAttrType(), file.getAttrId(), file.getName(), file.getType(), file.getMetaAddr(), (int) file.getMetaSeq(), file.getDirType(), file.getMetaType(), null, new Integer(0).shortValue(), file.getSize(), file.getCtime(), file.getCrtime(), file.getAtime(), file.getMtime(), new Integer(0).shortValue(), file.getUid(), file.getGid(), file.getMd5Hash(), file.getKnown(), file.getParentPath());
|
||||||
this.analyzed = new SimpleBooleanProperty(analyzed);
|
this.analyzed = new SimpleBooleanProperty(analyzed);
|
||||||
@ -115,9 +116,8 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
|||||||
|
|
||||||
public abstract boolean isVideo();
|
public abstract boolean isVideo();
|
||||||
|
|
||||||
synchronized public Collection<String> getHashHitSetNames() {
|
public Collection<String> getHashHitSetNames() {
|
||||||
Collection<String> hashHitSetNames = ImageGalleryController.getDefault().getDatabase().getHashSetsForFile(getId());
|
return ImageGalleryController.getDefault().getHashSetManager().getHashSetsForFile(getId());
|
||||||
return hashHitSetNames;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -290,10 +290,10 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
|
|||||||
}
|
}
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
Logger.getLogger(DrawableFile.class.getName()).log(Level.WARNING, "problem looking up category for file " + this.getName(), ex);
|
Logger.getLogger(DrawableFile.class.getName()).log(Level.WARNING, "problem looking up category for file " + this.getName(), ex);
|
||||||
} catch (IllegalStateException ex){
|
} catch (IllegalStateException ex) {
|
||||||
// We get here many times if the case is closed during ingest, so don't print out a ton of warnings.
|
// We get here many times if the case is closed during ingest, so don't print out a ton of warnings.
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Image getThumbnail();
|
public abstract Image getThumbnail();
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
package org.sleuthkit.autopsy.imagegallery.datamodel;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages a cache of hashset hits as a map from fileID to hashset names.
|
||||||
|
* Initial/invalid values are loaded from the backing DrawableDB
|
||||||
|
*/
|
||||||
|
public class HashSetManager {
|
||||||
|
|
||||||
|
/** The db that initial values are loaded from. */
|
||||||
|
private DrawableDB db = null;
|
||||||
|
|
||||||
|
/** the internal cache from fileID to a set of hashset names. */
|
||||||
|
private final LoadingCache<Long, Set<String>> hashSetCache = CacheBuilder.newBuilder().build(CacheLoader.from(this::getHashSetsForFileHelper));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* assign the given db to back this hashset manager.
|
||||||
|
*
|
||||||
|
* @param db
|
||||||
|
*/
|
||||||
|
public void setDb(DrawableDB db) {
|
||||||
|
this.db = db;
|
||||||
|
hashSetCache.invalidateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper method to load hashset hits for the given fileID from the db
|
||||||
|
*
|
||||||
|
* @param fileID
|
||||||
|
*
|
||||||
|
* @return the names of the hashsets the given fileID is in
|
||||||
|
*/
|
||||||
|
private Set<String> getHashSetsForFileHelper(long fileID) {
|
||||||
|
return db.getHashSetsForFile(fileID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is the given fileID in any hashset
|
||||||
|
*
|
||||||
|
* @param fileID
|
||||||
|
*
|
||||||
|
* @return true if the file is in any hashset
|
||||||
|
*/
|
||||||
|
public boolean isInAnyHashSet(long fileID) {
|
||||||
|
return getHashSetsForFile(fileID).isEmpty() == false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the names of the hash sets the given fileId is in
|
||||||
|
*
|
||||||
|
* @param fileID
|
||||||
|
*
|
||||||
|
* @return a set containging the names of the hash sets for the given file
|
||||||
|
*/
|
||||||
|
public Set<String> getHashSetsForFile(long fileID) {
|
||||||
|
return hashSetCache.getUnchecked(fileID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* invalidate the cached hashset names for the given fileID
|
||||||
|
*
|
||||||
|
* @param fileID the fileID to invalidate in the cache
|
||||||
|
*/
|
||||||
|
public void invalidateHashSetsForFile(long fileID) {
|
||||||
|
hashSetCache.invalidate(fileID);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-15 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");
|
||||||
@ -43,11 +43,11 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
|
|||||||
private final ObservableList<Long> fileIDs = FXCollections.observableArrayList();
|
private final ObservableList<Long> fileIDs = FXCollections.observableArrayList();
|
||||||
|
|
||||||
//cache the number of files in this groups with hashset hits
|
//cache the number of files in this groups with hashset hits
|
||||||
private int hashSetHitsCount = -1;
|
private long hashSetHitsCount = -1;
|
||||||
private final ReadOnlyBooleanWrapper seen = new ReadOnlyBooleanWrapper(false);
|
private final ReadOnlyBooleanWrapper seen = new ReadOnlyBooleanWrapper(false);
|
||||||
|
|
||||||
synchronized public ObservableList<Long> fileIds() {
|
synchronized public ObservableList<Long> fileIds() {
|
||||||
return fileIDs;
|
return FXCollections.unmodifiableObservableList(fileIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
final public GroupKey<?> groupKey;
|
final public GroupKey<?> groupKey;
|
||||||
@ -82,29 +82,28 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call to indicate that an image has been added or removed from the group,
|
* Call to indicate that an file has been added or removed from the group,
|
||||||
* so the hash counts may not longer be accurate.
|
* so the hash counts may no longer be accurate.
|
||||||
*/
|
*/
|
||||||
synchronized public void invalidateHashSetHitsCount() {
|
synchronized private void invalidateHashSetHitsCount() {
|
||||||
hashSetHitsCount = -1;
|
hashSetHitsCount = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized public int getHashSetHitsCount() {
|
/**
|
||||||
//TODO: use the drawable db for this ? -jm
|
* @return the number of files in this group that have hash set hits
|
||||||
|
*/
|
||||||
|
synchronized public long getHashSetHitsCount() {
|
||||||
if (hashSetHitsCount < 0) {
|
if (hashSetHitsCount < 0) {
|
||||||
hashSetHitsCount = 0;
|
try {
|
||||||
for (Long fileID : fileIds()) {
|
hashSetHitsCount = fileIDs.stream()
|
||||||
|
.map(fileID -> ImageGalleryController.getDefault().getHashSetManager().isInAnyHashSet(fileID))
|
||||||
try {
|
.filter(Boolean::booleanValue)
|
||||||
if (ImageGalleryController.getDefault().getDatabase().isInHashSet(fileID)) {
|
.count();
|
||||||
hashSetHitsCount++;
|
} catch (IllegalStateException | NullPointerException ex) {
|
||||||
}
|
LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()");
|
||||||
} catch (IllegalStateException | NullPointerException ex) {
|
|
||||||
LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hashSetHitsCount;
|
return hashSetHitsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,6 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
|
|||||||
private DrawableDB db;
|
private DrawableDB db;
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* map from {@link GroupKey}s to {@link DrawableGroup}s. All groups (even
|
* map from {@link GroupKey}s to {@link DrawableGroup}s. All groups (even
|
||||||
* not
|
* not
|
||||||
@ -436,7 +435,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
|
|||||||
switch (groupBy.attrName) {
|
switch (groupBy.attrName) {
|
||||||
//these cases get special treatment
|
//these cases get special treatment
|
||||||
case CATEGORY:
|
case CATEGORY:
|
||||||
values = (List<A>) Category.valuesList();
|
values = (List<A>) Arrays.asList(Category.values());
|
||||||
break;
|
break;
|
||||||
case TAGS:
|
case TAGS:
|
||||||
values = (List<A>) Case.getCurrentCase().getServices().getTagsManager().getTagNamesInUse().stream()
|
values = (List<A>) Case.getCurrentCase().getServices().getTagsManager().getTagNamesInUse().stream()
|
||||||
@ -447,7 +446,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
|
|||||||
values = (List<A>) Arrays.asList(false, true);
|
values = (List<A>) Arrays.asList(false, true);
|
||||||
break;
|
break;
|
||||||
case HASHSET:
|
case HASHSET:
|
||||||
TreeSet<A> names = new TreeSet<>((Set<A>) db.getHashSetNames());
|
TreeSet<A> names = new TreeSet<>((Collection<? extends A>) db.getHashSetNames());
|
||||||
values = new ArrayList<>(names);
|
values = new ArrayList<>(names);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -457,8 +456,8 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
|
|||||||
|
|
||||||
return values;
|
return values;
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
LOGGER.log(Level.WARNING, "TSK error getting list of type " + groupBy.getDisplayName());
|
LOGGER.log(Level.WARNING, "TSK error getting list of type {0}", groupBy.getDisplayName());
|
||||||
return new ArrayList<A>();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -490,7 +489,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
|
|||||||
for (TagName tn : tns) {
|
for (TagName tn : tns) {
|
||||||
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(tn);
|
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(tn);
|
||||||
for (ContentTag ct : contentTags) {
|
for (ContentTag ct : contentTags) {
|
||||||
if (ct.getContent() instanceof AbstractFile && db.isDrawableFile(((AbstractFile) ct.getContent()).getId())) {
|
if (ct.getContent() instanceof AbstractFile && db.isInDB(ct.getContent().getId())) {
|
||||||
files.add(ct.getContent().getId());
|
files.add(ct.getContent().getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -502,8 +501,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
|
|||||||
List<Long> files = new ArrayList<>();
|
List<Long> files = new ArrayList<>();
|
||||||
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(category.getTagName());
|
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(category.getTagName());
|
||||||
for (ContentTag ct : contentTags) {
|
for (ContentTag ct : contentTags) {
|
||||||
if (ct.getContent() instanceof AbstractFile && db.isDrawableFile(((AbstractFile) ct.getContent()).getId())) {
|
if (ct.getContent() instanceof AbstractFile && db.isInDB(ct.getContent().getId())) {
|
||||||
|
|
||||||
files.add(ct.getContent().getId());
|
files.add(ct.getContent().getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -516,27 +514,12 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Count the number of files with the given category.
|
|
||||||
* This is faster than getFileIDsWithCategory and should be used if only the
|
|
||||||
* counts are needed and not the file IDs.
|
|
||||||
*
|
|
||||||
* @param category Category to match against
|
|
||||||
*
|
|
||||||
* @return Number of files with the given category
|
|
||||||
*
|
|
||||||
* @throws TskCoreException
|
|
||||||
*/
|
|
||||||
public int countFilesWithCategory(Category category) throws TskCoreException {
|
|
||||||
return db.getCategoryCount(category);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Long> getFileIDsWithTag(TagName tagName) throws TskCoreException {
|
public List<Long> getFileIDsWithTag(TagName tagName) throws TskCoreException {
|
||||||
try {
|
try {
|
||||||
List<Long> files = new ArrayList<>();
|
List<Long> files = new ArrayList<>();
|
||||||
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(tagName);
|
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(tagName);
|
||||||
for (ContentTag ct : contentTags) {
|
for (ContentTag ct : contentTags) {
|
||||||
if (ct.getContent() instanceof AbstractFile && db.isDrawableFile(((AbstractFile) ct.getContent()).getId())) {
|
if (ct.getContent() instanceof AbstractFile && db.isInDB(ct.getContent().getId())) {
|
||||||
|
|
||||||
files.add(ct.getContent().getId());
|
files.add(ct.getContent().getId());
|
||||||
}
|
}
|
||||||
@ -655,8 +638,6 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
|
|||||||
if (checkAnalyzed != null) { // => the group is analyzed, so add it to the ui
|
if (checkAnalyzed != null) { // => the group is analyzed, so add it to the ui
|
||||||
populateAnalyzedGroup(gk, checkAnalyzed);
|
populateAnalyzedGroup(gk, checkAnalyzed);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
g.invalidateHashSetHitsCount();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -677,7 +658,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
|
|||||||
*/
|
*/
|
||||||
for (final long fileId : fileIDs) {
|
for (final long fileId : fileIDs) {
|
||||||
|
|
||||||
db.updateHashSetsForFile(fileId);
|
controller.getHashSetManager().invalidateHashSetsForFile(fileId);
|
||||||
|
|
||||||
//get grouping(s) this file would be in
|
//get grouping(s) this file would be in
|
||||||
Set<GroupKey<?>> groupsForFile = getGroupKeysForFileID(fileId);
|
Set<GroupKey<?>> groupsForFile = getGroupKeysForFileID(fileId);
|
||||||
@ -700,7 +681,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//we fire this event for all files so that the category counts get updated during initial db population
|
//we fire this event for all files so that the category counts get updated during initial db population
|
||||||
Category.fireChange(fileIDs);
|
controller.getCategoryManager().fireChange(fileIDs);
|
||||||
|
|
||||||
if (evt.getChangedAttribute() == DrawableAttribute.TAGS) {
|
if (evt.getChangedAttribute() == DrawableAttribute.TAGS) {
|
||||||
TagUtils.fireChange(fileIDs);
|
TagUtils.fireChange(fileIDs);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-15 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,10 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
package org.sleuthkit.autopsy.imagegallery.gui;
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.CacheHint;
|
import javafx.scene.CacheHint;
|
||||||
import javafx.scene.control.Control;
|
import javafx.scene.control.Control;
|
||||||
@ -34,17 +33,17 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
|||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined.ThreadType;
|
||||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.imagegallery.TagUtils;
|
import org.sleuthkit.autopsy.imagegallery.TagUtils;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
import static org.sleuthkit.autopsy.imagegallery.gui.DrawableViewBase.globalSelectionModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GUI component that represents a single image as a tile with an icon, a label
|
* GUI component that represents a single image as a tile with an icon, a label,
|
||||||
* a color coded border and possibly other controls. Designed to be in a
|
* a color coded border and possibly other controls. Designed to be in a
|
||||||
* {@link GroupPane}'s TilePane or SlideShow.
|
* {@link GroupPane}'s TilePane or SlideShow.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* TODO: refactor this to extend from {@link Control}? -jm
|
* TODO: refactor this to extend from {@link Control}? -jm
|
||||||
*/
|
*/
|
||||||
public class DrawableTile extends SingleDrawableViewBase implements Category.CategoryListener, TagUtils.TagListener {
|
public class DrawableTile extends DrawableViewBase implements TagUtils.TagListener {
|
||||||
|
|
||||||
private static final DropShadow LAST_SELECTED_EFFECT = new DropShadow(10, Color.BLUE);
|
private static final DropShadow LAST_SELECTED_EFFECT = new DropShadow(10, Color.BLUE);
|
||||||
|
|
||||||
@ -56,12 +55,6 @@ public class DrawableTile extends SingleDrawableViewBase implements Category.Cat
|
|||||||
@FXML
|
@FXML
|
||||||
private ImageView imageView;
|
private ImageView imageView;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ResourceBundle resources;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private URL location;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void disposeContent() {
|
protected void disposeContent() {
|
||||||
//no-op
|
//no-op
|
||||||
@ -99,11 +92,23 @@ public class DrawableTile extends SingleDrawableViewBase implements Category.Cat
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ThreadConfined(type = ThreadType.UI)
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
protected void clearContent() {
|
protected void clearContent() {
|
||||||
imageView.setImage(null);
|
imageView.setImage(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc }
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void updateSelectionState() {
|
||||||
|
super.updateSelectionState();
|
||||||
|
final boolean lastSelected = Objects.equals(globalSelectionModel.lastSelectedProperty().get(), fileID);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
setEffect(lastSelected ? LAST_SELECTED_EFFECT : null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Runnable getContentUpdateRunnable() {
|
protected Runnable getContentUpdateRunnable() {
|
||||||
Image image = file.getThumbnail();
|
Image image = file.getThumbnail();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
package org.sleuthkit.autopsy.imagegallery.gui;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
@ -14,13 +15,17 @@ import org.sleuthkit.autopsy.coreutils.Logger;
|
|||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.imagegallery.TagUtils;
|
import org.sleuthkit.autopsy.imagegallery.TagUtils;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: extract common interface out of {@link SingleImageView} and
|
* Interface for classes that are views of a single DrawableFile. Implementation
|
||||||
* {@link MetaDataPane}
|
* of DrawableView must be registered with {@link CategoryManager#registerListener(java.lang.Object)
|
||||||
|
* } to have there {@link DrawableView#handleCategoryChanged(org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent)
|
||||||
|
* } method invoked
|
||||||
*/
|
*/
|
||||||
public interface DrawableView extends Category.CategoryListener, TagUtils.TagListener {
|
public interface DrawableView extends TagUtils.TagListener {
|
||||||
|
|
||||||
//TODO: do this all in css? -jm
|
//TODO: do this all in css? -jm
|
||||||
static final int CAT_BORDER_WIDTH = 10;
|
static final int CAT_BORDER_WIDTH = 10;
|
||||||
@ -51,16 +56,24 @@ public interface DrawableView extends Category.CategoryListener, TagUtils.TagLis
|
|||||||
|
|
||||||
Long getFileID();
|
Long getFileID();
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
void handleCategoryChanged(Collection<Long> ids);
|
* update the visual representation of the category of the assigned file.
|
||||||
|
* Implementations of {@link DrawableView} must register themselves with
|
||||||
|
* {@link CategoryManager#registerListener(java.lang.Object)} to ahve this
|
||||||
|
* method invoked
|
||||||
|
*
|
||||||
|
* @param evt the CategoryChangeEvent to handle
|
||||||
|
*/
|
||||||
|
@Subscribe
|
||||||
|
void handleCategoryChanged(CategoryChangeEvent evt);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void handleTagsChanged(Collection<Long> ids);
|
void handleTagsChanged(Collection<Long> ids);
|
||||||
|
|
||||||
default boolean hasHashHit() {
|
default boolean hasHashHit() {
|
||||||
try{
|
try {
|
||||||
return getFile().getHashHitSetNames().isEmpty() == false;
|
return getFile().getHashHitSetNames().isEmpty() == false;
|
||||||
} catch (NullPointerException ex){
|
} catch (NullPointerException ex) {
|
||||||
// I think this happens when we're in the process of removing images from the view while
|
// I think this happens when we're in the process of removing images from the view while
|
||||||
// also trying to update it?
|
// also trying to update it?
|
||||||
Logger.getLogger(DrawableView.class.getName()).log(Level.WARNING, "Error looking up hash set hits");
|
Logger.getLogger(DrawableView.class.getName()).log(Level.WARNING, "Error looking up hash set hits");
|
||||||
@ -90,13 +103,12 @@ public interface DrawableView extends Category.CategoryListener, TagUtils.TagLis
|
|||||||
default Category updateCategoryBorder() {
|
default Category updateCategoryBorder() {
|
||||||
final Category category = getFile().getCategory();
|
final Category category = getFile().getCategory();
|
||||||
final Border border = hasHashHit() && (category == Category.ZERO)
|
final Border border = hasHashHit() && (category == Category.ZERO)
|
||||||
? HASH_BORDER
|
? HASH_BORDER
|
||||||
: DrawableView.getCategoryBorder(category);
|
: DrawableView.getCategoryBorder(category);
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
getBorderable().setBorder(border);
|
getBorderable().setBorder(border);
|
||||||
});
|
});
|
||||||
return category;
|
return category;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
package org.sleuthkit.autopsy.imagegallery.gui;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -71,7 +72,7 @@ import org.sleuthkit.autopsy.imagegallery.TagUtils;
|
|||||||
import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.AddDrawableTagAction;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter;
|
import org.sleuthkit.autopsy.imagegallery.actions.SwingMenuItemAdapter;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent;
|
||||||
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;
|
||||||
import org.sleuthkit.autopsy.imagegallery.grouping.GroupKey;
|
import org.sleuthkit.autopsy.imagegallery.grouping.GroupKey;
|
||||||
@ -88,9 +89,9 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
* of {@link DrawableView}s should implement the interface directly
|
* of {@link DrawableView}s should implement the interface directly
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public abstract class SingleDrawableViewBase extends AnchorPane implements DrawableView {
|
public abstract class DrawableViewBase extends AnchorPane implements DrawableView {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(SingleDrawableViewBase.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(DrawableViewBase.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)));
|
||||||
|
|
||||||
@ -141,11 +142,11 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa
|
|||||||
protected Long fileID;
|
protected Long fileID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the groupPane this {@link SingleDrawableViewBase} is embedded in
|
* the groupPane this {@link DrawableViewBase} is embedded in
|
||||||
*/
|
*/
|
||||||
protected GroupPane groupPane;
|
protected GroupPane groupPane;
|
||||||
|
|
||||||
protected SingleDrawableViewBase() {
|
protected DrawableViewBase() {
|
||||||
|
|
||||||
globalSelectionModel.getSelected().addListener((Observable observable) -> {
|
globalSelectionModel.getSelected().addListener((Observable observable) -> {
|
||||||
updateSelectionState();
|
updateSelectionState();
|
||||||
@ -186,7 +187,7 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa
|
|||||||
groupContextMenu.hide();
|
groupContextMenu.hide();
|
||||||
}
|
}
|
||||||
contextMenu = buildContextMenu();
|
contextMenu = buildContextMenu();
|
||||||
contextMenu.show(SingleDrawableViewBase.this, t.getScreenX(), t.getScreenY());
|
contextMenu.show(DrawableViewBase.this, t.getScreenX(), t.getScreenY());
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -360,14 +361,14 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa
|
|||||||
disposeContent();
|
disposeContent();
|
||||||
|
|
||||||
if (this.fileID == null || Case.isCaseOpen() == false) {
|
if (this.fileID == null || Case.isCaseOpen() == false) {
|
||||||
Category.unregisterListener(this);
|
ImageGalleryController.getDefault().getCategoryManager().unregisterListener(this);
|
||||||
TagUtils.unregisterListener(this);
|
TagUtils.unregisterListener(this);
|
||||||
file = null;
|
file = null;
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
clearContent();
|
clearContent();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Category.registerListener(this);
|
ImageGalleryController.getDefault().getCategoryManager().registerListener(this);
|
||||||
TagUtils.registerListener(this);
|
TagUtils.registerListener(this);
|
||||||
|
|
||||||
getFile();
|
getFile();
|
||||||
@ -385,10 +386,14 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSelectionState() {
|
/**
|
||||||
|
* update the visual representation of the selection state of this
|
||||||
|
* DrawableView
|
||||||
|
*/
|
||||||
|
protected void updateSelectionState() {
|
||||||
final boolean selected = globalSelectionModel.isSelected(fileID);
|
final boolean selected = globalSelectionModel.isSelected(fileID);
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
SingleDrawableViewBase.this.setBorder(selected ? SELECTED_BORDER : UNSELECTED_BORDER);
|
setBorder(selected ? SELECTED_BORDER : UNSELECTED_BORDER);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,9 +402,10 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa
|
|||||||
return imageBorder;
|
return imageBorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
@Override
|
@Override
|
||||||
public void handleCategoryChanged(Collection<Long> ids) {
|
public void handleCategoryChanged(CategoryChangeEvent evt) {
|
||||||
if (ids.contains(fileID)) {
|
if (evt.getIds().contains(fileID)) {
|
||||||
updateCategoryBorder();
|
updateCategoryBorder();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -85,6 +85,7 @@ import javafx.scene.paint.Color;
|
|||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import javax.swing.Action;
|
import javax.swing.Action;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
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;
|
||||||
import org.controlsfx.control.SegmentedButton;
|
import org.controlsfx.control.SegmentedButton;
|
||||||
@ -123,8 +124,13 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
* both a {@link GridView} based view and a {@link SlideShowView} view by
|
* both a {@link GridView} based view and a {@link SlideShowView} view by
|
||||||
* swapping out its internal components.
|
* swapping out its internal components.
|
||||||
*
|
*
|
||||||
* TODO: review for synchronization issues. TODO: Extract the The GridView
|
*
|
||||||
* instance to a separate class analogous to the SlideShow
|
* TODO: Extract the The GridView instance to a separate class analogous to the
|
||||||
|
* SlideShow. Move selection model into controlsfx GridView and submit pull
|
||||||
|
* request to them.
|
||||||
|
* https://bitbucket.org/controlsfx/controlsfx/issue/4/add-a-multipleselectionmodel-to-gridview
|
||||||
|
*
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public class GroupPane extends BorderPane implements GroupView {
|
public class GroupPane extends BorderPane implements GroupView {
|
||||||
|
|
||||||
@ -205,10 +211,8 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
* to determine whether fileIDs are visible or are offscreen. No entry
|
* to determine whether fileIDs are visible or are offscreen. No entry
|
||||||
* indicates the given fileID is not displayed on screen. DrawableCells are
|
* indicates the given fileID is not displayed on screen. DrawableCells are
|
||||||
* responsible for adding and removing themselves from this map.
|
* responsible for adding and removing themselves from this map.
|
||||||
*
|
|
||||||
* TODO: use ConcurrentHashMap ?
|
|
||||||
*/
|
*/
|
||||||
@ThreadConfined(type = ThreadType.UI)
|
@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) -> {
|
||||||
@ -298,7 +302,7 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
/** create the string to display in the group header */
|
/** create the string to display in the group header */
|
||||||
protected String getHeaderString() {
|
protected String getHeaderString() {
|
||||||
return isNull(getGrouping()) ? ""
|
return isNull(getGrouping()) ? ""
|
||||||
: defaultIfBlank(getGrouping().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()) + " -- "
|
: StringUtils.defaultIfBlank(getGrouping().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()) + " -- "
|
||||||
+ getGrouping().getHashSetHitsCount() + " hash set hits / " + getGrouping().getSize() + " files";
|
+ getGrouping().getHashSetHitsCount() + " hash set hits / " + getGrouping().getSize() + " files";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,6 +509,7 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.
|
||||||
@ -527,10 +532,6 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
.max().getAsInt();
|
.max().getAsInt();
|
||||||
|
|
||||||
//[minIndex, maxIndex] is the range of indexes in the fileIDs list that are currently displayed
|
//[minIndex, maxIndex] is the range of indexes in the fileIDs list that are currently displayed
|
||||||
if (minIndex < 0 && maxIndex < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedIndex < minIndex) {
|
if (selectedIndex < minIndex) {
|
||||||
scrollBar.decrement();
|
scrollBar.decrement();
|
||||||
} else if (selectedIndex > maxIndex) {
|
} else if (selectedIndex > maxIndex) {
|
||||||
@ -647,6 +648,7 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void resetItem() {
|
void resetItem() {
|
||||||
|
updateItem(null, true);
|
||||||
tile.setFile(null);
|
tile.setFile(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,18 +18,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
package org.sleuthkit.autopsy.imagegallery.gui;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.ResourceBundle;
|
|
||||||
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.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
@ -46,20 +43,20 @@ import static javafx.scene.layout.Region.USE_COMPUTED_SIZE;
|
|||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.util.Pair;
|
import javafx.util.Pair;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.openide.util.Exceptions;
|
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.TagUtils;
|
import org.sleuthkit.autopsy.imagegallery.TagUtils;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent;
|
||||||
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;
|
||||||
import org.sleuthkit.datamodel.TagName;
|
import org.sleuthkit.datamodel.TagName;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Shows details of the selected file.
|
||||||
*/
|
*/
|
||||||
public class MetaDataPane extends AnchorPane implements Category.CategoryListener, TagUtils.TagListener, DrawableView {
|
public class MetaDataPane extends AnchorPane implements TagUtils.TagListener, DrawableView {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(MetaDataPane.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(MetaDataPane.class.getName());
|
||||||
|
|
||||||
@ -70,12 +67,6 @@ public class MetaDataPane extends AnchorPane implements Category.CategoryListene
|
|||||||
@FXML
|
@FXML
|
||||||
private ImageView imageView;
|
private ImageView imageView;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ResourceBundle resources;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private URL location;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableColumn<Pair<DrawableAttribute<?>, ? extends Object>, DrawableAttribute<?>> attributeColumn;
|
private TableColumn<Pair<DrawableAttribute<?>, ? extends Object>, DrawableAttribute<?>> attributeColumn;
|
||||||
|
|
||||||
@ -102,7 +93,7 @@ public class MetaDataPane extends AnchorPane implements Category.CategoryListene
|
|||||||
assert tableView != null : "fx:id=\"tableView\" was not injected: check your FXML file 'MetaDataPane.fxml'.";
|
assert tableView != null : "fx:id=\"tableView\" was not injected: check your FXML file 'MetaDataPane.fxml'.";
|
||||||
assert valueColumn != null : "fx:id=\"valueColumn\" was not injected: check your FXML file 'MetaDataPane.fxml'.";
|
assert valueColumn != null : "fx:id=\"valueColumn\" was not injected: check your FXML file 'MetaDataPane.fxml'.";
|
||||||
TagUtils.registerListener(this);
|
TagUtils.registerListener(this);
|
||||||
Category.registerListener(this);
|
ImageGalleryController.getDefault().getCategoryManager().registerListener(this);
|
||||||
|
|
||||||
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||||
tableView.setPlaceholder(new Label("Select a file to show its details here."));
|
tableView.setPlaceholder(new Label("Select a file to show its details here."));
|
||||||
@ -131,7 +122,7 @@ public class MetaDataPane extends AnchorPane implements Category.CategoryListene
|
|||||||
.filter((String t) -> t.startsWith(Category.CATEGORY_PREFIX) == false)
|
.filter((String t) -> t.startsWith(Category.CATEGORY_PREFIX) == false)
|
||||||
.collect(Collectors.joining(" ; ", "", "")));
|
.collect(Collectors.joining(" ; ", "", "")));
|
||||||
} else {
|
} else {
|
||||||
return new SimpleStringProperty(StringUtils.join((Collection<?>) p.getValue().getValue(), " ; "));
|
return new SimpleStringProperty(StringUtils.join((Iterable<?>) p.getValue().getValue(), " ; "));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
valueColumn.setPrefWidth(USE_COMPUTED_SIZE);
|
valueColumn.setPrefWidth(USE_COMPUTED_SIZE);
|
||||||
@ -192,15 +183,8 @@ public class MetaDataPane extends AnchorPane implements Category.CategoryListene
|
|||||||
try {
|
try {
|
||||||
file = controller.getFileFromId(fileID);
|
file = controller.getFileFromId(fileID);
|
||||||
updateUI();
|
updateUI();
|
||||||
|
|
||||||
file.categoryProperty().addListener(new ChangeListener<Category>() {
|
|
||||||
@Override
|
|
||||||
public void changed(ObservableValue<? extends Category> ov, Category t, final Category t1) {
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
Exceptions.printStackTrace(ex);
|
LOGGER.log(Level.WARNING, "Failed to get drawable file from ID", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -236,9 +220,11 @@ public class MetaDataPane extends AnchorPane implements Category.CategoryListene
|
|||||||
return imageBorder;
|
return imageBorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc } */
|
||||||
|
@Subscribe
|
||||||
@Override
|
@Override
|
||||||
public void handleCategoryChanged(Collection<Long> ids) {
|
public void handleCategoryChanged(CategoryChangeEvent evt) {
|
||||||
if (getFile() != null && ids.contains(getFileID())) {
|
if (getFile() != null && evt.getIds().contains(getFileID())) {
|
||||||
updateUI();
|
updateUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
* GroupPane. TODO: Extract a subclass for video files in slideshow mode-jm
|
* GroupPane. TODO: Extract a subclass for video files in slideshow mode-jm
|
||||||
* TODO: reduce coupling to GroupPane
|
* TODO: reduce coupling to GroupPane
|
||||||
*/
|
*/
|
||||||
public class SlideShowView extends SingleDrawableViewBase implements TagUtils.TagListener, Category.CategoryListener {
|
public class SlideShowView extends DrawableViewBase implements TagUtils.TagListener {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(SlideShowView.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(SlideShowView.class.getName());
|
||||||
|
|
||||||
@ -193,15 +193,17 @@ public class SlideShowView extends SingleDrawableViewBase implements TagUtils.Ta
|
|||||||
|
|
||||||
groupPane.grouping().addListener((Observable observable) -> {
|
groupPane.grouping().addListener((Observable observable) -> {
|
||||||
syncButtonVisibility();
|
syncButtonVisibility();
|
||||||
groupPane.getGrouping().fileIds().addListener((Observable observable1) -> {
|
if (groupPane.getGrouping() != null) {
|
||||||
syncButtonVisibility();
|
groupPane.getGrouping().fileIds().addListener((Observable observable1) -> {
|
||||||
});
|
syncButtonVisibility();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadType.ANY)
|
@ThreadConfined(type = ThreadType.ANY)
|
||||||
private void syncButtonVisibility() {
|
private void syncButtonVisibility() {
|
||||||
try{
|
try {
|
||||||
final boolean hasMultipleFiles = groupPane.getGrouping().fileIds().size() > 1;
|
final boolean hasMultipleFiles = groupPane.getGrouping().fileIds().size() > 1;
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
rightButton.setVisible(hasMultipleFiles);
|
rightButton.setVisible(hasMultipleFiles);
|
||||||
@ -209,7 +211,7 @@ public class SlideShowView extends SingleDrawableViewBase implements TagUtils.Ta
|
|||||||
rightButton.setManaged(hasMultipleFiles);
|
rightButton.setManaged(hasMultipleFiles);
|
||||||
leftButton.setManaged(hasMultipleFiles);
|
leftButton.setManaged(hasMultipleFiles);
|
||||||
});
|
});
|
||||||
} catch (NullPointerException ex){
|
} catch (NullPointerException ex) {
|
||||||
// The case has likely been closed
|
// The case has likely been closed
|
||||||
LOGGER.log(Level.WARNING, "Error accessing groupPane");
|
LOGGER.log(Level.WARNING, "Error accessing groupPane");
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
package org.sleuthkit.autopsy.imagegallery.gui;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
@ -34,27 +33,26 @@ import static javafx.scene.layout.Region.USE_COMPUTED_SIZE;
|
|||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.util.Pair;
|
import javafx.util.Pair;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
|
||||||
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.datamodel.Category;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryChangeEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays summary statistics (counts) for each group
|
* Displays summary statistics (counts) for each group
|
||||||
*/
|
*/
|
||||||
public class SummaryTablePane extends AnchorPane implements Category.CategoryListener {
|
public class SummaryTablePane extends AnchorPane {
|
||||||
|
|
||||||
private static SummaryTablePane instance;
|
private static SummaryTablePane instance;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableColumn<Pair<Category, Integer>, String> catColumn;
|
private TableColumn<Pair<Category, Long>, String> catColumn;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableColumn<Pair<Category, Integer>, Integer> countColumn;
|
private TableColumn<Pair<Category, Long>, Long> countColumn;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableView<Pair<Category, Integer>> tableView;
|
private TableView<Pair<Category, Long>> tableView;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void initialize() {
|
void initialize() {
|
||||||
@ -68,16 +66,16 @@ public class SummaryTablePane extends AnchorPane implements Category.CategoryLis
|
|||||||
tableView.prefHeightProperty().set(7 * 25);
|
tableView.prefHeightProperty().set(7 * 25);
|
||||||
|
|
||||||
//set up columns
|
//set up columns
|
||||||
catColumn.setCellValueFactory((TableColumn.CellDataFeatures<Pair<Category, Integer>, String> p) -> new SimpleObjectProperty<>(p.getValue().getKey().getDisplayName()));
|
catColumn.setCellValueFactory((TableColumn.CellDataFeatures<Pair<Category, Long>, String> p) -> new SimpleObjectProperty<>(p.getValue().getKey().getDisplayName()));
|
||||||
catColumn.setPrefWidth(USE_COMPUTED_SIZE);
|
catColumn.setPrefWidth(USE_COMPUTED_SIZE);
|
||||||
|
|
||||||
countColumn.setCellValueFactory((TableColumn.CellDataFeatures<Pair<Category, Integer>, Integer> p) -> new SimpleObjectProperty<>(p.getValue().getValue()));
|
countColumn.setCellValueFactory((TableColumn.CellDataFeatures<Pair<Category, Long>, Long> p) -> new SimpleObjectProperty<>(p.getValue().getValue()));
|
||||||
countColumn.setPrefWidth(USE_COMPUTED_SIZE);
|
countColumn.setPrefWidth(USE_COMPUTED_SIZE);
|
||||||
|
|
||||||
tableView.getColumns().setAll(Arrays.asList(catColumn, countColumn));
|
tableView.getColumns().setAll(Arrays.asList(catColumn, countColumn));
|
||||||
|
|
||||||
// //register for category events
|
// //register for category events
|
||||||
Category.registerListener(this);
|
ImageGalleryController.getDefault().getCategoryManager().registerListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SummaryTablePane() {
|
private SummaryTablePane() {
|
||||||
@ -94,16 +92,16 @@ public class SummaryTablePane extends AnchorPane implements Category.CategoryLis
|
|||||||
/**
|
/**
|
||||||
* listen to Category updates and rebuild the table
|
* listen to Category updates and rebuild the table
|
||||||
*/
|
*/
|
||||||
@Override
|
@Subscribe
|
||||||
public void handleCategoryChanged(Collection<Long> ids) {
|
public void handleCategoryChanged(CategoryChangeEvent evt) {
|
||||||
final ObservableList<Pair<Category, Integer>> data = FXCollections.observableArrayList();
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
final ObservableList<Pair<Category, Long>> data = FXCollections.observableArrayList();
|
||||||
if (Case.isCaseOpen()) {
|
if (Case.isCaseOpen()) {
|
||||||
for (Category cat : Category.values()) {
|
for (Category cat : Category.values()) {
|
||||||
try {
|
data.add(new Pair<>(cat, ImageGalleryController.getDefault().getCategoryManager().getCategoryCount(cat)));
|
||||||
data.add(new Pair<>(cat, ImageGalleryController.getDefault().getGroupManager().countFilesWithCategory(cat)));
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
Logger.getLogger(SummaryTablePane.class.getName()).log(Level.WARNING, "Error performing category file count");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
|
@ -18,9 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
package org.sleuthkit.autopsy.imagegallery.gui;
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.ResourceBundle;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.InvalidationListener;
|
import javafx.beans.InvalidationListener;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
@ -29,6 +27,7 @@ import javafx.beans.property.SimpleObjectProperty;
|
|||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
@ -44,9 +43,10 @@ import javax.swing.SortOrder;
|
|||||||
import org.openide.util.Exceptions;
|
import org.openide.util.Exceptions;
|
||||||
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
|
||||||
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
|
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.TagUtils;
|
import org.sleuthkit.autopsy.imagegallery.TagUtils;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
|
||||||
|
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.Category;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||||
import org.sleuthkit.autopsy.imagegallery.grouping.GroupSortBy;
|
import org.sleuthkit.autopsy.imagegallery.grouping.GroupSortBy;
|
||||||
@ -54,18 +54,12 @@ import org.sleuthkit.datamodel.TagName;
|
|||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for the ToolBar
|
* Controller for the ToolBar
|
||||||
*/
|
*/
|
||||||
public class Toolbar extends ToolBar {
|
public class Toolbar extends ToolBar {
|
||||||
|
|
||||||
private static final int SIZE_SLIDER_DEFAULT = 100;
|
private static final int SIZE_SLIDER_DEFAULT = 100;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ResourceBundle resources;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private URL location;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ComboBox<DrawableAttribute<?>> groupByBox;
|
private ComboBox<DrawableAttribute<?>> groupByBox;
|
||||||
|
|
||||||
@ -175,14 +169,14 @@ public class Toolbar extends ToolBar {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
catSelectedMenuButton.setOnAction(Category.FIVE.createSelCatMenuItem(catSelectedMenuButton).getOnAction());
|
catSelectedMenuButton.setOnAction(createSelCatMenuItem(Category.FIVE, catSelectedMenuButton).getOnAction());
|
||||||
catSelectedMenuButton.setText(Category.FIVE.getDisplayName());
|
catSelectedMenuButton.setText(Category.FIVE.getDisplayName());
|
||||||
catSelectedMenuButton.setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon()));
|
catSelectedMenuButton.setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon()));
|
||||||
catSelectedMenuButton.showingProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
|
catSelectedMenuButton.showingProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
|
||||||
if (t1) {
|
if (t1) {
|
||||||
ArrayList<MenuItem> categoryMenues = new ArrayList<>();
|
ArrayList<MenuItem> categoryMenues = new ArrayList<>();
|
||||||
for (final Category cat : Category.values()) {
|
for (final Category cat : Category.values()) {
|
||||||
MenuItem menuItem = cat.createSelCatMenuItem(catSelectedMenuButton);
|
MenuItem menuItem = createSelCatMenuItem(cat, catSelectedMenuButton);
|
||||||
categoryMenues.add(menuItem);
|
categoryMenues.add(menuItem);
|
||||||
}
|
}
|
||||||
catSelectedMenuButton.getItems().setAll(categoryMenues);
|
catSelectedMenuButton.getItems().setAll(categoryMenues);
|
||||||
@ -230,4 +224,17 @@ public class Toolbar extends ToolBar {
|
|||||||
private Toolbar() {
|
private Toolbar() {
|
||||||
FXMLConstructor.construct(this, "Toolbar.fxml");
|
FXMLConstructor.construct(this, "Toolbar.fxml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static MenuItem createSelCatMenuItem(Category cat, final SplitMenuButton catSelectedMenuButton) {
|
||||||
|
final MenuItem menuItem = new MenuItem(cat.getDisplayName(), new ImageView(DrawableAttribute.CATEGORY.getIcon()));
|
||||||
|
menuItem.setOnAction(new EventHandler<ActionEvent>() {
|
||||||
|
@Override
|
||||||
|
public void handle(ActionEvent t) {
|
||||||
|
new CategorizeAction().addTag(cat.getTagName(), "");
|
||||||
|
catSelectedMenuButton.setText(cat.getDisplayName());
|
||||||
|
catSelectedMenuButton.setOnAction(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return menuItem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,31 +28,30 @@ import javafx.scene.control.TreeItem;
|
|||||||
enum TreeNodeComparators implements Comparator<TreeItem<TreeNode>>, NonNullCompareable {
|
enum TreeNodeComparators implements Comparator<TreeItem<TreeNode>>, NonNullCompareable {
|
||||||
|
|
||||||
ALPHABETICAL("Group Name") {
|
ALPHABETICAL("Group Name") {
|
||||||
@Override
|
@Override
|
||||||
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
|
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
|
||||||
|
return o1.getValue().getGroup().groupKey.getValue().toString().compareTo(o2.getValue().getGroup().groupKey.getValue().toString());
|
||||||
return o1.getValue().getGroup().groupKey.getValue().toString().compareTo(o2.getValue().getGroup().groupKey.getValue().toString());
|
}
|
||||||
}
|
},
|
||||||
}, HIT_COUNT("Hit Count") {
|
HIT_COUNT("Hit Count") {
|
||||||
@Override
|
@Override
|
||||||
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
|
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
|
||||||
|
return -Long.compare(o1.getValue().getGroup().getHashSetHitsCount(), o2.getValue().getGroup().getHashSetHitsCount());
|
||||||
return -Integer.compare(o1.getValue().getGroup().getHashSetHitsCount(), o2.getValue().getGroup().getHashSetHitsCount());
|
}
|
||||||
}
|
},
|
||||||
}, FILE_COUNT("Group Size") {
|
FILE_COUNT("Group Size") {
|
||||||
@Override
|
@Override
|
||||||
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
|
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
|
||||||
|
return -Integer.compare(o1.getValue().getGroup().getSize(), o2.getValue().getGroup().getSize());
|
||||||
return -Integer.compare(o1.getValue().getGroup().getSize(), o2.getValue().getGroup().getSize());
|
}
|
||||||
}
|
},
|
||||||
}, HIT_FILE_RATIO("Hit Density") {
|
HIT_FILE_RATIO("Hit Density") {
|
||||||
@Override
|
@Override
|
||||||
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
|
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
|
||||||
|
return -Double.compare(o1.getValue().getGroup().getHashSetHitsCount() / (double) o1.getValue().getGroup().getSize(),
|
||||||
return -Double.compare(o1.getValue().getGroup().getHashSetHitsCount() / (double) o1.getValue().getGroup().getSize(),
|
o2.getValue().getGroup().getHashSetHitsCount() / (double) o2.getValue().getGroup().getSize());
|
||||||
o2.getValue().getGroup().getHashSetHitsCount() / (double) o2.getValue().getGroup().getSize());
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
|
public int compare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013-14 Basis Technology Corp.
|
* Copyright 2015 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");
|
Loading…
x
Reference in New Issue
Block a user