mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 07:56:16 +00:00
Merge pull request #4100 from millmanorama/1012-examiner_seen_UI_integration
1012 examiner seen ui integration
This commit is contained in:
commit
eb2193dd6f
@ -502,7 +502,7 @@ public final class ImageGalleryController {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileTask(AbstractFile f, DrawableDB taskDB) {
|
FileTask(AbstractFile f, DrawableDB taskDB) {
|
||||||
super();
|
super();
|
||||||
this.file = f;
|
this.file = f;
|
||||||
this.taskDB = taskDB;
|
this.taskDB = taskDB;
|
||||||
@ -562,13 +562,13 @@ public final class ImageGalleryController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NbBundle.Messages({"BulkTask.committingDb.status=committing image/video database",
|
|
||||||
"BulkTask.stopCopy.status=Stopping copy to drawable db task.",
|
|
||||||
"BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."})
|
|
||||||
/**
|
/**
|
||||||
* Base abstract class for various methods of copying image files data, for
|
* Base abstract class for various methods of copying image files data, for
|
||||||
* a given data source, into the Image gallery DB.
|
* a given data source, into the Image gallery DB.
|
||||||
*/
|
*/
|
||||||
|
@NbBundle.Messages({"BulkTask.committingDb.status=committing image/video database",
|
||||||
|
"BulkTask.stopCopy.status=Stopping copy to drawable db task.",
|
||||||
|
"BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."})
|
||||||
abstract static class BulkTransferTask extends BackgroundTask {
|
abstract static class BulkTransferTask extends BackgroundTask {
|
||||||
|
|
||||||
static private final String FILE_EXTENSION_CLAUSE
|
static private final String FILE_EXTENSION_CLAUSE
|
||||||
@ -581,15 +581,15 @@ public final class ImageGalleryController {
|
|||||||
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
|
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
|
||||||
+ "') ";
|
+ "') ";
|
||||||
|
|
||||||
final String DRAWABLE_QUERY;
|
private final String DRAWABLE_QUERY;
|
||||||
final String DATASOURCE_CLAUSE;
|
private final String DATASOURCE_CLAUSE;
|
||||||
|
|
||||||
final ImageGalleryController controller;
|
protected final ImageGalleryController controller;
|
||||||
final DrawableDB taskDB;
|
protected final DrawableDB taskDB;
|
||||||
final SleuthkitCase tskCase;
|
protected final SleuthkitCase tskCase;
|
||||||
final long dataSourceObjId;
|
protected final long dataSourceObjId;
|
||||||
|
|
||||||
ProgressHandle progressHandle;
|
private ProgressHandle progressHandle;
|
||||||
private boolean taskCompletionStatus;
|
private boolean taskCompletionStatus;
|
||||||
|
|
||||||
BulkTransferTask(long dataSourceObjId, ImageGalleryController controller) {
|
BulkTransferTask(long dataSourceObjId, ImageGalleryController controller) {
|
||||||
@ -612,11 +612,14 @@ public final class ImageGalleryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Do any cleanup for this task.
|
||||||
*
|
*
|
||||||
* @param success true if the transfer was successful
|
* @param success true if the transfer was successful
|
||||||
*/
|
*/
|
||||||
abstract void cleanup(boolean success);
|
abstract void cleanup(boolean success);
|
||||||
|
|
||||||
|
abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) throws TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a list of files to process.
|
* Gets a list of files to process.
|
||||||
*
|
*
|
||||||
@ -628,8 +631,6 @@ public final class ImageGalleryController {
|
|||||||
return tskCase.findAllFilesWhere(DRAWABLE_QUERY);
|
return tskCase.findAllFilesWhere(DRAWABLE_QUERY);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) throws TskCoreException;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
progressHandle = getInitialProgressHandle();
|
progressHandle = getInitialProgressHandle();
|
||||||
@ -775,16 +776,14 @@ public final class ImageGalleryController {
|
|||||||
* Copy files from a newly added data source into the DB. Get all "drawable"
|
* Copy files from a newly added data source into the DB. Get all "drawable"
|
||||||
* files, based on extension and mime-type. After ingest we use file type id
|
* files, based on extension and mime-type. After ingest we use file type id
|
||||||
* module and if necessary jpeg/png signature matching to add/remove files
|
* module and if necessary jpeg/png signature matching to add/remove files
|
||||||
*
|
|
||||||
* TODO: create methods to simplify progress value/text updates to both
|
|
||||||
* netbeans and ImageGallery progress/status
|
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({"PrePopulateDataSourceFiles.committingDb.status=committing image/video database"})
|
@NbBundle.Messages({"PrePopulateDataSourceFiles.committingDb.status=committing image/video database"})
|
||||||
static class PrePopulateDataSourceFiles extends BulkTransferTask {
|
static class PrePopulateDataSourceFiles extends BulkTransferTask {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @param dataSourceObjId The object ID of the DataSource that is being
|
||||||
* @param dataSourceId Data source object ID
|
* pre-populated into the DrawableDB.
|
||||||
|
* @param controller The controller for this task.
|
||||||
*/
|
*/
|
||||||
PrePopulateDataSourceFiles(long dataSourceObjId, ImageGalleryController controller) {
|
PrePopulateDataSourceFiles(long dataSourceObjId, ImageGalleryController controller) {
|
||||||
super(dataSourceObjId, controller);
|
super(dataSourceObjId, controller);
|
||||||
@ -805,5 +804,4 @@ public final class ImageGalleryController {
|
|||||||
return ProgressHandle.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this);
|
return ProgressHandle.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,8 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
|
|||||||
*/
|
*/
|
||||||
@NbBundle.Messages({
|
@NbBundle.Messages({
|
||||||
"NextUnseenGroup.markGroupSeen=Mark Group Seen",
|
"NextUnseenGroup.markGroupSeen=Mark Group Seen",
|
||||||
"NextUnseenGroup.nextUnseenGroup=Next Unseen group"})
|
"NextUnseenGroup.nextUnseenGroup=Next Unseen group",
|
||||||
|
"NextUnseenGroup.allGroupsSeen=All Groups Have Been Seen"})
|
||||||
public class NextUnseenGroup extends Action {
|
public class NextUnseenGroup extends Action {
|
||||||
|
|
||||||
private static final String IMAGE_PATH = "/org/sleuthkit/autopsy/imagegallery/images/"; //NON-NLS
|
private static final String IMAGE_PATH = "/org/sleuthkit/autopsy/imagegallery/images/"; //NON-NLS
|
||||||
@ -50,6 +51,7 @@ public class NextUnseenGroup extends Action {
|
|||||||
|
|
||||||
private static final String MARK_GROUP_SEEN = Bundle.NextUnseenGroup_markGroupSeen();
|
private static final String MARK_GROUP_SEEN = Bundle.NextUnseenGroup_markGroupSeen();
|
||||||
private static final String NEXT_UNSEEN_GROUP = Bundle.NextUnseenGroup_nextUnseenGroup();
|
private static final String NEXT_UNSEEN_GROUP = Bundle.NextUnseenGroup_nextUnseenGroup();
|
||||||
|
private static final String ALL_GROUPS_SEEN = Bundle.NextUnseenGroup_allGroupsSeen();
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
private final ObservableList<DrawableGroup> unSeenGroups;
|
private final ObservableList<DrawableGroup> unSeenGroups;
|
||||||
@ -63,6 +65,7 @@ public class NextUnseenGroup extends Action {
|
|||||||
groupManager = controller.getGroupManager();
|
groupManager = controller.getGroupManager();
|
||||||
unSeenGroups = groupManager.getUnSeenGroups();
|
unSeenGroups = groupManager.getUnSeenGroups();
|
||||||
unSeenGroups.addListener((Observable observable) -> updateButton());
|
unSeenGroups.addListener((Observable observable) -> updateButton());
|
||||||
|
controller.viewStateProperty().addListener((Observable observable) -> updateButton());
|
||||||
|
|
||||||
setEventHandler(event -> { //on fx-thread
|
setEventHandler(event -> { //on fx-thread
|
||||||
//if there is a group assigned to the view, mark it as seen
|
//if there is a group assigned to the view, mark it as seen
|
||||||
@ -88,16 +91,33 @@ public class NextUnseenGroup extends Action {
|
|||||||
|
|
||||||
private void updateButton() {
|
private void updateButton() {
|
||||||
int size = unSeenGroups.size();
|
int size = unSeenGroups.size();
|
||||||
Platform.runLater(() -> {
|
|
||||||
|
|
||||||
setDisabled(size == 0);
|
if (size < 1) {
|
||||||
if (size <= 1) {
|
//there are no unseen groups.
|
||||||
setText(MARK_GROUP_SEEN);
|
Platform.runLater(() -> {
|
||||||
setGraphic(new ImageView(END_IMAGE));
|
setDisabled(true);
|
||||||
|
setText(ALL_GROUPS_SEEN);
|
||||||
|
setGraphic(null);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
DrawableGroup get = unSeenGroups.get(0);
|
||||||
|
DrawableGroup orElse = Optional.ofNullable(controller.getViewState()).flatMap(GroupViewState::getGroup).orElse(null);
|
||||||
|
boolean equals = get.equals(orElse);
|
||||||
|
if (size == 1 & equals) {
|
||||||
|
//The only unseen group is the one that is being viewed.
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
setDisabled(false);
|
||||||
|
setText(MARK_GROUP_SEEN);
|
||||||
|
setGraphic(new ImageView(END_IMAGE));
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setText(NEXT_UNSEEN_GROUP);
|
//there are more unseen groups.
|
||||||
setGraphic(new ImageView(ADVANCE_IMAGE));
|
Platform.runLater(() -> {
|
||||||
|
setDisabled(false);
|
||||||
|
setText(NEXT_UNSEEN_GROUP);
|
||||||
|
setGraphic(new ImageView(ADVANCE_IMAGE));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,9 @@ import java.util.Map;
|
|||||||
import static java.util.Objects.isNull;
|
import static java.util.Objects.isNull;
|
||||||
import static java.util.Objects.nonNull;
|
import static java.util.Objects.nonNull;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
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;
|
||||||
@ -457,6 +459,7 @@ public final class DrawableDB {
|
|||||||
+ " value VARCHAR(255) not null, " //NON-NLS
|
+ " value VARCHAR(255) not null, " //NON-NLS
|
||||||
+ " attribute VARCHAR(255) not null, " //NON-NLS
|
+ " attribute VARCHAR(255) not null, " //NON-NLS
|
||||||
+ " UNIQUE(data_source_obj_id, value, attribute) )"; //NON-NLS
|
+ " UNIQUE(data_source_obj_id, value, attribute) )"; //NON-NLS
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().createTable(GROUPS_TABLENAME, tableSchema);
|
tskCase.getCaseDbAccessManager().createTable(GROUPS_TABLENAME, tableSchema);
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
logger.log(Level.SEVERE, "problem creating groups table", ex); //NON-NLS
|
logger.log(Level.SEVERE, "problem creating groups table", ex); //NON-NLS
|
||||||
@ -478,7 +481,7 @@ public final class DrawableDB {
|
|||||||
|
|
||||||
tskCase.getCaseDbAccessManager().createTable(GROUPS_SEEN_TABLENAME, tableSchema);
|
tskCase.getCaseDbAccessManager().createTable(GROUPS_SEEN_TABLENAME, tableSchema);
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
logger.log(Level.SEVERE, "problem creating groups_seen table", ex); //NON-NLS
|
logger.log(Level.SEVERE, "problem creating image_gallery_groups_seen table", ex); //NON-NLS
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,6 +629,26 @@ public final class DrawableDB {
|
|||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static private String getGroupIdQuery(GroupKey<?> groupKey) {
|
||||||
|
// query to find the group id from attribute/value
|
||||||
|
return String.format(" SELECT group_id FROM " + GROUPS_TABLENAME
|
||||||
|
+ " WHERE attribute = \'%s\' AND value = \'%s\' AND data_source_obj_id = %d",
|
||||||
|
groupKey.getAttribute().attrName.toString(),
|
||||||
|
groupKey.getValueDisplayName(),
|
||||||
|
(groupKey.getAttribute() == DrawableAttribute.PATH) ? groupKey.getDataSourceObjId() : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the specified group has been any examiner
|
||||||
|
*
|
||||||
|
* @param groupKey
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isGroupSeen(GroupKey<?> groupKey) {
|
||||||
|
return isGroupSeenByExaminer(groupKey, -1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the specified group has been seen by the specified
|
* Returns true if the specified group has been seen by the specified
|
||||||
* examiner
|
* examiner
|
||||||
@ -638,96 +661,34 @@ public final class DrawableDB {
|
|||||||
public boolean isGroupSeenByExaminer(GroupKey<?> groupKey, long examinerId) {
|
public boolean isGroupSeenByExaminer(GroupKey<?> groupKey, long examinerId) {
|
||||||
|
|
||||||
// Callback to process result of seen query
|
// Callback to process result of seen query
|
||||||
class GroupSeenQueryResultProcessor implements CaseDbAccessQueryCallback {
|
class GroupSeenQueryResultProcessor extends CompletableFuture<Boolean> implements CaseDbAccessQueryCallback {
|
||||||
|
|
||||||
private boolean seen = false;
|
|
||||||
|
|
||||||
boolean getGroupSeen() {
|
|
||||||
return seen;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process(ResultSet resultSet) {
|
public void process(ResultSet resultSet) {
|
||||||
try {
|
try {
|
||||||
if (resultSet != null) {
|
if (resultSet != null) {
|
||||||
while (resultSet.next()) {
|
while (resultSet.next()) {
|
||||||
seen = resultSet.getBoolean("seen"); //NON-NLS;
|
complete(resultSet.getInt("count") > 0); //NON-NLS;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
logger.log(Level.SEVERE, "failed to get group seen", ex); //NON-NLS
|
logger.log(Level.SEVERE, "Failed to get group seen", ex); //NON-NLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
|
|
||||||
// query to find the group id from attribute/value
|
|
||||||
String groupIdQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME
|
|
||||||
+ " WHERE attribute = \'%s\' AND value = \'%s\' )", groupKey.getAttribute().attrName.toString(), groupKey.getValueDisplayName());
|
|
||||||
|
|
||||||
String groupSeenQueryStmt = String.format("seen FROM " + GROUPS_SEEN_TABLENAME + " WHERE examiner_id = %d AND group_id in ( %s )", examinerId, groupIdQuery);
|
|
||||||
GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor();
|
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().select(groupSeenQueryStmt, queryResultProcessor);
|
|
||||||
return queryResultProcessor.getGroupSeen();
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
String msg = String.format("Failed to get is group seen for group key %s", groupKey.getValueDisplayName()); //NON-NLS
|
|
||||||
logger.log(Level.WARNING, msg, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the specified group has been any examiner
|
|
||||||
*
|
|
||||||
* @param groupKey
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public boolean isGroupSeen(GroupKey<?> groupKey) {
|
|
||||||
|
|
||||||
// Callback to process result of seen query
|
// Callback to process result of seen query
|
||||||
class GroupSeenQueryResultProcessor implements CaseDbAccessQueryCallback {
|
GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor();
|
||||||
|
|
||||||
private boolean seen = false;
|
|
||||||
|
|
||||||
boolean getGroupSeen() {
|
|
||||||
return seen;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void process(ResultSet resultSet) {
|
|
||||||
try {
|
|
||||||
if (resultSet != null) {
|
|
||||||
while (resultSet.next()) {
|
|
||||||
int count = resultSet.getInt("count");
|
|
||||||
seen = count > 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SQLException ex) {
|
|
||||||
logger.log(Level.SEVERE, "failed to get group seen", ex); //NON-NLS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
String groupSeenQueryStmt = "COUNT(*) as count FROM " + GROUPS_SEEN_TABLENAME
|
||||||
// query to find the group id from attribute/value
|
+ " WHERE seen = 1 "
|
||||||
String groupIdQuery = "";
|
+ " AND group_id in ( " + getGroupIdQuery(groupKey) + ")"
|
||||||
|
+ (examinerId > 0 ? " AND examiner_id = " + examinerId : "");// query to find the group id from attribute/value
|
||||||
groupIdQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME
|
|
||||||
+ " WHERE attribute = \'%s\' AND value = \'%s\' AND data_source_obj_id = %d)", groupKey.getAttribute().attrName.toString(), groupKey.getValueDisplayName(), (groupKey.getAttribute() == DrawableAttribute.PATH) ? groupKey.getDataSourceObjId() : 0);
|
|
||||||
|
|
||||||
String groupSeenQueryStmt = String.format("COUNT((*) as count FROM " + GROUPS_SEEN_TABLENAME + " WHERE seen = 1 AND group_id in ( %s )", groupIdQuery);
|
|
||||||
|
|
||||||
GroupSeenQueryResultProcessor queryResultProcessor = new GroupSeenQueryResultProcessor();
|
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().select(groupSeenQueryStmt, queryResultProcessor);
|
tskCase.getCaseDbAccessManager().select(groupSeenQueryStmt, queryResultProcessor);
|
||||||
return queryResultProcessor.getGroupSeen();
|
return queryResultProcessor.get();
|
||||||
} catch (TskCoreException ex) {
|
} catch (ExecutionException | InterruptedException | TskCoreException ex) {
|
||||||
String msg = String.format("Failed to get is group seen for group key %s", groupKey.getValueDisplayName()); //NON-NLS
|
String msg = String.format("Failed to get is group seen for group key %s", groupKey.getValueDisplayName()); //NON-NLS
|
||||||
logger.log(Level.WARNING, msg, ex);
|
logger.log(Level.WARNING, msg, ex);
|
||||||
}
|
}
|
||||||
@ -746,6 +707,7 @@ public final class DrawableDB {
|
|||||||
* @throws TskCoreException
|
* @throws TskCoreException
|
||||||
*/
|
*/
|
||||||
public void markGroupSeen(GroupKey<?> groupKey, boolean seen, long examinerID) throws TskCoreException {
|
public void markGroupSeen(GroupKey<?> groupKey, boolean seen, long examinerID) throws TskCoreException {
|
||||||
|
|
||||||
// query to find the group id from attribute/value
|
// query to find the group id from attribute/value
|
||||||
String innerQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME
|
String innerQuery = String.format("( SELECT group_id FROM " + GROUPS_TABLENAME
|
||||||
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d )",
|
+ " WHERE attribute = \'%s\' AND value = \'%s\' and data_source_obj_id = %d )",
|
||||||
@ -760,6 +722,7 @@ public final class DrawableDB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().insertOrUpdate(GROUPS_SEEN_TABLENAME, insertSQL);
|
tskCase.getCaseDbAccessManager().insertOrUpdate(GROUPS_SEEN_TABLENAME, insertSQL);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeFile(long id) {
|
public boolean removeFile(long id) {
|
||||||
@ -1175,7 +1138,7 @@ public final class DrawableDB {
|
|||||||
(A) results.getObject(groupBy.attrName.toString()));
|
(A) results.getObject(groupBy.attrName.toString()));
|
||||||
}
|
}
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
if (ex.getCause() instanceof java.lang.InterruptedException) {
|
if (!(ex.getCause() instanceof java.lang.InterruptedException)) {
|
||||||
|
|
||||||
/* It seems like this originaly comes out of c3p0 when
|
/* It seems like this originaly comes out of c3p0 when
|
||||||
* its thread is intereupted (cancelled because of
|
* its thread is intereupted (cancelled because of
|
||||||
@ -1185,7 +1148,6 @@ public final class DrawableDB {
|
|||||||
* see
|
* see
|
||||||
* https://sourceforge.net/p/c3p0/mailman/c3p0-users/thread/EBB32BB8-6487-43AF-B291-9464C9051869@mchange.com/
|
* https://sourceforge.net/p/c3p0/mailman/c3p0-users/thread/EBB32BB8-6487-43AF-B291-9464C9051869@mchange.com/
|
||||||
*/
|
*/
|
||||||
} else {
|
|
||||||
throw new TskCoreException("Unable to get values for attribute", ex); //NON-NLS
|
throw new TskCoreException("Unable to get values for attribute", ex); //NON-NLS
|
||||||
}
|
}
|
||||||
} catch (TskDataException ex) {
|
} catch (TskDataException ex) {
|
||||||
@ -1225,7 +1187,6 @@ public final class DrawableDB {
|
|||||||
if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
|
if (DbType.POSTGRESQL == tskCase.getDatabaseType()) {
|
||||||
insertSQL += "ON CONFLICT DO NOTHING";
|
insertSQL += "ON CONFLICT DO NOTHING";
|
||||||
}
|
}
|
||||||
|
|
||||||
tskCase.getCaseDbAccessManager().insert(GROUPS_TABLENAME, insertSQL, caseDbTransaction);
|
tskCase.getCaseDbAccessManager().insert(GROUPS_TABLENAME, insertSQL, caseDbTransaction);
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
// Don't need to report it if the case was closed
|
// Don't need to report it if the case was closed
|
||||||
|
@ -26,6 +26,7 @@ import java.util.logging.Level;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.DoubleBinding;
|
import javafx.beans.binding.DoubleBinding;
|
||||||
import javafx.beans.binding.IntegerBinding;
|
import javafx.beans.binding.IntegerBinding;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||||
import javafx.beans.property.ReadOnlyLongProperty;
|
import javafx.beans.property.ReadOnlyLongProperty;
|
||||||
import javafx.beans.property.ReadOnlyLongWrapper;
|
import javafx.beans.property.ReadOnlyLongWrapper;
|
||||||
@ -165,8 +166,8 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
|
|||||||
return seen.get();
|
return seen.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyBooleanWrapper seenProperty() {
|
public ReadOnlyBooleanProperty seenProperty() {
|
||||||
return seen;
|
return seen.getReadOnlyProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
@ -46,6 +46,7 @@ import java.util.logging.Level;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
import javafx.beans.property.ReadOnlyDoubleProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
@ -59,7 +60,6 @@ import javax.annotation.Nullable;
|
|||||||
import javax.annotation.concurrent.GuardedBy;
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import javax.swing.SortOrder;
|
import javax.swing.SortOrder;
|
||||||
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
|
||||||
import static org.apache.commons.lang3.ObjectUtils.notEqual;
|
import static org.apache.commons.lang3.ObjectUtils.notEqual;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||||
@ -99,7 +99,7 @@ public class GroupManager {
|
|||||||
|
|
||||||
/** An executor to submit async UI related background tasks to. */
|
/** An executor to submit async UI related background tasks to. */
|
||||||
private final ListeningExecutorService exec = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
|
private final ListeningExecutorService exec = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
|
||||||
new BasicThreadFactory.Builder().namingPattern("GUI Task -%d").build())); //NON-NLS
|
new BasicThreadFactory.Builder().namingPattern("GroupManager BG Thread-%d").build())); //NON-NLS
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
|
|
||||||
@ -127,6 +127,7 @@ public class GroupManager {
|
|||||||
private final ReadOnlyObjectWrapper< DrawableAttribute<?>> groupByProp = new ReadOnlyObjectWrapper<>(DrawableAttribute.PATH);
|
private final ReadOnlyObjectWrapper< DrawableAttribute<?>> groupByProp = new ReadOnlyObjectWrapper<>(DrawableAttribute.PATH);
|
||||||
private final ReadOnlyObjectWrapper<SortOrder> sortOrderProp = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING);
|
private final ReadOnlyObjectWrapper<SortOrder> sortOrderProp = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING);
|
||||||
private final ReadOnlyObjectWrapper<DataSource> dataSourceProp = new ReadOnlyObjectWrapper<>(null);//null indicates all datasources
|
private final ReadOnlyObjectWrapper<DataSource> dataSourceProp = new ReadOnlyObjectWrapper<>(null);//null indicates all datasources
|
||||||
|
private final ReadOnlyBooleanWrapper collaborativeModeProp = new ReadOnlyBooleanWrapper(false);
|
||||||
|
|
||||||
private final GroupingService regrouper;
|
private final GroupingService regrouper;
|
||||||
|
|
||||||
@ -241,23 +242,24 @@ public class GroupManager {
|
|||||||
*
|
*
|
||||||
* @return A ListenableFuture that encapsulates saving the seen state to the
|
* @return A ListenableFuture that encapsulates saving the seen state to the
|
||||||
* DB.
|
* DB.
|
||||||
|
*
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public ListenableFuture<?> markGroupSeen(DrawableGroup group, boolean seen) {
|
public ListenableFuture<?> markGroupSeen(DrawableGroup group, boolean seen) {
|
||||||
return exec.submit(() -> {
|
return exec.submit(() -> {
|
||||||
try {
|
try {
|
||||||
Examiner examiner = controller.getSleuthKitCase().getCurrentExaminer();
|
Examiner examiner = controller.getSleuthKitCase().getCurrentExaminer();
|
||||||
|
|
||||||
getDrawableDB().markGroupSeen(group.getGroupKey(), seen, examiner.getId());
|
getDrawableDB().markGroupSeen(group.getGroupKey(), seen, examiner.getId());
|
||||||
group.setSeen(seen);
|
group.setSeen(seen);
|
||||||
updateUnSeenGroups(group, seen);
|
updateUnSeenGroups(group);
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
logger.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS
|
logger.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized private void updateUnSeenGroups(DrawableGroup group, boolean seen) {
|
synchronized private void updateUnSeenGroups(DrawableGroup group) {
|
||||||
if (seen) {
|
if (group.isSeen()) {
|
||||||
unSeenGroups.removeAll(group);
|
unSeenGroups.removeAll(group);
|
||||||
} else if (unSeenGroups.contains(group) == false) {
|
} else if (unSeenGroups.contains(group) == false) {
|
||||||
unSeenGroups.add(group);
|
unSeenGroups.add(group);
|
||||||
@ -579,14 +581,14 @@ public class GroupManager {
|
|||||||
try {
|
try {
|
||||||
Set<Long> fileIDs = getFileIDsInGroup(groupKey);
|
Set<Long> fileIDs = getFileIDsInGroup(groupKey);
|
||||||
if (Objects.nonNull(fileIDs)) {
|
if (Objects.nonNull(fileIDs)) {
|
||||||
|
|
||||||
|
long examinerID = collaborativeModeProp.get() ? -1 : controller.getSleuthKitCase().getCurrentExaminer().getId();
|
||||||
|
final boolean groupSeen = getDrawableDB().isGroupSeenByExaminer(groupKey, examinerID);
|
||||||
DrawableGroup group;
|
DrawableGroup group;
|
||||||
|
|
||||||
Examiner examiner = controller.getSleuthKitCase().getCurrentExaminer();
|
|
||||||
|
|
||||||
final boolean groupSeen = getDrawableDB().isGroupSeenByExaminer(groupKey, examiner.getId());
|
|
||||||
if (groupMap.containsKey(groupKey)) {
|
if (groupMap.containsKey(groupKey)) {
|
||||||
group = groupMap.get(groupKey);
|
group = groupMap.get(groupKey);
|
||||||
group.setFiles(ObjectUtils.defaultIfNull(fileIDs, Collections.emptySet()));
|
group.setFiles(fileIDs);
|
||||||
group.setSeen(groupSeen);
|
group.setSeen(groupSeen);
|
||||||
} else {
|
} else {
|
||||||
group = new DrawableGroup(groupKey, fileIDs, groupSeen);
|
group = new DrawableGroup(groupKey, fileIDs, groupSeen);
|
||||||
@ -598,10 +600,9 @@ public class GroupManager {
|
|||||||
analyzedGroups.add(group);
|
analyzedGroups.add(group);
|
||||||
sortAnalyzedGroups();
|
sortAnalyzedGroups();
|
||||||
}
|
}
|
||||||
updateUnSeenGroups(group, groupSeen);
|
updateUnSeenGroups(group);
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
logger.log(Level.SEVERE, "failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex); //NON-NLS
|
logger.log(Level.SEVERE, "failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex); //NON-NLS
|
||||||
@ -634,9 +635,34 @@ public class GroupManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized public void setCollaborativeMode(Boolean newValue) {
|
||||||
|
collaborativeModeProp.set(newValue);
|
||||||
|
analyzedGroups.forEach(group -> {
|
||||||
|
try {
|
||||||
|
boolean groupSeenByExaminer = getDrawableDB().isGroupSeenByExaminer(
|
||||||
|
group.getGroupKey(),
|
||||||
|
newValue ? -1 : controller.getSleuthKitCase().getCurrentExaminer().getId()
|
||||||
|
);
|
||||||
|
group.setSeen(groupSeenByExaminer);
|
||||||
|
updateUnSeenGroups(group);
|
||||||
|
if (group.isSeen()) {
|
||||||
|
unSeenGroups.removeAll(group);
|
||||||
|
} else if (unSeenGroups.contains(group) == false) {
|
||||||
|
unSeenGroups.add(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Error checking seen state of group.", ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sortUnseenGroups();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task to query database for files in sorted groups and build
|
* Task to query database for files in sorted groups and build
|
||||||
* DrawableGroups for them.
|
* DrawableGroups for them.
|
||||||
|
*
|
||||||
|
* @param <AttrValType> The type of the values that this task will group by.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
@NbBundle.Messages({"# {0} - groupBy attribute Name",
|
@NbBundle.Messages({"# {0} - groupBy attribute Name",
|
||||||
|
@ -1,888 +0,0 @@
|
|||||||
/*
|
|
||||||
* Autopsy Forensic Browser
|
|
||||||
*
|
|
||||||
* Copyright 2013-18 Basis Technology Corp.
|
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.sleuthkit.autopsy.imagegallery.datamodel.grouping;
|
|
||||||
|
|
||||||
import com.google.common.collect.HashMultimap;
|
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
|
||||||
import java.sql.ResultSet;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import static java.util.Objects.isNull;
|
|
||||||
import static java.util.Objects.nonNull;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.concurrent.CancellationException;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.property.ReadOnlyDoubleProperty;
|
|
||||||
import javafx.beans.property.ReadOnlyDoubleWrapper;
|
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
|
||||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import static javafx.concurrent.Worker.State.CANCELLED;
|
|
||||||
import static javafx.concurrent.Worker.State.FAILED;
|
|
||||||
import static javafx.concurrent.Worker.State.READY;
|
|
||||||
import static javafx.concurrent.Worker.State.RUNNING;
|
|
||||||
import static javafx.concurrent.Worker.State.SCHEDULED;
|
|
||||||
import static javafx.concurrent.Worker.State.SUCCEEDED;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.annotation.concurrent.GuardedBy;
|
|
||||||
import javax.swing.SortOrder;
|
|
||||||
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
|
||||||
import org.netbeans.api.progress.ProgressHandle;
|
|
||||||
import org.openide.util.Exceptions;
|
|
||||||
import org.openide.util.NbBundle;
|
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
|
||||||
import org.sleuthkit.autopsy.coreutils.LoggedTask;
|
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
|
||||||
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
|
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
|
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
|
||||||
import org.sleuthkit.datamodel.ContentTag;
|
|
||||||
import org.sleuthkit.datamodel.DataSource;
|
|
||||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
|
||||||
import org.sleuthkit.datamodel.TagName;
|
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
|
||||||
import org.sleuthkit.datamodel.TskData.DbType;
|
|
||||||
import org.sleuthkit.datamodel.TskDataException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides an abstraction layer on top of DrawableDB ( and to some extent
|
|
||||||
* SleuthkitCase ) to facilitate creation, retrieval, updating, and sorting of
|
|
||||||
* DrawableGroups.
|
|
||||||
*/
|
|
||||||
public class GroupManager {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(GroupManager.class.getName());
|
|
||||||
|
|
||||||
/** An executor to submit async UI related background tasks to. */
|
|
||||||
private final ListeningExecutorService exec = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
|
|
||||||
new BasicThreadFactory.Builder().namingPattern("GUI Task -%d").build())); //NON-NLS
|
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
|
||||||
|
|
||||||
/** list of all analyzed groups */
|
|
||||||
@GuardedBy("this")
|
|
||||||
private final ObservableList<DrawableGroup> analyzedGroups = FXCollections.observableArrayList();
|
|
||||||
private final ObservableList<DrawableGroup> unmodifiableAnalyzedGroups = FXCollections.unmodifiableObservableList(analyzedGroups);
|
|
||||||
|
|
||||||
/** list of unseen groups */
|
|
||||||
@GuardedBy("this")
|
|
||||||
private final ObservableList<DrawableGroup> unSeenGroups = FXCollections.observableArrayList();
|
|
||||||
private final ObservableList<DrawableGroup> unmodifiableUnSeenGroups = FXCollections.unmodifiableObservableList(unSeenGroups);
|
|
||||||
/**
|
|
||||||
* map from GroupKey} to DrawableGroupSs. All groups (even not fully
|
|
||||||
* analyzed or not visible groups could be in this map
|
|
||||||
*/
|
|
||||||
@GuardedBy("this")
|
|
||||||
private final Map<GroupKey<?>, DrawableGroup> groupMap = new HashMap<>();
|
|
||||||
|
|
||||||
@GuardedBy("this")
|
|
||||||
private ReGroupTask<?> groupByTask;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* --- current grouping/sorting attributes ---
|
|
||||||
*/
|
|
||||||
@GuardedBy("this")
|
|
||||||
private final ReadOnlyObjectWrapper< GroupSortBy> sortByProp = new ReadOnlyObjectWrapper<>(GroupSortBy.PRIORITY);
|
|
||||||
private final ReadOnlyObjectWrapper< DrawableAttribute<?>> groupByProp = new ReadOnlyObjectWrapper<>(DrawableAttribute.PATH);
|
|
||||||
private final ReadOnlyObjectWrapper<SortOrder> sortOrderProp = new ReadOnlyObjectWrapper<>(SortOrder.ASCENDING);
|
|
||||||
private final ReadOnlyObjectWrapper<DataSource> dataSourceProp = new ReadOnlyObjectWrapper<>(null);//null indicates all datasources
|
|
||||||
|
|
||||||
private final ReadOnlyDoubleWrapper regroupProgress = new ReadOnlyDoubleWrapper();
|
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
synchronized DrawableDB getDB() {
|
|
||||||
=======
|
|
||||||
public void setDB(DrawableDB db) {
|
|
||||||
regroup(dataSource, groupBy, sortBy, sortOrder, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DrawableDB getDB() {
|
|
||||||
>>>>>>> 1010/7-datasource-filtering-test
|
|
||||||
return controller.getDatabase();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ReturnOfCollectionOrArrayField")
|
|
||||||
public ObservableList<DrawableGroup> getAnalyzedGroups() {
|
|
||||||
return unmodifiableAnalyzedGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ReturnOfCollectionOrArrayField")
|
|
||||||
public ObservableList<DrawableGroup> getUnSeenGroups() {
|
|
||||||
return unmodifiableUnSeenGroups;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* construct a group manager hooked up to the given db and controller
|
|
||||||
*
|
|
||||||
* @param controller
|
|
||||||
*/
|
|
||||||
public GroupManager(ImageGalleryController controller) {
|
|
||||||
this.controller = controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Using the current groupBy set for this manager, find groupkeys for all
|
|
||||||
* the groups the given file is a part of
|
|
||||||
*
|
|
||||||
* @param file
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return A a set of GroupKeys representing the group(s) the given file is
|
|
||||||
* a part of.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
||||||
synchronized public Set<GroupKey<?>> getGroupKeysForFile(DrawableFile file) {
|
|
||||||
Set<GroupKey<?>> resultSet = new HashSet<>();
|
|
||||||
for (Comparable<?> val : getGroupBy().getValue(file)) {
|
|
||||||
if (getGroupBy() == DrawableAttribute.TAGS) {
|
|
||||||
if (CategoryManager.isNotCategoryTagName((TagName) val)) {
|
|
||||||
resultSet.add(new GroupKey(getGroupBy(), val, getDataSource()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resultSet.add(new GroupKey(getGroupBy(), val, getDataSource()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resultSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Using the current grouping paramaters set for this manager, find
|
|
||||||
* GroupKeys for all the Groups the given file is a part of.
|
|
||||||
*
|
|
||||||
* @param fileID The Id of the file to get group keys for.
|
|
||||||
*
|
|
||||||
* @return A set of GroupKeys representing the group(s) the given file is a
|
|
||||||
* part of
|
|
||||||
*/
|
|
||||||
synchronized public Set<GroupKey<?>> getGroupKeysForFileID(Long fileID) {
|
|
||||||
try {
|
|
||||||
DrawableDB db = getDB();
|
|
||||||
if (nonNull(db)) {
|
|
||||||
DrawableFile file = db.getFileFromID(fileID);
|
|
||||||
return getGroupKeysForFile(file);
|
|
||||||
} else {
|
|
||||||
Logger.getLogger(GroupManager.class.getName()).log(Level.WARNING, "Failed to load file with id: {0} from database. There is no database assigned.", fileID); //NON-NLS
|
|
||||||
}
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
Logger.getLogger(GroupManager.class.getName()).log(Level.SEVERE, "failed to load file with id: " + fileID + " from database", ex); //NON-NLS
|
|
||||||
}
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param groupKey
|
|
||||||
*
|
|
||||||
* @return return the DrawableGroup (if it exists) for the given GroupKey,
|
|
||||||
* or null if no group exists for that key.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
synchronized public DrawableGroup getGroupForKey(@Nonnull GroupKey<?> groupKey) {
|
|
||||||
return groupMap.get(groupKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public void reset() {
|
|
||||||
if (groupByTask != null) {
|
|
||||||
groupByTask.cancel(true);
|
|
||||||
}
|
|
||||||
setSortBy(GroupSortBy.GROUP_BY_VALUE);
|
|
||||||
setGroupBy(DrawableAttribute.PATH);
|
|
||||||
setSortOrder(SortOrder.ASCENDING);
|
|
||||||
setDataSource(null);
|
|
||||||
|
|
||||||
unSeenGroups.forEach(controller.getCategoryManager()::unregisterListener);
|
|
||||||
unSeenGroups.clear();
|
|
||||||
analyzedGroups.forEach(controller.getCategoryManager()::unregisterListener);
|
|
||||||
analyzedGroups.clear();
|
|
||||||
|
|
||||||
groupMap.values().forEach(controller.getCategoryManager()::unregisterListener);
|
|
||||||
groupMap.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public boolean isRegrouping() {
|
|
||||||
if (groupByTask == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (groupByTask.getState()) {
|
|
||||||
case READY:
|
|
||||||
case RUNNING:
|
|
||||||
case SCHEDULED:
|
|
||||||
return true;
|
|
||||||
case CANCELLED:
|
|
||||||
case FAILED:
|
|
||||||
case SUCCEEDED:
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 'Save' the given group as seen in the drawable db.
|
|
||||||
*
|
|
||||||
* @param group The DrawableGroup to mark as seen.
|
|
||||||
* @param seen The seen state to set for the given group.
|
|
||||||
*
|
|
||||||
* @return A ListenableFuture that encapsulates saving the seen state to the
|
|
||||||
* DB.
|
|
||||||
*/
|
|
||||||
synchronized public ListenableFuture<?> setGroupSeen(DrawableGroup group, boolean seen) {
|
|
||||||
DrawableDB db = getDB();
|
|
||||||
if (nonNull(db)) {
|
|
||||||
return exec.submit(() -> {
|
|
||||||
try {
|
|
||||||
|
|
||||||
db.setGroupSeen(group.getGroupKey(), seen);
|
|
||||||
group.setSeen(seen);
|
|
||||||
updateUnSeenGroups(group, seen);
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
logger.log(Level.SEVERE, "Error marking group as seen", ex); //NON-NLS
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Futures.immediateFuture(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized private void updateUnSeenGroups(DrawableGroup group, boolean seen) {
|
|
||||||
if (seen) {
|
|
||||||
unSeenGroups.removeAll(group);
|
|
||||||
} else if (unSeenGroups.contains(group) == false) {
|
|
||||||
unSeenGroups.add(group);
|
|
||||||
}
|
|
||||||
sortUnseenGroups();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* remove the given file from the group with the given key. If the group
|
|
||||||
* doesn't exist or doesn't already contain this file, this method is a
|
|
||||||
* no-op
|
|
||||||
*
|
|
||||||
* @param groupKey the value of groupKey
|
|
||||||
* @param fileID the value of file
|
|
||||||
*
|
|
||||||
* @return The DrawableGroup the file was removed from.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public synchronized DrawableGroup removeFromGroup(GroupKey<?> groupKey, final Long fileID) {
|
|
||||||
//get grouping this file would be in
|
|
||||||
final DrawableGroup group = getGroupForKey(groupKey);
|
|
||||||
if (group != null) {
|
|
||||||
synchronized (group) {
|
|
||||||
group.removeFile(fileID);
|
|
||||||
|
|
||||||
// If we're grouping by category, we don't want to remove empty groups.
|
|
||||||
if (groupKey.getAttribute() != DrawableAttribute.CATEGORY) {
|
|
||||||
if (group.getFileIDs().isEmpty()) {
|
|
||||||
if (analyzedGroups.contains(group)) {
|
|
||||||
analyzedGroups.remove(group);
|
|
||||||
sortAnalyzedGroups();
|
|
||||||
}
|
|
||||||
if (unSeenGroups.contains(group)) {
|
|
||||||
unSeenGroups.remove(group);
|
|
||||||
sortUnseenGroups();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
} else { //group == null
|
|
||||||
// It may be that this was the last unanalyzed file in the group, so test
|
|
||||||
// whether the group is now fully analyzed.
|
|
||||||
return popuplateIfAnalyzed(groupKey, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized private void sortUnseenGroups() {
|
|
||||||
FXCollections.sort(unSeenGroups, makeGroupComparator(getSortOrder(), getSortBy()));
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized private void sortAnalyzedGroups() {
|
|
||||||
FXCollections.sort(analyzedGroups, makeGroupComparator(getSortOrder(), getSortBy()));
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public Set<Long> getFileIDsInGroup(GroupKey<?> groupKey) throws TskCoreException {
|
|
||||||
Set<Long> fileIDsToReturn = Collections.emptySet();
|
|
||||||
switch (groupKey.getAttribute().attrName) {
|
|
||||||
//these cases get special treatment
|
|
||||||
case CATEGORY:
|
|
||||||
fileIDsToReturn = getFileIDsWithCategory((DhsImageCategory) groupKey.getValue());
|
|
||||||
break;
|
|
||||||
case TAGS:
|
|
||||||
fileIDsToReturn = getFileIDsWithTag((TagName) groupKey.getValue());
|
|
||||||
break;
|
|
||||||
case MIME_TYPE:
|
|
||||||
fileIDsToReturn = getFileIDsWithMimeType((String) groupKey.getValue());
|
|
||||||
break;
|
|
||||||
// case HASHSET: //comment out this case to use db functionality for hashsets
|
|
||||||
// return getFileIDsWithHashSetName((String) groupKey.getValue());
|
|
||||||
default:
|
|
||||||
DrawableDB db = getDB();
|
|
||||||
//straight db query
|
|
||||||
if (nonNull(db)) {
|
|
||||||
fileIDsToReturn = db.getFileIDsInGroup(groupKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileIDsToReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @@@ This was kind of slow in the profiler. Maybe we should cache it.
|
|
||||||
// Unless the list of file IDs is necessary, use countFilesWithCategory() to get the counts.
|
|
||||||
synchronized public Set<Long> getFileIDsWithCategory(DhsImageCategory category) throws TskCoreException {
|
|
||||||
Set<Long> fileIDsToReturn = Collections.emptySet();
|
|
||||||
DrawableDB db = getDB();
|
|
||||||
if (nonNull(db)) {
|
|
||||||
try {
|
|
||||||
final DrawableTagsManager tagsManager = controller.getTagsManager();
|
|
||||||
if (category == DhsImageCategory.ZERO) {
|
|
||||||
List< TagName> tns = Stream.of(DhsImageCategory.ONE, DhsImageCategory.TWO,
|
|
||||||
DhsImageCategory.THREE, DhsImageCategory.FOUR, DhsImageCategory.FIVE)
|
|
||||||
.map(tagsManager::getTagName)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
Set<Long> files = new HashSet<>();
|
|
||||||
for (TagName tn : tns) {
|
|
||||||
if (tn != null) {
|
|
||||||
List<ContentTag> contentTags = tagsManager.getContentTagsByTagName(tn);
|
|
||||||
files.addAll(contentTags.stream()
|
|
||||||
.filter(ct -> ct.getContent() instanceof AbstractFile)
|
|
||||||
.filter(ct -> db.isInDB(ct.getContent().getId()))
|
|
||||||
.map(ct -> ct.getContent().getId())
|
|
||||||
.collect(Collectors.toSet()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileIDsToReturn = db.findAllFileIdsWhere("obj_id NOT IN (" + StringUtils.join(files, ',') + ")"); //NON-NLS
|
|
||||||
} else {
|
|
||||||
|
|
||||||
List<ContentTag> contentTags = tagsManager.getContentTagsByTagName(tagsManager.getTagName(category));
|
|
||||||
fileIDsToReturn = contentTags.stream()
|
|
||||||
.filter(ct -> ct.getContent() instanceof AbstractFile)
|
|
||||||
.filter(ct -> db.isInDB(ct.getContent().getId()))
|
|
||||||
.map(ct -> ct.getContent().getId())
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
logger.log(Level.WARNING, "TSK error getting files in Category:" + category.getDisplayName(), ex); //NON-NLS
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileIDsToReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public Set<Long> getFileIDsWithTag(TagName tagName) throws TskCoreException {
|
|
||||||
Set<Long> files = new HashSet<>();
|
|
||||||
try {
|
|
||||||
|
|
||||||
List<ContentTag> contentTags = controller.getTagsManager().getContentTagsByTagName(tagName);
|
|
||||||
DrawableDB db = getDB();
|
|
||||||
for (ContentTag ct : contentTags) {
|
|
||||||
if (ct.getContent() instanceof AbstractFile && nonNull(db) && db.isInDB(ct.getContent().getId())) {
|
|
||||||
files.add(ct.getContent().getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files;
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
logger.log(Level.WARNING, "TSK error getting files with Tag:" + tagName.getDisplayName(), ex); //NON-NLS
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized GroupSortBy getSortBy() {
|
|
||||||
return sortByProp.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void setSortBy(GroupSortBy sortBy) {
|
|
||||||
sortByProp.set(sortBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyObjectProperty< GroupSortBy> getSortByProperty() {
|
|
||||||
return sortByProp.getReadOnlyProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized DrawableAttribute<?> getGroupBy() {
|
|
||||||
return groupByProp.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void setGroupBy(DrawableAttribute<?> groupBy) {
|
|
||||||
groupByProp.set(groupBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<DrawableAttribute<?>> getGroupByProperty() {
|
|
||||||
return groupByProp.getReadOnlyProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized SortOrder getSortOrder() {
|
|
||||||
return sortOrderProp.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void setSortOrder(SortOrder sortOrder) {
|
|
||||||
sortOrderProp.set(sortOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<SortOrder> getSortOrderProperty() {
|
|
||||||
return sortOrderProp.getReadOnlyProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized DataSource getDataSource() {
|
|
||||||
return dataSourceProp.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void setDataSource(DataSource dataSource) {
|
|
||||||
dataSourceProp.set(dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<DataSource> getDataSourceProperty() {
|
|
||||||
return dataSourceProp.getReadOnlyProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Regroup all files in the database. see ReGroupTask for more details.
|
|
||||||
*
|
|
||||||
* @param <A> The type of the values of the groupBy attriubte.
|
|
||||||
* @param dataSource The DataSource to show. Null for all data sources.
|
|
||||||
* @param groupBy The DrawableAttribute to group by
|
|
||||||
* @param sortBy The GroupSortBy to sort the groups by
|
|
||||||
* @param sortOrder The SortOrder to use when sorting the groups.
|
|
||||||
* @param force true to force a full db query regroup, even if only the
|
|
||||||
* sorting has changed.
|
|
||||||
*/
|
|
||||||
public synchronized <A extends Comparable<A>> void regroup(DataSource dataSource, DrawableAttribute<A> groupBy, GroupSortBy sortBy, SortOrder sortOrder, Boolean force) {
|
|
||||||
|
|
||||||
if (!Case.isCaseOpen()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//only re-query the db if the data source or group by attribute changed or it is forced
|
|
||||||
if (dataSource != getDataSource()
|
|
||||||
|| groupBy != getGroupBy()
|
|
||||||
|| force) {
|
|
||||||
|
|
||||||
setDataSource(dataSource);
|
|
||||||
setGroupBy(groupBy);
|
|
||||||
setSortBy(sortBy);
|
|
||||||
setSortOrder(sortOrder);
|
|
||||||
if (groupByTask != null) {
|
|
||||||
groupByTask.cancel(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
groupByTask = new ReGroupTask<>(dataSource, groupBy, sortBy, sortOrder);
|
|
||||||
Platform.runLater(() -> regroupProgress.bind(groupByTask.progressProperty()));
|
|
||||||
exec.submit(groupByTask);
|
|
||||||
} else {
|
|
||||||
// resort the list of groups
|
|
||||||
setSortBy(sortBy);
|
|
||||||
setSortOrder(sortOrder);
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
FXCollections.sort(analyzedGroups, makeGroupComparator(sortOrder, sortBy));
|
|
||||||
FXCollections.sort(unSeenGroups, makeGroupComparator(sortOrder, sortBy));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyDoubleProperty regroupProgress() {
|
|
||||||
return regroupProgress.getReadOnlyProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
synchronized public void handleTagAdded(ContentTagAddedEvent evt) {
|
|
||||||
GroupKey<?> newGroupKey = null;
|
|
||||||
final long fileID = evt.getAddedTag().getContent().getId();
|
|
||||||
if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(evt.getAddedTag().getName())) {
|
|
||||||
newGroupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(evt.getAddedTag().getName()), getDataSource());
|
|
||||||
for (GroupKey<?> oldGroupKey : groupMap.keySet()) {
|
|
||||||
if (oldGroupKey.equals(newGroupKey) == false) {
|
|
||||||
removeFromGroup(oldGroupKey, fileID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(evt.getAddedTag().getName())) {
|
|
||||||
newGroupKey = new GroupKey<>(DrawableAttribute.TAGS, evt.getAddedTag().getName(), getDataSource());
|
|
||||||
}
|
|
||||||
if (newGroupKey != null) {
|
|
||||||
DrawableGroup g = getGroupForKey(newGroupKey);
|
|
||||||
addFileToGroup(g, newGroupKey, fileID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("AssignmentToMethodParameter")
|
|
||||||
synchronized private void addFileToGroup(DrawableGroup g, final GroupKey<?> groupKey, final long fileID) {
|
|
||||||
if (g == null) {
|
|
||||||
//if there wasn't already a group check if there should be one now
|
|
||||||
g = popuplateIfAnalyzed(groupKey, null);
|
|
||||||
}
|
|
||||||
DrawableGroup group = g;
|
|
||||||
if (group != null) {
|
|
||||||
//if there is aleady a group that was previously deemed fully analyzed, then add this newly analyzed file to it.
|
|
||||||
group.addFile(fileID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
synchronized public void handleTagDeleted(ContentTagDeletedEvent evt) {
|
|
||||||
GroupKey<?> groupKey = null;
|
|
||||||
final ContentTagDeletedEvent.DeletedContentTagInfo deletedTagInfo = evt.getDeletedTagInfo();
|
|
||||||
final TagName tagName = deletedTagInfo.getName();
|
|
||||||
if (getGroupBy() == DrawableAttribute.CATEGORY && CategoryManager.isCategoryTagName(tagName)) {
|
|
||||||
groupKey = new GroupKey<>(DrawableAttribute.CATEGORY, CategoryManager.categoryFromTagName(tagName), getDataSource());
|
|
||||||
} else if (getGroupBy() == DrawableAttribute.TAGS && CategoryManager.isNotCategoryTagName(tagName)) {
|
|
||||||
groupKey = new GroupKey<>(DrawableAttribute.TAGS, tagName, getDataSource());
|
|
||||||
}
|
|
||||||
if (groupKey != null) {
|
|
||||||
final long fileID = deletedTagInfo.getContentID();
|
|
||||||
DrawableGroup g = removeFromGroup(groupKey, fileID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe
|
|
||||||
synchronized public void handleFileRemoved(Collection<Long> removedFileIDs) {
|
|
||||||
|
|
||||||
for (final long fileId : removedFileIDs) {
|
|
||||||
//get grouping(s) this file would be in
|
|
||||||
Set<GroupKey<?>> groupsForFile = getGroupKeysForFileID(fileId);
|
|
||||||
|
|
||||||
for (GroupKey<?> gk : groupsForFile) {
|
|
||||||
removeFromGroup(gk, fileId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle notifications sent from Db when files are inserted/updated
|
|
||||||
*
|
|
||||||
* @param updatedFileIDs The ID of the inserted/updated files.
|
|
||||||
*/
|
|
||||||
@Subscribe
|
|
||||||
synchronized public void handleFileUpdate(Collection<Long> updatedFileIDs) {
|
|
||||||
/**
|
|
||||||
* TODO: is there a way to optimize this to avoid quering to db so much.
|
|
||||||
* the problem is that as a new files are analyzed they might be in new
|
|
||||||
* groups( if we are grouping by say make or model) -jm
|
|
||||||
*/
|
|
||||||
for (long fileId : updatedFileIDs) {
|
|
||||||
|
|
||||||
controller.getHashSetManager().invalidateHashSetsForFile(fileId);
|
|
||||||
|
|
||||||
//get grouping(s) this file would be in
|
|
||||||
Set<GroupKey<?>> groupsForFile = getGroupKeysForFileID(fileId);
|
|
||||||
for (GroupKey<?> gk : groupsForFile) {
|
|
||||||
DrawableGroup g = getGroupForKey(gk);
|
|
||||||
addFileToGroup(g, gk, fileId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//we fire this event for all files so that the category counts get updated during initial db population
|
|
||||||
controller.getCategoryManager().fireChange(updatedFileIDs, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized private DrawableGroup popuplateIfAnalyzed(GroupKey<?> groupKey, ReGroupTask<?> task) {
|
|
||||||
/*
|
|
||||||
* If this method call is part of a ReGroupTask and that task is
|
|
||||||
* cancelled, no-op.
|
|
||||||
*
|
|
||||||
* This allows us to stop if a regroup task has been cancelled (e.g. the
|
|
||||||
* user picked a different group by attribute, while the current task
|
|
||||||
* was still running)
|
|
||||||
*/
|
|
||||||
if (isNull(task) || task.isCancelled() == false) {
|
|
||||||
DrawableDB db = getDB();
|
|
||||||
/*
|
|
||||||
* For attributes other than path we can't be sure a group is fully
|
|
||||||
* analyzed because we don't know all the files that will be a part
|
|
||||||
* of that group. just show them no matter what.
|
|
||||||
*/
|
|
||||||
if (nonNull(db) && ((groupKey.getAttribute() != DrawableAttribute.PATH) || db.isGroupAnalyzed(groupKey))) {
|
|
||||||
try {
|
|
||||||
Set<Long> fileIDs = getFileIDsInGroup(groupKey);
|
|
||||||
if (Objects.nonNull(fileIDs)) {
|
|
||||||
DrawableGroup group;
|
|
||||||
final boolean groupSeen = db.isGroupSeen(groupKey);
|
|
||||||
if (groupMap.containsKey(groupKey)) {
|
|
||||||
group = groupMap.get(groupKey);
|
|
||||||
group.setFiles(ObjectUtils.defaultIfNull(fileIDs, Collections.emptySet()));
|
|
||||||
group.setSeen(groupSeen);
|
|
||||||
} else {
|
|
||||||
group = new DrawableGroup(groupKey, fileIDs, groupSeen);
|
|
||||||
controller.getCategoryManager().registerListener(group);
|
|
||||||
groupMap.put(groupKey, group);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (analyzedGroups.contains(group) == false) {
|
|
||||||
analyzedGroups.add(group);
|
|
||||||
if (isNull(task)) {
|
|
||||||
sortAnalyzedGroups();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateUnSeenGroups(group, groupSeen);
|
|
||||||
|
|
||||||
return group;
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
logger.log(Level.SEVERE, "failed to get files for group: " + groupKey.getAttribute().attrName.toString() + " = " + groupKey.getValue(), ex); //NON-NLS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized public Set<Long> getFileIDsWithMimeType(String mimeType) throws TskCoreException {
|
|
||||||
|
|
||||||
HashSet<Long> hashSet = new HashSet<>();
|
|
||||||
String query = (null == mimeType)
|
|
||||||
? "SELECT obj_id FROM tsk_files WHERE mime_type IS NULL" //NON-NLS
|
|
||||||
: "SELECT obj_id FROM tsk_files WHERE mime_type = '" + mimeType + "'"; //NON-NLS
|
|
||||||
DrawableDB db = getDB();
|
|
||||||
try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query);
|
|
||||||
ResultSet resultSet = executeQuery.getResultSet();) {
|
|
||||||
while (resultSet.next()) {
|
|
||||||
final long fileID = resultSet.getLong("obj_id"); //NON-NLS
|
|
||||||
if (nonNull(db) && db.isInDB(fileID)) {
|
|
||||||
hashSet.add(fileID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hashSet;
|
|
||||||
|
|
||||||
} catch (Exception ex) {
|
|
||||||
throw new TskCoreException("Failed to get file ids with mime type " + mimeType, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task to query database for files in sorted groups and build
|
|
||||||
* DrawableGroups for them.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
@NbBundle.Messages({"# {0} - groupBy attribute Name",
|
|
||||||
"# {1} - sortBy name",
|
|
||||||
"# {2} - sort Order",
|
|
||||||
"ReGroupTask.displayTitle=regrouping files by {0} sorted by {1} in {2} order",
|
|
||||||
"# {0} - groupBy attribute Name",
|
|
||||||
"# {1} - atribute value",
|
|
||||||
"ReGroupTask.progressUpdate=regrouping files by {0} : {1}"})
|
|
||||||
private class ReGroupTask<AttrValType extends Comparable<AttrValType>> extends LoggedTask<Void> {
|
|
||||||
|
|
||||||
private final DataSource dataSource;
|
|
||||||
private final DrawableAttribute<AttrValType> groupBy;
|
|
||||||
private final GroupSortBy sortBy;
|
|
||||||
private final SortOrder sortOrder;
|
|
||||||
|
|
||||||
private final ProgressHandle groupProgress;
|
|
||||||
|
|
||||||
ReGroupTask(DataSource dataSource, DrawableAttribute<AttrValType> groupBy, GroupSortBy sortBy, SortOrder sortOrder) {
|
|
||||||
super(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), true);
|
|
||||||
this.dataSource = dataSource;
|
|
||||||
this.groupBy = groupBy;
|
|
||||||
this.sortBy = sortBy;
|
|
||||||
this.sortOrder = sortOrder;
|
|
||||||
|
|
||||||
groupProgress = ProgressHandle.createHandle(Bundle.ReGroupTask_displayTitle(groupBy.attrName.toString(), sortBy.getDisplayName(), sortOrder.toString()), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCancelled() {
|
|
||||||
return super.isCancelled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void call() throws Exception {
|
|
||||||
|
|
||||||
if (isCancelled()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
groupProgress.start();
|
|
||||||
|
|
||||||
synchronized (GroupManager.this) {
|
|
||||||
analyzedGroups.clear();
|
|
||||||
unSeenGroups.clear();
|
|
||||||
|
|
||||||
// Get the list of group keys
|
|
||||||
final Multimap<DataSource, AttrValType> valsByDataSource = findValuesForAttribute();
|
|
||||||
groupProgress.switchToDeterminate(valsByDataSource.size());
|
|
||||||
int p = 0;
|
|
||||||
// For each key value, partially create the group and add it to the list.
|
|
||||||
for (final Map.Entry<DataSource, AttrValType> val : valsByDataSource.entries()) {
|
|
||||||
if (isCancelled()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
p++;
|
|
||||||
updateMessage(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val.getValue()));
|
|
||||||
updateProgress(p, valsByDataSource.size());
|
|
||||||
groupProgress.progress(Bundle.ReGroupTask_progressUpdate(groupBy.attrName.toString(), val), p);
|
|
||||||
popuplateIfAnalyzed(new GroupKey<>(groupBy, val.getValue(), val.getKey()), this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DataSource dataSourceOfCurrentGroup
|
|
||||||
= Optional.ofNullable(controller.getViewState())
|
|
||||||
.flatMap(GroupViewState::getGroup)
|
|
||||||
.map(DrawableGroup::getGroupKey)
|
|
||||||
.flatMap(GroupKey::getDataSource)
|
|
||||||
.orElse(null);
|
|
||||||
if (getDataSource() == null
|
|
||||||
|| Objects.equals(dataSourceOfCurrentGroup, getDataSource())) {
|
|
||||||
//the current group is for the given datasource, so just keep it in view.
|
|
||||||
} else { //the current group should not be visible so ...
|
|
||||||
if (isNotEmpty(unSeenGroups)) {// show then next unseen group
|
|
||||||
controller.advance(GroupViewState.tile(unSeenGroups.get(0)), false);
|
|
||||||
} else { // clear the group area.
|
|
||||||
controller.advance(GroupViewState.tile(null), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
groupProgress.finish();
|
|
||||||
updateProgress(1, 1);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void done() {
|
|
||||||
super.done();
|
|
||||||
try {
|
|
||||||
get();
|
|
||||||
} catch (CancellationException cancelEx) {
|
|
||||||
//cancellation is normal
|
|
||||||
} catch (InterruptedException | ExecutionException ex) {
|
|
||||||
logger.log(Level.SEVERE, "Error while regrouping.", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* find the distinct values for the given column (DrawableAttribute)
|
|
||||||
*
|
|
||||||
* These values represent the groups of files.
|
|
||||||
*
|
|
||||||
* @param groupBy
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Multimap<DataSource, AttrValType> findValuesForAttribute() {
|
|
||||||
synchronized (GroupManager.this) {
|
|
||||||
DrawableDB db = getDB();
|
|
||||||
Multimap results = HashMultimap.create();
|
|
||||||
try {
|
|
||||||
switch (groupBy.attrName) {
|
|
||||||
//these cases get special treatment
|
|
||||||
case CATEGORY:
|
|
||||||
results.putAll(null, Arrays.asList(DhsImageCategory.values()));
|
|
||||||
break;
|
|
||||||
case TAGS:
|
|
||||||
results.putAll(null, controller.getTagsManager().getTagNamesInUse().stream()
|
|
||||||
.filter(CategoryManager::isNotCategoryTagName)
|
|
||||||
.collect(Collectors.toList()));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ANALYZED:
|
|
||||||
results.putAll(null, Arrays.asList(false, true));
|
|
||||||
break;
|
|
||||||
case HASHSET:
|
|
||||||
if (nonNull(db)) {
|
|
||||||
results.putAll(null, new TreeSet<>(db.getHashSetNames()));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MIME_TYPE:
|
|
||||||
if (nonNull(db)) {
|
|
||||||
HashSet<String> types = new HashSet<>();
|
|
||||||
|
|
||||||
// Use the group_concat function to get a list of files for each mime type.
|
|
||||||
// This has different syntax on Postgres vs SQLite
|
|
||||||
String groupConcatClause;
|
|
||||||
if (DbType.POSTGRESQL == controller.getSleuthKitCase().getDatabaseType()) {
|
|
||||||
groupConcatClause = " array_to_string(array_agg(obj_id), ',') as object_ids";
|
|
||||||
} else {
|
|
||||||
groupConcatClause = " group_concat(obj_id) as object_ids";
|
|
||||||
}
|
|
||||||
String query = "select " + groupConcatClause + " , mime_type from tsk_files group by mime_type ";
|
|
||||||
try (SleuthkitCase.CaseDbQuery executeQuery = controller.getSleuthKitCase().executeQuery(query); //NON-NLS
|
|
||||||
ResultSet resultSet = executeQuery.getResultSet();) {
|
|
||||||
while (resultSet.next()) {
|
|
||||||
final String mimeType = resultSet.getString("mime_type"); //NON-NLS
|
|
||||||
String objIds = resultSet.getString("object_ids"); //NON-NLS
|
|
||||||
|
|
||||||
Pattern.compile(",").splitAsStream(objIds)
|
|
||||||
.map(Long::valueOf)
|
|
||||||
.filter(db::isInDB)
|
|
||||||
.findAny().ifPresent(obj_id -> types.add(mimeType));
|
|
||||||
}
|
|
||||||
} catch (SQLException | TskCoreException ex) {
|
|
||||||
Exceptions.printStackTrace(ex);
|
|
||||||
}
|
|
||||||
results.putAll(null, types);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
//otherwise do straight db query
|
|
||||||
if (nonNull(db)) {
|
|
||||||
results.putAll(db.findValuesForAttribute(groupBy, sortBy, sortOrder, dataSource));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (TskCoreException | TskDataException ex) {
|
|
||||||
logger.log(Level.SEVERE, "TSK error getting list of type {0}", groupBy.getDisplayName()); //NON-NLS
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Comparator<DrawableGroup> makeGroupComparator(final SortOrder sortOrder, GroupSortBy comparator) {
|
|
||||||
switch (sortOrder) {
|
|
||||||
case ASCENDING:
|
|
||||||
return comparator;
|
|
||||||
case DESCENDING:
|
|
||||||
return comparator.reversed();
|
|
||||||
case UNSORTED:
|
|
||||||
default:
|
|
||||||
return new GroupSortBy.AllEqualComparator<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -40,7 +40,6 @@ import javafx.scene.layout.BorderPane;
|
|||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.controlsfx.control.action.ActionUtils;
|
import org.controlsfx.control.action.ActionUtils;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
|
||||||
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
|
||||||
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
|
||||||
import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction;
|
import org.sleuthkit.autopsy.imagegallery.actions.OpenExternalViewerAction;
|
||||||
@ -54,10 +53,10 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
"DrawableUIBase.errorLabel.OOMText=Insufficent memory"})
|
"DrawableUIBase.errorLabel.OOMText=Insufficent memory"})
|
||||||
abstract public class DrawableUIBase extends AnchorPane implements DrawableView {
|
abstract public class DrawableUIBase extends AnchorPane implements DrawableView {
|
||||||
|
|
||||||
|
/** The use of SingleThreadExecutor means we can only load a single image at
|
||||||
|
* a time */
|
||||||
static final Executor exec = Executors.newSingleThreadExecutor();
|
static final Executor exec = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DrawableUIBase.class.getName());
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
BorderPane imageBorder;
|
BorderPane imageBorder;
|
||||||
@FXML
|
@FXML
|
||||||
@ -132,13 +131,13 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
|||||||
Platform.runLater(() -> imageBorder.setCenter(progressNode));
|
Platform.runLater(() -> imageBorder.setCenter(progressNode));
|
||||||
|
|
||||||
//called on fx thread
|
//called on fx thread
|
||||||
myTask.setOnSucceeded(succeeded -> {
|
myTask.setOnSucceeded(succeeded -> { //on fx thread
|
||||||
showImage(file, myTask);
|
showImage(file, myTask);
|
||||||
synchronized (DrawableUIBase.this) {
|
synchronized (DrawableUIBase.this) {
|
||||||
imageTask = null;
|
imageTask = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
myTask.setOnFailed(failed -> {
|
myTask.setOnFailed(failed -> { //on fx thread
|
||||||
Throwable exception = myTask.getException();
|
Throwable exception = myTask.getException();
|
||||||
if (exception instanceof OutOfMemoryError
|
if (exception instanceof OutOfMemoryError
|
||||||
&& exception.getMessage().contains("Java heap space")) { //NON-NLS
|
&& exception.getMessage().contains("Java heap space")) { //NON-NLS
|
||||||
@ -150,7 +149,7 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
|||||||
imageTask = null;
|
imageTask = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
myTask.setOnCancelled(cancelled -> {
|
myTask.setOnCancelled(cancelled -> { //on fx thread
|
||||||
synchronized (DrawableUIBase.this) {
|
synchronized (DrawableUIBase.this) {
|
||||||
imageTask = null;
|
imageTask = null;
|
||||||
}
|
}
|
||||||
@ -174,9 +173,12 @@ abstract public class DrawableUIBase extends AnchorPane implements DrawableView
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Get a new progress indicator to use as a place holder for the image in
|
||||||
|
* this view.
|
||||||
*
|
*
|
||||||
* @param file the value of file
|
* @param imageTask The imageTask to get a progress indicator for.
|
||||||
* @param imageTask the value of imageTask
|
*
|
||||||
|
* @return The new Node to use as a progress indicator.
|
||||||
*/
|
*/
|
||||||
Node newProgressIndicator(final Task<?> imageTask) {
|
Node newProgressIndicator(final Task<?> imageTask) {
|
||||||
ProgressIndicator loadingProgressIndicator = new ProgressIndicator(-1);
|
ProgressIndicator loadingProgressIndicator = new ProgressIndicator(-1);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.CheckBox?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.MenuItem?>
|
<?import javafx.scene.control.MenuItem?>
|
||||||
<?import javafx.scene.control.RadioButton?>
|
<?import javafx.scene.control.RadioButton?>
|
||||||
@ -11,6 +12,7 @@
|
|||||||
<?import javafx.scene.control.ToolBar?>
|
<?import javafx.scene.control.ToolBar?>
|
||||||
<?import javafx.scene.image.Image?>
|
<?import javafx.scene.image.Image?>
|
||||||
<?import javafx.scene.image.ImageView?>
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
<?import javafx.scene.layout.BorderPane?>
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
@ -18,7 +20,7 @@
|
|||||||
<?import org.controlsfx.control.GridView?>
|
<?import org.controlsfx.control.GridView?>
|
||||||
<?import org.controlsfx.control.SegmentedButton?>
|
<?import org.controlsfx.control.SegmentedButton?>
|
||||||
|
|
||||||
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
|
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
|
||||||
<center>
|
<center>
|
||||||
<GridView fx:id="gridView" BorderPane.alignment="CENTER" />
|
<GridView fx:id="gridView" BorderPane.alignment="CENTER" />
|
||||||
@ -29,7 +31,7 @@
|
|||||||
<HBox alignment="CENTER_LEFT" spacing="5.0" BorderPane.alignment="TOP_LEFT">
|
<HBox alignment="CENTER_LEFT" spacing="5.0" BorderPane.alignment="TOP_LEFT">
|
||||||
<children>
|
<children>
|
||||||
<Label fx:id="bottomLabel" text="Group Viewing History: " />
|
<Label fx:id="bottomLabel" text="Group Viewing History: " />
|
||||||
<Button fx:id="backButton" mnemonicParsing="false" text="back">
|
<Button fx:id="backButton" mnemonicParsing="false" text="Back">
|
||||||
<graphic>
|
<graphic>
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
@ -38,7 +40,7 @@
|
|||||||
</ImageView>
|
</ImageView>
|
||||||
</graphic>
|
</graphic>
|
||||||
</Button>
|
</Button>
|
||||||
<Button fx:id="forwardButton" mnemonicParsing="false" text="forward">
|
<Button fx:id="forwardButton" mnemonicParsing="false" text="Forward">
|
||||||
<graphic>
|
<graphic>
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||||
<image>
|
<image>
|
||||||
@ -56,18 +58,23 @@
|
|||||||
<right>
|
<right>
|
||||||
<HBox alignment="CENTER_RIGHT" spacing="5.0" BorderPane.alignment="TOP_RIGHT">
|
<HBox alignment="CENTER_RIGHT" spacing="5.0" BorderPane.alignment="TOP_RIGHT">
|
||||||
<children>
|
<children>
|
||||||
<Button fx:id="nextButton" contentDisplay="RIGHT" mnemonicParsing="false" text="next unseen group" BorderPane.alignment="CENTER_RIGHT">
|
<CheckBox fx:id="seenByOtherExaminersCheckBox" mnemonicParsing="false" text="Don't show groups seen by other examiners" />
|
||||||
<BorderPane.margin>
|
<AnchorPane fx:id="nextButtonPane" BorderPane.alignment="CENTER_RIGHT">
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<BorderPane.margin>
|
||||||
</BorderPane.margin>
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
<graphic>
|
</BorderPane.margin>
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
<children>
|
||||||
<image>
|
<Button fx:id="nextButton" contentDisplay="RIGHT" minWidth="175.0" mnemonicParsing="false" text="All Groups Gave Been Seen">
|
||||||
<Image url="@../../images/control-double.png" />
|
<graphic>
|
||||||
</image>
|
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true">
|
||||||
</ImageView>
|
<image>
|
||||||
</graphic>
|
<Image url="@../../images/control-double.png" />
|
||||||
</Button>
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</AnchorPane>
|
||||||
</children>
|
</children>
|
||||||
<BorderPane.margin>
|
<BorderPane.margin>
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
@ -57,7 +57,9 @@ import javafx.event.ActionEvent;
|
|||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.geometry.Bounds;
|
import javafx.geometry.Bounds;
|
||||||
|
import javafx.scene.Cursor;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.ContextMenu;
|
import javafx.scene.control.ContextMenu;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.MenuItem;
|
import javafx.scene.control.MenuItem;
|
||||||
@ -85,6 +87,7 @@ import static javafx.scene.input.KeyCode.RIGHT;
|
|||||||
import static javafx.scene.input.KeyCode.UP;
|
import static javafx.scene.input.KeyCode.UP;
|
||||||
import javafx.scene.input.KeyEvent;
|
import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.input.MouseEvent;
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.layout.Border;
|
import javafx.scene.layout.Border;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.BorderStroke;
|
import javafx.scene.layout.BorderStroke;
|
||||||
@ -95,6 +98,7 @@ import javafx.scene.layout.HBox;
|
|||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.controlsfx.control.GridCell;
|
import org.controlsfx.control.GridCell;
|
||||||
import org.controlsfx.control.GridView;
|
import org.controlsfx.control.GridView;
|
||||||
@ -135,9 +139,8 @@ import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
|
|||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A GroupPane displays the contents of a {@link DrawableGroup}. It supports
|
* A GroupPane displays the contents of a DrawableGroup. It supports both
|
||||||
* both a {@link GridView} based view and a {@link SlideShowView} view by
|
* GridView and SlideShowView modes by swapping out its internal components.
|
||||||
* swapping out its internal components.
|
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* TODO: Extract the The GridView instance to a separate class analogous to the
|
* TODO: Extract the The GridView instance to a separate class analogous to the
|
||||||
@ -150,26 +153,20 @@ import org.sleuthkit.datamodel.TskCoreException;
|
|||||||
public class GroupPane extends BorderPane {
|
public class GroupPane extends BorderPane {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(GroupPane.class.getName());
|
private static final Logger logger = Logger.getLogger(GroupPane.class.getName());
|
||||||
private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(GroupPane.class);
|
|
||||||
|
|
||||||
private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2);
|
private static final BorderWidths BORDER_WIDTHS_2 = new BorderWidths(2);
|
||||||
private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2);
|
private static final CornerRadii CORNER_RADII_2 = new CornerRadii(2);
|
||||||
|
|
||||||
private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE);
|
private static final DropShadow DROP_SHADOW = new DropShadow(10, Color.BLUE);
|
||||||
|
|
||||||
private static final Timeline flashAnimation = new Timeline(new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)),
|
private static final Timeline flashAnimation = new Timeline(
|
||||||
|
new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 1, Interpolator.LINEAR)),
|
||||||
new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR))
|
new KeyFrame(Duration.millis(400), new KeyValue(DROP_SHADOW.radiusProperty(), 15, Interpolator.LINEAR))
|
||||||
);
|
);
|
||||||
|
|
||||||
private final FileIDSelectionModel selectionModel;
|
private static final List<KeyCode> categoryKeyCodes = Arrays.asList(
|
||||||
|
NUMPAD0, NUMPAD1, NUMPAD2, NUMPAD3, NUMPAD4, NUMPAD5,
|
||||||
private static final List<KeyCode> categoryKeyCodes
|
DIGIT0, DIGIT1, DIGIT2, DIGIT3, DIGIT4, DIGIT5);
|
||||||
= Arrays.asList(KeyCode.NUMPAD0, KeyCode.NUMPAD1, KeyCode.NUMPAD2, KeyCode.NUMPAD3, KeyCode.NUMPAD4, KeyCode.NUMPAD5,
|
|
||||||
KeyCode.DIGIT0, KeyCode.DIGIT1, KeyCode.DIGIT2, KeyCode.DIGIT3, KeyCode.DIGIT4, KeyCode.DIGIT5);
|
|
||||||
|
|
||||||
private final Back backAction;
|
|
||||||
|
|
||||||
private final Forward forwardAction;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button undoButton;
|
private Button undoButton;
|
||||||
@ -178,13 +175,10 @@ public class GroupPane extends BorderPane {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private SplitMenuButton catSelectedSplitMenu;
|
private SplitMenuButton catSelectedSplitMenu;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private SplitMenuButton tagSelectedSplitMenu;
|
private SplitMenuButton tagSelectedSplitMenu;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToolBar headerToolBar;
|
private ToolBar headerToolBar;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleButton cat0Toggle;
|
private ToggleButton cat0Toggle;
|
||||||
@FXML
|
@FXML
|
||||||
@ -201,26 +195,25 @@ public class GroupPane extends BorderPane {
|
|||||||
@FXML
|
@FXML
|
||||||
private SegmentedButton segButton;
|
private SegmentedButton segButton;
|
||||||
|
|
||||||
private SlideShowView slideShowPane;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleButton slideShowToggle;
|
private ToggleButton slideShowToggle;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private GridView<Long> gridView;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleButton tileToggle;
|
private ToggleButton tileToggle;
|
||||||
|
|
||||||
|
private SlideShowView slideShowPane;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private GridView<Long> gridView;
|
||||||
@FXML
|
@FXML
|
||||||
private Button nextButton;
|
private Button nextButton;
|
||||||
|
@FXML
|
||||||
|
private AnchorPane nextButtonPane;
|
||||||
|
@FXML
|
||||||
|
private CheckBox seenByOtherExaminersCheckBox;
|
||||||
@FXML
|
@FXML
|
||||||
private Button backButton;
|
private Button backButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button forwardButton;
|
private Button forwardButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label groupLabel;
|
private Label groupLabel;
|
||||||
@FXML
|
@FXML
|
||||||
@ -237,30 +230,27 @@ public class GroupPane extends BorderPane {
|
|||||||
@FXML
|
@FXML
|
||||||
private HBox catSplitMenuContainer;
|
private HBox catSplitMenuContainer;
|
||||||
|
|
||||||
private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler();
|
private final ListeningExecutorService exec = TaskUtils.getExecutorForClass(GroupPane.class);
|
||||||
|
|
||||||
private final NextUnseenGroup nextGroupAction;
|
|
||||||
|
|
||||||
private final ImageGalleryController controller;
|
private final ImageGalleryController controller;
|
||||||
|
|
||||||
private ContextMenu contextMenu;
|
private final FileIDSelectionModel selectionModel;
|
||||||
|
|
||||||
private Integer selectionAnchorIndex;
|
private Integer selectionAnchorIndex;
|
||||||
|
|
||||||
private final UndoAction undoAction;
|
private final UndoAction undoAction;
|
||||||
private final RedoAction redoAction;
|
private final RedoAction redoAction;
|
||||||
|
private final Back backAction;
|
||||||
|
private final Forward forwardAction;
|
||||||
|
private final NextUnseenGroup nextGroupAction;
|
||||||
|
|
||||||
GroupViewMode getGroupViewMode() {
|
private final KeyboardHandler tileKeyboardNavigationHandler = new KeyboardHandler();
|
||||||
return groupViewMode.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private ContextMenu contextMenu;
|
||||||
* the current GroupViewMode of this GroupPane
|
|
||||||
*/
|
/** the current GroupViewMode of this GroupPane */
|
||||||
private final SimpleObjectProperty<GroupViewMode> groupViewMode = new SimpleObjectProperty<>(GroupViewMode.TILE);
|
private final SimpleObjectProperty<GroupViewMode> groupViewMode = new SimpleObjectProperty<>(GroupViewMode.TILE);
|
||||||
|
|
||||||
/**
|
/** the grouping this pane is currently the view for */
|
||||||
* the grouping this pane is currently the view for
|
|
||||||
*/
|
|
||||||
private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>();
|
private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -294,6 +284,10 @@ public class GroupPane extends BorderPane {
|
|||||||
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
|
FXMLConstructor.construct(this, "GroupPane.fxml"); //NON-NLS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GroupViewMode getGroupViewMode() {
|
||||||
|
return groupViewMode.get();
|
||||||
|
}
|
||||||
|
|
||||||
@ThreadConfined(type = ThreadType.JFX)
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
public void activateSlideShowViewer(Long slideShowFileID) {
|
public void activateSlideShowViewer(Long slideShowFileID) {
|
||||||
groupViewMode.set(GroupViewMode.SLIDE_SHOW);
|
groupViewMode.set(GroupViewMode.SLIDE_SHOW);
|
||||||
@ -340,7 +334,9 @@ public class GroupPane extends BorderPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create the string to display in the group header
|
* Create the string to display in the group header.
|
||||||
|
*
|
||||||
|
* @return The string to display in the group header.
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({"# {0} - default group name",
|
@NbBundle.Messages({"# {0} - default group name",
|
||||||
"# {1} - hashset hits count",
|
"# {1} - hashset hits count",
|
||||||
@ -391,19 +387,20 @@ public class GroupPane extends BorderPane {
|
|||||||
"GroupPane.catContainerLabel.displayText=Categorize Selected File:",
|
"GroupPane.catContainerLabel.displayText=Categorize Selected File:",
|
||||||
"GroupPane.catHeadingLabel.displayText=Category:"})
|
"GroupPane.catHeadingLabel.displayText=Category:"})
|
||||||
void initialize() {
|
void initialize() {
|
||||||
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
assert cat0Toggle != null : "fx:id=\"cat0Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
assert cat1Toggle != null : "fx:id=\"cat1Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
assert cat2Toggle != null : "fx:id=\"cat2Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
assert cat2Toggle != null : "fx:id=\"cat2Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
assert cat3Toggle != null : "fx:id=\"cat3Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
assert cat3Toggle != null : "fx:id=\"cat3Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
assert cat4Toggle != null : "fx:id=\"cat4Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
assert cat4Toggle != null : "fx:id=\"cat4Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
assert cat5Toggle != null : "fx:id=\"cat5Toggle\" was not injected: check your FXML file 'SlideShowView.fxml'.";
|
assert cat5Toggle != null : "fx:id=\"cat5Toggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
assert gridView != null : "fx:id=\"tilePane\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
assert gridView != null : "fx:id=\"tilePane\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
assert catSelectedSplitMenu != null : "fx:id=\"grpCatSplitMenu\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
assert catSelectedSplitMenu != null : "fx:id=\"grpCatSplitMenu\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
assert tagSelectedSplitMenu != null : "fx:id=\"grpTagSplitMenu\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
assert tagSelectedSplitMenu != null : "fx:id=\"grpTagSplitMenu\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
assert headerToolBar != null : "fx:id=\"headerToolBar\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
assert headerToolBar != null : "fx:id=\"headerToolBar\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
assert segButton != null : "fx:id=\"previewList\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
assert slideShowToggle != null : "fx:id=\"segButton\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupHeader.fxml'.";
|
assert tileToggle != null : "fx:id=\"tileToggle\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
|
assert seenByOtherExaminersCheckBox != null : "fx:id=\"seenByOtherExaminersCheckBox\" was not injected: check your FXML file 'GroupPane.fxml'.";
|
||||||
|
|
||||||
for (DhsImageCategory cat : DhsImageCategory.values()) {
|
for (DhsImageCategory cat : DhsImageCategory.values()) {
|
||||||
ToggleButton toggleForCategory = getToggleForCategory(cat);
|
ToggleButton toggleForCategory = getToggleForCategory(cat);
|
||||||
@ -530,6 +527,16 @@ public class GroupPane extends BorderPane {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
seenByOtherExaminersCheckBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
nextButtonPane.setDisable(true);
|
||||||
|
nextButtonPane.setCursor(Cursor.WAIT);
|
||||||
|
exec.submit(() -> controller.getGroupManager().setCollaborativeMode(newValue))
|
||||||
|
.addListener(() -> {
|
||||||
|
nextButtonPane.setDisable(false);
|
||||||
|
nextButtonPane.setCursor(Cursor.DEFAULT);
|
||||||
|
}, Platform::runLater);
|
||||||
|
});
|
||||||
|
|
||||||
//listen to tile selection and make sure it is visible in scroll area
|
//listen to tile selection and make sure it is visible in scroll area
|
||||||
selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> {
|
selectionModel.lastSelectedProperty().addListener((observable, oldFileID, newFileId) -> {
|
||||||
if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW
|
if (groupViewMode.get() == GroupViewMode.SLIDE_SHOW
|
||||||
@ -607,7 +614,7 @@ public class GroupPane extends BorderPane {
|
|||||||
* assigns a grouping for this pane to represent and initializes grouping
|
* assigns a grouping for this pane to represent and initializes grouping
|
||||||
* specific properties and listeners
|
* specific properties and listeners
|
||||||
*
|
*
|
||||||
* @param grouping the new grouping assigned to this group
|
* @param newViewState
|
||||||
*/
|
*/
|
||||||
void setViewState(GroupViewState newViewState) {
|
void setViewState(GroupViewState newViewState) {
|
||||||
|
|
||||||
@ -894,7 +901,7 @@ public class GroupPane extends BorderPane {
|
|||||||
if (t.getClickCount() == 1) {
|
if (t.getClickCount() == 1) {
|
||||||
selectAllFiles();
|
selectAllFiles();
|
||||||
}
|
}
|
||||||
if (selectionModel.getSelected().isEmpty() == false) {
|
if (isNotEmpty(selectionModel.getSelected())) {
|
||||||
if (contextMenu == null) {
|
if (contextMenu == null) {
|
||||||
contextMenu = buildContextMenu();
|
contextMenu = buildContextMenu();
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@ class GroupCellFactory {
|
|||||||
private final InvalidationListener groupListener = new GroupListener<>(this);
|
private final InvalidationListener groupListener = new GroupListener<>(this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* reference to group files listener that allows us to remove it from a
|
* Reference to group files listener that allows us to remove it from a
|
||||||
* group when a new group is assigned to this Cell
|
* group when a new group is assigned to this Cell
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
x
Reference in New Issue
Block a user