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

This commit is contained in:
Brian Carrier 2015-06-10 23:08:56 -04:00
commit f1b3dd5a94
21 changed files with 622 additions and 441 deletions

View File

@ -20,7 +20,6 @@ package org.sleuthkit.autopsy.imagegallery;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
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.ThreadConfined;
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.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.HashSetManager;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupManager;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog;
@ -124,6 +125,8 @@ public final class ImageGalleryController {
private DrawableDB db;
private final GroupManager groupManager = new GroupManager(this);
private final HashSetManager hashSetManager = new HashSetManager();
private final CategoryManager categoryManager = new CategoryManager();
private StackPane fullUIStackPane;
@ -341,7 +344,7 @@ public final class ImageGalleryController {
* @param theNewCase the case to configure the controller for
*/
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));
setStale(ImageGalleryModule.isDrawableDBStale(theNewCase));
@ -351,8 +354,9 @@ public final class ImageGalleryController {
restartWorker();
historyManager.clear();
groupManager.setDB(db);
db.initializeImageList();
SummaryTablePane.getDefault().handleCategoryChanged(Collections.emptyList());
hashSetManager.setDb(db);
categoryManager.setDb(db);
SummaryTablePane.getDefault().refresh();
}
/**
@ -381,7 +385,7 @@ public final class ImageGalleryController {
*
* @param innerTask
*/
public final void queueDBWorkerTask(InnerTask innerTask) {
public void queueDBWorkerTask(InnerTask innerTask) {
// @@@ We could make a lock for the worker thread
if (dbWorkerThread == null) {
@ -400,7 +404,7 @@ public final class ImageGalleryController {
Platform.runLater(this::checkForGroups);
}
public final ReadOnlyIntegerProperty getFileUpdateQueueSizeProperty() {
public ReadOnlyIntegerProperty getFileUpdateQueueSizeProperty() {
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...
//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.)
@ -640,7 +652,7 @@ public final class ImageGalleryController {
try {
DrawableFile<?> drawableFile = DrawableFile.create(getFile(), true, db.isVideoFile(getFile()));
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.
// We don't want to print out a ton of exceptions if this is the case.
if (Case.isCaseOpen()) {

View File

@ -162,6 +162,8 @@ public class ImageGalleryModule {
*
* @return true if the given file has a supported video mime type or
* extension, else false
*
* //TODO: convert this to use the new FileTypeDetector?
*/
public static boolean isVideoFile(AbstractFile file) {
try {

View File

@ -26,6 +26,7 @@ import java.util.logging.Level;
import javafx.event.ActionEvent;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javax.swing.JOptionPane;
import org.sleuthkit.autopsy.casemodule.Case;
@ -101,7 +102,7 @@ public class CategorizeAction extends AddTagAction {
final CategorizeAction categorizeAction = new CategorizeAction();
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);
}
}
@ -135,11 +136,11 @@ public class CategorizeAction extends AddTagAction {
//TODO: abandon using tags for categories and instead add a new column to DrawableDB
if (ct.getName().getDisplayName().startsWith(Category.CATEGORY_PREFIX)) {
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
Case.getCurrentCase().getServices().getTagsManager().addContentTag(file, tagName, comment); //tsk db
}

View File

@ -18,30 +18,19 @@
*/
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.Set;
import java.util.function.Function;
import java.util.logging.Level;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SplitMenuButton;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.scene.paint.Color;
import javax.annotation.concurrent.GuardedBy;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.TagUtils;
import org.sleuthkit.autopsy.imagegallery.actions.CategorizeAction;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
*
* Enum to represent the six categories in the DHs image categorization scheme.
*/
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)"),
FIVE(Color.GREEN, 5, "CAT-5, Non-pertinent");
final static private Map<String, Category> nameMap = new HashMap<>();
private static final List<Category> valuesList = Arrays.asList(values());
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));
}
/** map from displayName to enum value */
private static final Map<String, Category> nameMap
= Stream.of(values()).collect(Collectors.toMap(Category::getDisplayName,
Function.identity()));
public static final String CATEGORY_PREFIX = "CAT-";
private TagName tagName;
public static List<Category> valuesList() {
return valuesList;
public static Category fromDisplayName(String displayName) {
return nameMap.get(displayName);
}
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() {
return color;
@ -119,18 +96,12 @@ public enum Category implements Comparable<Category> {
return displayName;
}
static public Category fromDisplayName(String displayName) {
return nameMap.get(displayName);
}
private Category(Color color, int id, String name) {
this.color = color;
this.displayName = name;
this.id = id;
}
/**
* get the TagName used to store this Category in the main autopsy db.
*
* @return the TagName used for this Category
*/
public TagName getTagName() {
if (tagName == null) {
try {
tagName = TagUtils.getTagName(displayName);
@ -140,35 +111,4 @@ public enum Category implements Comparable<Category> {
}
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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-14 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* 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.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy;
import javax.swing.SortOrder;
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.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.SleuthkitCase;
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)
* from {@link SleuthkitCase}.
*
* TODO: Creating an abstract base class for sqlite databases* may make sense in
* the future. see also {@link EventsDB}
* TODO: Creating an abstract base class for sqlite databases may make sense in
* 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());
@ -127,9 +130,7 @@ public class DrawableDB {
*/
private final HashSet<FileUpdateEvent.FileUpdateListener> updateListeners = new HashSet<>();
private GroupManager manager;
private ImageGalleryController controller;
private GroupManager groupManager;
private final Path dbPath;
@ -146,6 +147,7 @@ public class DrawableDB {
LOGGER.log(Level.SEVERE, "Failed to load sqlite JDBC driver", ex);
}
}
private final SleuthkitCase tskCase;
//////////////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
*/
private DrawableDB(Path dbPath) throws SQLException, ExceptionInInitializerError, IOException {
private DrawableDB(Path dbPath, SleuthkitCase tskCase) throws SQLException, ExceptionInInitializerError, IOException {
this.dbPath = dbPath;
this.tskCase = tskCase;
Files.createDirectories(dbPath.getParent());
if (initializeDB()) {
if (initializeDBSchema()) {
updateFileStmt = prepareStatement(
"INSERT OR REPLACE INTO drawable_files (obj_id , path, name, created_time, modified_time, make, model, analyzed) "
+ "VALUES (?,?,?,?,?,?,?,?)");
@ -228,9 +230,11 @@ public class DrawableDB {
insertHashHitStmt = prepareStatement("insert or ignore into hash_set_hits (hash_set_id, obj_id) values (?,?)");
initializeImageList();
} else {
throw new ExceptionInInitializerError();
}
}
/**
@ -280,14 +284,10 @@ public class DrawableDB {
*
* @return
*/
public static DrawableDB getDrawableDB(Path dbPath, ImageGalleryController controller) {
public static DrawableDB getDrawableDB(Path dbPath, SleuthkitCase tskCase) {
try {
Path dbFilePath = dbPath.resolve("drawable.db");
DrawableDB drawableDB = new DrawableDB(dbFilePath);
drawableDB.controller = controller;
return drawableDB;
return new DrawableDB(dbPath.resolve("drawable.db"), tskCase);
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "sql error creating database connection", ex);
return null;
@ -339,7 +339,7 @@ public class DrawableDB {
* @return the number of rows in the table , count > 0 indicating an
* existing table
*/
private boolean initializeDB() {
private boolean initializeDBSchema() {
try {
if (isClosed()) {
openDBCon();
@ -954,7 +954,7 @@ public class DrawableDB {
*/
private DrawableFile<?> getFileFromID(Long id, boolean analyzed) throws TskCoreException {
try {
AbstractFile f = controller.getSleuthKitCase().getAbstractFileById(id);
AbstractFile f = tskCase.getAbstractFileById(id);
return DrawableFile.create(f, analyzed, isVideoFile(f));
} catch (IllegalStateException 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 {
try {
AbstractFile f = controller.getSleuthKitCase().getAbstractFileById(id);
AbstractFile f = tskCase.getAbstractFileById(id);
return DrawableFile.create(f,
areFilesAnalyzed(Collections.singleton(id)), isVideoFile(f));
} catch (IllegalStateException ex) {
@ -986,9 +986,9 @@ public class DrawableDB {
if (groupKey.getAttribute().isDBColumn) {
switch (groupKey.getAttribute().attrName) {
case CATEGORY:
return manager.getFileIDsWithCategory((Category) groupKey.getValue());
return groupManager.getFileIDsWithCategory((Category) groupKey.getValue());
case TAGS:
return manager.getFileIDsWithTag((TagName) groupKey.getValue());
return groupManager.getFileIDsWithTag((TagName) groupKey.getValue());
}
}
List<Long> files = new ArrayList<>();
@ -1129,45 +1129,24 @@ public class DrawableDB {
}
}
/*
* The following groups of functions are used to store information in memory
* instead of in the database. Due to the change listeners in the GUI,
* this data is requested many, many times when browsing the images, and
* especially when making any changes to things like categories.
/**
* For the given fileID, get the names of all the hashsets that the file is
* in.
*
* I don't like having multiple copies of the data, but these were causing
* major bottlenecks when they were all database lookups.
* @param fileID the fileID to file all the hash sets for
*
* TODO: factor these out to seperate classes such as HashSetHitCache or
* CategoryCountCache
* @return a set of names, each of which is a hashset that the given file is
* 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")
private final Map<Long, Set<String>> hashSetMap = new HashMap<>();
@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) {
@Nonnull
Set<String> getHashSetsForFile(long fileID) {
try {
List<BlackboardArtifact> arts = ImageGalleryController.getDefault().getSleuthKitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, id);
Set<String> hashNames = new HashSet<>();
List<BlackboardArtifact> arts = ImageGalleryController.getDefault().getSleuthKitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT, fileID);
for (BlackboardArtifact a : arts) {
List<BlackboardAttribute> attrs = a.getAttributes();
for (BlackboardAttribute attr : attrs) {
@ -1175,48 +1154,48 @@ public class DrawableDB {
hashNames.add(attr.getValueString());
}
}
return hashNames;
}
hashSetMap.put(id, hashNames);
} catch (IllegalStateException | TskCoreException ex) {
LOGGER.log(Level.WARNING, "could not access case during updateHashSetsForFile()", ex);
} catch (TskCoreException tskCoreException) {
throw new IllegalStateException(tskCoreException);
}
return Collections.emptySet();
}
/**
* For performance reasons, keep a list of all file IDs currently in the
* drawable database.
* Otherwise the database is queried many times to retrieve the same data.
* drawable database. Otherwise the database is queried many times to
* retrieve the same data.
*/
@GuardedBy("fileIDlist")
private final Set<Long> fileIDlist = new HashSet<>();
private final Set<Long> fileIDsInDB = new HashSet<>();
public boolean isDrawableFile(Long id) {
synchronized (fileIDlist) {
return fileIDlist.contains(id);
public boolean isInDB(Long id) {
synchronized (fileIDsInDB) {
return fileIDsInDB.contains(id);
}
}
public void addImageFileToList(Long id) {
synchronized (fileIDlist) {
fileIDlist.add(id);
private void addImageFileToList(Long id) {
synchronized (fileIDsInDB) {
fileIDsInDB.add(id);
}
}
public void removeImageFileFromList(Long id) {
synchronized (fileIDlist) {
fileIDlist.remove(id);
private void removeImageFileFromList(Long id) {
synchronized (fileIDsInDB) {
fileIDsInDB.remove(id);
}
}
public int getNumberOfImageFilesInList() {
synchronized (fileIDlist) {
return fileIDlist.size();
synchronized (fileIDsInDB) {
return fileIDsInDB.size();
}
}
public void initializeImageList() {
synchronized (fileIDlist) {
private void initializeImageList() {
synchronized (fileIDsInDB) {
dbReadLock();
try {
Statement stmt = con.createStatement();
@ -1224,7 +1203,6 @@ public class DrawableDB {
while (analyzedQuery.next()) {
addImageFileToList(analyzedQuery.getLong(OBJ_ID));
}
} catch (SQLException ex) {
LOGGER.log(Level.WARNING, "problem loading file IDs: ", ex);
} 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<Category, Integer> categoryCounts = new HashMap<>();
private final Map<AbstractFile, Boolean> videoFileMap = new ConcurrentHashMap<>();
public void incrementCategoryCount(Category cat) throws TskCoreException {
if (cat != Category.ZERO) {
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");
}
}
}
public boolean isVideoFile(AbstractFile f) {
return videoFileMap.computeIfAbsent(f, ImageGalleryModule::isVideoFile);
}
/**
* 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")
private final Map<Long, Boolean> videoFileMap = new HashMap<>();
public long getCategoryCount(Category cat) {
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 {
synchronized (videoFileMap) {
if (videoFileMap.containsKey(f.getId())) {
return videoFileMap.get(f.getId());
}
boolean isVideo = ImageGalleryModule.isVideoFile(f);
videoFileMap.put(f.getId(), isVideo);
return isVideo;
} catch (IllegalStateException ex) {
LOGGER.log(Level.WARNING, "Case closed while getting files");
} catch (TskCoreException ex1) {
LOGGER.log(Level.SEVERE, "Failed to get content tags by tag name.", ex1);
}
return -1;
}
/**

View File

@ -68,7 +68,7 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
return new ImageFile<>(abstractFileById, analyzed);
}
}
/**
* 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 {
return new ImageFile<>(abstractFileById, analyzed);
}
}
}
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) {
/* @TODO: the two 'new Integer(0).shortValue()' values and null are
* 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());
this.analyzed = new SimpleBooleanProperty(analyzed);
@ -115,9 +116,8 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
public abstract boolean isVideo();
synchronized public Collection<String> getHashHitSetNames() {
Collection<String> hashHitSetNames = ImageGalleryController.getDefault().getDatabase().getHashSetsForFile(getId());
return hashHitSetNames;
public Collection<String> getHashHitSetNames() {
return ImageGalleryController.getDefault().getHashSetManager().getHashSetsForFile(getId());
}
@Override
@ -290,10 +290,10 @@ public abstract class DrawableFile<T extends AbstractFile> extends AbstractFile
}
} catch (TskCoreException 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.
}
}
public abstract Image getThumbnail();

View File

@ -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);
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* 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();
//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);
synchronized public ObservableList<Long> fileIds() {
return fileIDs;
return FXCollections.unmodifiableObservableList(fileIDs);
}
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,
* so the hash counts may not longer be accurate.
* Call to indicate that an file has been added or removed from the group,
* so the hash counts may no longer be accurate.
*/
synchronized public void invalidateHashSetHitsCount() {
synchronized private void invalidateHashSetHitsCount() {
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) {
hashSetHitsCount = 0;
for (Long fileID : fileIds()) {
try {
if (ImageGalleryController.getDefault().getDatabase().isInHashSet(fileID)) {
hashSetHitsCount++;
}
} catch (IllegalStateException | NullPointerException ex) {
LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()");
break;
}
try {
hashSetHitsCount = fileIDs.stream()
.map(fileID -> ImageGalleryController.getDefault().getHashSetManager().isInAnyHashSet(fileID))
.filter(Boolean::booleanValue)
.count();
} catch (IllegalStateException | NullPointerException ex) {
LOGGER.log(Level.WARNING, "could not access case during getFilesWithHashSetHitsCount()");
}
}
return hashSetHitsCount;
}

View File

@ -78,7 +78,6 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
private DrawableDB db;
private final ImageGalleryController controller;
/**
* map from {@link GroupKey}s to {@link DrawableGroup}s. All groups (even
* not
@ -436,7 +435,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
switch (groupBy.attrName) {
//these cases get special treatment
case CATEGORY:
values = (List<A>) Category.valuesList();
values = (List<A>) Arrays.asList(Category.values());
break;
case TAGS:
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);
break;
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);
break;
default:
@ -457,8 +456,8 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
return values;
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "TSK error getting list of type " + groupBy.getDisplayName());
return new ArrayList<A>();
LOGGER.log(Level.WARNING, "TSK error getting list of type {0}", groupBy.getDisplayName());
return Collections.emptyList();
}
}
@ -490,7 +489,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
for (TagName tn : tns) {
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(tn);
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());
}
}
@ -502,8 +501,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
List<Long> files = new ArrayList<>();
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(category.getTagName());
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());
}
}
@ -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 {
try {
List<Long> files = new ArrayList<>();
List<ContentTag> contentTags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByTagName(tagName);
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());
}
@ -655,8 +638,6 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
if (checkAnalyzed != null) { // => the group is analyzed, so add it to the ui
populateAnalyzedGroup(gk, checkAnalyzed);
}
} else {
g.invalidateHashSetHitsCount();
}
}
}
@ -677,7 +658,7 @@ public class GroupManager implements FileUpdateEvent.FileUpdateListener {
*/
for (final long fileId : fileIDs) {
db.updateHashSetsForFile(fileId);
controller.getHashSetManager().invalidateHashSetsForFile(fileId);
//get grouping(s) this file would be in
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
Category.fireChange(fileIDs);
controller.getCategoryManager().fireChange(fileIDs);
if (evt.getChangedAttribute() == DrawableAttribute.TAGS) {
TagUtils.fireChange(fileIDs);

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-15 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,10 +18,9 @@
*/
package org.sleuthkit.autopsy.imagegallery.gui;
import java.net.URL;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.CacheHint;
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.imagegallery.FXMLConstructor;
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
* {@link GroupPane}'s TilePane or SlideShow.
*
*
* 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);
@ -56,12 +55,6 @@ public class DrawableTile extends SingleDrawableViewBase implements Category.Cat
@FXML
private ImageView imageView;
@FXML
private ResourceBundle resources;
@FXML
private URL location;
@Override
protected void disposeContent() {
//no-op
@ -99,11 +92,23 @@ public class DrawableTile extends SingleDrawableViewBase implements Category.Cat
}
@Override
@ThreadConfined(type = ThreadType.UI)
@ThreadConfined(type = ThreadType.JFX)
protected void clearContent() {
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
protected Runnable getContentUpdateRunnable() {
Image image = file.getThumbnail();

View File

@ -1,5 +1,6 @@
package org.sleuthkit.autopsy.imagegallery.gui;
import com.google.common.eventbus.Subscribe;
import java.util.Collection;
import java.util.logging.Level;
import javafx.application.Platform;
@ -14,13 +15,17 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.TagUtils;
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;
/**
* TODO: extract common interface out of {@link SingleImageView} and
* {@link MetaDataPane}
* Interface for classes that are views of a single DrawableFile. Implementation
* 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
static final int CAT_BORDER_WIDTH = 10;
@ -51,16 +56,24 @@ public interface DrawableView extends Category.CategoryListener, TagUtils.TagLis
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
void handleTagsChanged(Collection<Long> ids);
default boolean hasHashHit() {
try{
try {
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
// also trying to update it?
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() {
final Category category = getFile().getCategory();
final Border border = hasHashHit() && (category == Category.ZERO)
? HASH_BORDER
: DrawableView.getCategoryBorder(category);
? HASH_BORDER
: DrawableView.getCategoryBorder(category);
Platform.runLater(() -> {
getBorderable().setBorder(border);
});
return category;
}
}

View File

@ -19,6 +19,7 @@
*/
package org.sleuthkit.autopsy.imagegallery.gui;
import com.google.common.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.Collection;
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.CategorizeAction;
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.DrawableFile;
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
*
*/
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)));
@ -141,11 +142,11 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa
protected Long fileID;
/**
* the groupPane this {@link SingleDrawableViewBase} is embedded in
* the groupPane this {@link DrawableViewBase} is embedded in
*/
protected GroupPane groupPane;
protected SingleDrawableViewBase() {
protected DrawableViewBase() {
globalSelectionModel.getSelected().addListener((Observable observable) -> {
updateSelectionState();
@ -186,7 +187,7 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa
groupContextMenu.hide();
}
contextMenu = buildContextMenu();
contextMenu.show(SingleDrawableViewBase.this, t.getScreenX(), t.getScreenY());
contextMenu.show(DrawableViewBase.this, t.getScreenX(), t.getScreenY());
break;
}
@ -360,14 +361,14 @@ public abstract class SingleDrawableViewBase extends AnchorPane implements Drawa
disposeContent();
if (this.fileID == null || Case.isCaseOpen() == false) {
Category.unregisterListener(this);
ImageGalleryController.getDefault().getCategoryManager().unregisterListener(this);
TagUtils.unregisterListener(this);
file = null;
Platform.runLater(() -> {
clearContent();
});
} else {
Category.registerListener(this);
ImageGalleryController.getDefault().getCategoryManager().registerListener(this);
TagUtils.registerListener(this);
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);
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;
}
@Subscribe
@Override
public void handleCategoryChanged(Collection<Long> ids) {
if (ids.contains(fileID)) {
public void handleCategoryChanged(CategoryChangeEvent evt) {
if (evt.getIds().contains(fileID)) {
updateCategoryBorder();
}
}

View File

@ -85,6 +85,7 @@ import javafx.scene.paint.Color;
import javafx.util.Duration;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import org.apache.commons.lang3.StringUtils;
import org.controlsfx.control.GridCell;
import org.controlsfx.control.GridView;
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
* 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 {
@ -205,10 +211,8 @@ public class GroupPane extends BorderPane implements GroupView {
* to determine whether fileIDs are visible or are offscreen. No entry
* indicates the given fileID is not displayed on screen. DrawableCells are
* 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 InvalidationListener filesSyncListener = (observable) -> {
@ -298,7 +302,7 @@ public class GroupPane extends BorderPane implements GroupView {
/** create the string to display in the group header */
protected String getHeaderString() {
return isNull(getGrouping()) ? ""
: defaultIfBlank(getGrouping().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()) + " -- "
: StringUtils.defaultIfBlank(getGrouping().getGroupByValueDislpayName(), DrawableGroup.getBlankGroupName()) + " -- "
+ 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();
int selectedIndex = fileIds.indexOf(newFileID);
if (selectedIndex == -1) {
//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();
//[minIndex, maxIndex] is the range of indexes in the fileIDs list that are currently displayed
if (minIndex < 0 && maxIndex < 0) {
return;
}
if (selectedIndex < minIndex) {
scrollBar.decrement();
} else if (selectedIndex > maxIndex) {
@ -647,6 +648,7 @@ public class GroupPane extends BorderPane implements GroupView {
}
void resetItem() {
updateItem(null, true);
tile.setFile(null);
}
}

View File

@ -18,18 +18,15 @@
*/
package org.sleuthkit.autopsy.imagegallery.gui;
import com.google.common.eventbus.Subscribe;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
@ -46,20 +43,20 @@ import static javafx.scene.layout.Region.USE_COMPUTED_SIZE;
import javafx.scene.text.Text;
import javafx.util.Pair;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.TagUtils;
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.DrawableFile;
import org.sleuthkit.datamodel.TagName;
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());
@ -70,12 +67,6 @@ public class MetaDataPane extends AnchorPane implements Category.CategoryListene
@FXML
private ImageView imageView;
@FXML
private ResourceBundle resources;
@FXML
private URL location;
@FXML
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 valueColumn != null : "fx:id=\"valueColumn\" was not injected: check your FXML file 'MetaDataPane.fxml'.";
TagUtils.registerListener(this);
Category.registerListener(this);
ImageGalleryController.getDefault().getCategoryManager().registerListener(this);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
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)
.collect(Collectors.joining(" ; ", "", "")));
} else {
return new SimpleStringProperty(StringUtils.join((Collection<?>) p.getValue().getValue(), " ; "));
return new SimpleStringProperty(StringUtils.join((Iterable<?>) p.getValue().getValue(), " ; "));
}
});
valueColumn.setPrefWidth(USE_COMPUTED_SIZE);
@ -192,15 +183,8 @@ public class MetaDataPane extends AnchorPane implements Category.CategoryListene
try {
file = controller.getFileFromId(fileID);
updateUI();
file.categoryProperty().addListener(new ChangeListener<Category>() {
@Override
public void changed(ObservableValue<? extends Category> ov, Category t, final Category t1) {
updateUI();
}
});
} 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;
}
/** {@inheritDoc } */
@Subscribe
@Override
public void handleCategoryChanged(Collection<Long> ids) {
if (getFile() != null && ids.contains(getFileID())) {
public void handleCategoryChanged(CategoryChangeEvent evt) {
if (getFile() != null && evt.getIds().contains(getFileID())) {
updateUI();
}
}

View File

@ -64,7 +64,7 @@ import org.sleuthkit.datamodel.TskCoreException;
* GroupPane. TODO: Extract a subclass for video files in slideshow mode-jm
* 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());
@ -193,15 +193,17 @@ public class SlideShowView extends SingleDrawableViewBase implements TagUtils.Ta
groupPane.grouping().addListener((Observable observable) -> {
syncButtonVisibility();
groupPane.getGrouping().fileIds().addListener((Observable observable1) -> {
syncButtonVisibility();
});
if (groupPane.getGrouping() != null) {
groupPane.getGrouping().fileIds().addListener((Observable observable1) -> {
syncButtonVisibility();
});
}
});
}
@ThreadConfined(type = ThreadType.ANY)
private void syncButtonVisibility() {
try{
try {
final boolean hasMultipleFiles = groupPane.getGrouping().fileIds().size() > 1;
Platform.runLater(() -> {
rightButton.setVisible(hasMultipleFiles);
@ -209,7 +211,7 @@ public class SlideShowView extends SingleDrawableViewBase implements TagUtils.Ta
rightButton.setManaged(hasMultipleFiles);
leftButton.setManaged(hasMultipleFiles);
});
} catch (NullPointerException ex){
} catch (NullPointerException ex) {
// The case has likely been closed
LOGGER.log(Level.WARNING, "Error accessing groupPane");
}

View File

@ -18,9 +18,8 @@
*/
package org.sleuthkit.autopsy.imagegallery.gui;
import com.google.common.eventbus.Subscribe;
import java.util.Arrays;
import java.util.Collection;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
@ -34,27 +33,26 @@ import static javafx.scene.layout.Region.USE_COMPUTED_SIZE;
import javafx.scene.layout.VBox;
import javafx.util.Pair;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
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
*/
public class SummaryTablePane extends AnchorPane implements Category.CategoryListener {
public class SummaryTablePane extends AnchorPane {
private static SummaryTablePane instance;
@FXML
private TableColumn<Pair<Category, Integer>, String> catColumn;
private TableColumn<Pair<Category, Long>, String> catColumn;
@FXML
private TableColumn<Pair<Category, Integer>, Integer> countColumn;
private TableColumn<Pair<Category, Long>, Long> countColumn;
@FXML
private TableView<Pair<Category, Integer>> tableView;
private TableView<Pair<Category, Long>> tableView;
@FXML
void initialize() {
@ -68,16 +66,16 @@ public class SummaryTablePane extends AnchorPane implements Category.CategoryLis
tableView.prefHeightProperty().set(7 * 25);
//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);
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);
tableView.getColumns().setAll(Arrays.asList(catColumn, countColumn));
// //register for category events
Category.registerListener(this);
ImageGalleryController.getDefault().getCategoryManager().registerListener(this);
}
private SummaryTablePane() {
@ -94,16 +92,16 @@ public class SummaryTablePane extends AnchorPane implements Category.CategoryLis
/**
* listen to Category updates and rebuild the table
*/
@Override
public void handleCategoryChanged(Collection<Long> ids) {
final ObservableList<Pair<Category, Integer>> data = FXCollections.observableArrayList();
@Subscribe
public void handleCategoryChanged(CategoryChangeEvent evt) {
refresh();
}
public void refresh() {
final ObservableList<Pair<Category, Long>> data = FXCollections.observableArrayList();
if (Case.isCaseOpen()) {
for (Category cat : Category.values()) {
try {
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");
}
data.add(new Pair<>(cat, ImageGalleryController.getDefault().getCategoryManager().getCategoryCount(cat)));
}
}
Platform.runLater(() -> {

View File

@ -18,9 +18,7 @@
*/
package org.sleuthkit.autopsy.imagegallery.gui;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
@ -29,6 +27,7 @@ import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
@ -44,9 +43,10 @@ import javax.swing.SortOrder;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.imagegallery.FXMLConstructor;
import org.sleuthkit.autopsy.imagegallery.FileIDSelectionModel;
import org.sleuthkit.autopsy.imagegallery.ThumbnailCache;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
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.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.grouping.GroupSortBy;
@ -54,18 +54,12 @@ import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Controller for the ToolBar
* Controller for the ToolBar
*/
public class Toolbar extends ToolBar {
private static final int SIZE_SLIDER_DEFAULT = 100;
@FXML
private ResourceBundle resources;
@FXML
private URL location;
@FXML
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.setGraphic(new ImageView(DrawableAttribute.CATEGORY.getIcon()));
catSelectedMenuButton.showingProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
if (t1) {
ArrayList<MenuItem> categoryMenues = new ArrayList<>();
for (final Category cat : Category.values()) {
MenuItem menuItem = cat.createSelCatMenuItem(catSelectedMenuButton);
MenuItem menuItem = createSelCatMenuItem(cat, catSelectedMenuButton);
categoryMenues.add(menuItem);
}
catSelectedMenuButton.getItems().setAll(categoryMenues);
@ -230,4 +224,17 @@ public class Toolbar extends ToolBar {
private Toolbar() {
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;
}
}

View File

@ -28,31 +28,30 @@ import javafx.scene.control.TreeItem;
enum TreeNodeComparators implements Comparator<TreeItem<TreeNode>>, NonNullCompareable {
ALPHABETICAL("Group Name") {
@Override
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
return o1.getValue().getGroup().groupKey.getValue().toString().compareTo(o2.getValue().getGroup().groupKey.getValue().toString());
}
}, HIT_COUNT("Hit Count") {
@Override
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
return -Integer.compare(o1.getValue().getGroup().getHashSetHitsCount(), o2.getValue().getGroup().getHashSetHitsCount());
}
}, FILE_COUNT("Group Size") {
@Override
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
return -Integer.compare(o1.getValue().getGroup().getSize(), o2.getValue().getGroup().getSize());
}
}, HIT_FILE_RATIO("Hit Density") {
@Override
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
return -Double.compare(o1.getValue().getGroup().getHashSetHitsCount() / (double) o1.getValue().getGroup().getSize(),
o2.getValue().getGroup().getHashSetHitsCount() / (double) o2.getValue().getGroup().getSize());
}
};
@Override
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
return o1.getValue().getGroup().groupKey.getValue().toString().compareTo(o2.getValue().getGroup().groupKey.getValue().toString());
}
},
HIT_COUNT("Hit Count") {
@Override
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
return -Long.compare(o1.getValue().getGroup().getHashSetHitsCount(), o2.getValue().getGroup().getHashSetHitsCount());
}
},
FILE_COUNT("Group Size") {
@Override
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
return -Integer.compare(o1.getValue().getGroup().getSize(), o2.getValue().getGroup().getSize());
}
},
HIT_FILE_RATIO("Hit Density") {
@Override
public int nonNullCompare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {
return -Double.compare(o1.getValue().getGroup().getHashSetHitsCount() / (double) o1.getValue().getGroup().getSize(),
o2.getValue().getGroup().getHashSetHitsCount() / (double) o2.getValue().getGroup().getSize());
}
};
@Override
public int compare(TreeItem<TreeNode> o1, TreeItem<TreeNode> o2) {

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-14 Basis Technology Corp.
* Copyright 2015 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");