mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-15 09:17:42 +00:00
interim refactor of GroupPane to attempt to eliminate ConcurerntModificationException
This commit is contained in:
parent
0322ef8b8a
commit
03c64089eb
@ -50,6 +50,10 @@ public class DrawableGroup implements Comparable<DrawableGroup> {
|
|||||||
|
|
||||||
final public GroupKey<?> groupKey;
|
final public GroupKey<?> groupKey;
|
||||||
|
|
||||||
|
public GroupKey<?> getGroupKey() {
|
||||||
|
return groupKey;
|
||||||
|
}
|
||||||
|
|
||||||
DrawableGroup(GroupKey<?> groupKey, List<Long> filesInGroup) {
|
DrawableGroup(GroupKey<?> groupKey, List<Long> filesInGroup) {
|
||||||
this.groupKey = groupKey;
|
this.groupKey = groupKey;
|
||||||
fileIDs.setAll(filesInGroup);
|
fileIDs.setAll(filesInGroup);
|
||||||
|
@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.imagegallery.grouping;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
|
||||||
import org.sleuthkit.datamodel.TagName;
|
import org.sleuthkit.datamodel.TagName;
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ import org.sleuthkit.datamodel.TagName;
|
|||||||
* key identifying information of a {@link Grouping}. Used to look up groups in
|
* key identifying information of a {@link Grouping}. Used to look up groups in
|
||||||
* {@link Map}s and from the db.
|
* {@link Map}s and from the db.
|
||||||
*/
|
*/
|
||||||
|
@Immutable
|
||||||
public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>> {
|
public class GroupKey<T extends Comparable<T>> implements Comparable<GroupKey<T>> {
|
||||||
|
|
||||||
private final T val;
|
private final T val;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2013 Basis Technology Corp.
|
* Copyright 2013-15 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,18 +18,20 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.imagegallery.gui;
|
package org.sleuthkit.autopsy.imagegallery.gui;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import static java.util.Objects.isNull;
|
||||||
|
import static java.util.Objects.nonNull;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.Set;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
import javafx.animation.Interpolator;
|
import javafx.animation.Interpolator;
|
||||||
@ -43,7 +45,6 @@ import javafx.beans.property.ReadOnlyObjectProperty;
|
|||||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.event.EventHandler;
|
import javafx.event.EventHandler;
|
||||||
@ -82,7 +83,6 @@ import javafx.scene.layout.BorderPane;
|
|||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.scene.layout.TilePane;
|
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import javax.swing.Action;
|
import javax.swing.Action;
|
||||||
@ -122,11 +122,11 @@ import org.sleuthkit.datamodel.TagName;
|
|||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A GroupPane displays the contents of a {@link DrawableGroup}. It support both a
|
* A GroupPane displays the contents of a {@link DrawableGroup}. It supports
|
||||||
* {@link TilePane} based view and a {@link SlideShowView} view by swapping out
|
* both a {@link GridView} based view and a {@link SlideShowView} view by
|
||||||
* its internal components.
|
* swapping out its internal components.
|
||||||
*
|
*
|
||||||
* TODO: review for synchronization issues. TODO: Extract the The TilePane
|
* TODO: review for synchronization issues. TODO: Extract the The GridView
|
||||||
* instance to a separate class analogous to the SlideShow
|
* instance to a separate class analogous to the SlideShow
|
||||||
*/
|
*/
|
||||||
public class GroupPane extends BorderPane implements GroupView {
|
public class GroupPane extends BorderPane implements GroupView {
|
||||||
@ -145,12 +145,6 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
|
|
||||||
private final Forward forwardAction;
|
private final Forward forwardAction;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private URL location;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private ResourceBundle resources;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private SplitMenuButton grpCatSplitMenu;
|
private SplitMenuButton grpCatSplitMenu;
|
||||||
|
|
||||||
@ -212,11 +206,12 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
/**
|
/**
|
||||||
* map from fileIDs to their assigned cells in the tile view. This is used
|
* map from fileIDs to their assigned cells in the tile view. This is used
|
||||||
* to determine whether fileIDs are visible or are offscreen. No entry
|
* to determine whether fileIDs are visible or are offscreen. No entry
|
||||||
* indicates the given fileID is not displayed on screenDrawableCells
|
* indicates the given fileID is not displayed on screen. DrawableCells are
|
||||||
* responsible for adding and removing themselves from this map
|
* responsible for adding and removing themselves from this map.
|
||||||
*/
|
*/
|
||||||
@ThreadConfined(type = ThreadType.UI)
|
@ThreadConfined(type = ThreadType.UI)
|
||||||
private final Map<Long, DrawableCell> cellMap = new HashMap<>();
|
private final Map<Long, DrawableCell> cellMap = new HashMap<>();
|
||||||
|
private FilesSyncListener filesSyncListener;
|
||||||
|
|
||||||
public GroupPane(ImageGalleryController controller) {
|
public GroupPane(ImageGalleryController controller) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
@ -236,10 +231,10 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
|
|
||||||
//assign last selected file or if none first file in group
|
//assign last selected file or if none first file in group
|
||||||
if (slideShowFileId == null || grouping.get().fileIds().contains(slideShowFileId) == false) {
|
if (slideShowFileId == null || grouping.get().fileIds().contains(slideShowFileId) == false) {
|
||||||
slideShowFileId = grouping.get().fileIds().get(0);
|
slideShowPane.setFile(grouping.get().fileIds().get(0));
|
||||||
|
} else {
|
||||||
|
slideShowPane.setFile(slideShowFileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
slideShowPane.setFile(slideShowFileId);
|
|
||||||
setCenter(slideShowPane);
|
setCenter(slideShowPane);
|
||||||
slideShowPane.requestFocus();
|
slideShowPane.requestFocus();
|
||||||
}
|
}
|
||||||
@ -259,27 +254,12 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
return grouping.get();
|
return grouping.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the text to display as part of the header
|
|
||||||
*/
|
|
||||||
public String getHeaderString() {
|
|
||||||
int size = grouping.get().getSize();
|
|
||||||
int hashHitCount = grouping.get().getFilesWithHashSetHitsCount();
|
|
||||||
String groupName;
|
|
||||||
if (grouping.get().groupKey.getAttribute() == DrawableAttribute.TAGS) {
|
|
||||||
groupName = ((TagName) grouping.get().groupKey.getValue()).getDisplayName();
|
|
||||||
} else {
|
|
||||||
groupName = grouping.get().groupKey.getValue().toString();
|
|
||||||
}
|
|
||||||
return StringUtils.defaultIfBlank(groupName, DrawableGroup.UNKNOWN) + " -- " + hashHitCount + " hash set hits / " + size + " files";
|
|
||||||
}
|
|
||||||
|
|
||||||
private MenuItem createGrpCatMenuItem(final Category cat) {
|
private MenuItem createGrpCatMenuItem(final Category cat) {
|
||||||
final MenuItem menuItem = new MenuItem(cat.getDisplayName(), new ImageView(DrawableAttribute.CATEGORY.getIcon()));
|
final MenuItem menuItem = new MenuItem(cat.getDisplayName(), new ImageView(DrawableAttribute.CATEGORY.getIcon()));
|
||||||
menuItem.setOnAction(new EventHandler<ActionEvent>() {
|
menuItem.setOnAction(new EventHandler<ActionEvent>() {
|
||||||
@Override
|
@Override
|
||||||
public void handle(ActionEvent t) {
|
public void handle(ActionEvent t) {
|
||||||
Set<Long> fileIdSet = new HashSet<Long>(getGrouping().fileIds());
|
Set<Long> fileIdSet = new HashSet<>(getGrouping().fileIds());
|
||||||
new CategorizeAction().addTagsToFiles(cat.getTagName(), "", fileIdSet);
|
new CategorizeAction().addTagsToFiles(cat.getTagName(), "", fileIdSet);
|
||||||
|
|
||||||
grpCatSplitMenu.setText(cat.getDisplayName());
|
grpCatSplitMenu.setText(cat.getDisplayName());
|
||||||
@ -294,7 +274,7 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
menuItem.setOnAction(new EventHandler<ActionEvent>() {
|
menuItem.setOnAction(new EventHandler<ActionEvent>() {
|
||||||
@Override
|
@Override
|
||||||
public void handle(ActionEvent t) {
|
public void handle(ActionEvent t) {
|
||||||
Set<Long> fileIdSet = new HashSet<Long>(getGrouping().fileIds());
|
Set<Long> fileIdSet = new HashSet<>(getGrouping().fileIds());
|
||||||
AddDrawableTagAction.getInstance().addTagsToFiles(tn, "", fileIdSet);
|
AddDrawableTagAction.getInstance().addTagsToFiles(tn, "", fileIdSet);
|
||||||
|
|
||||||
grpTagSplitMenu.setText(tn.getDisplayName());
|
grpTagSplitMenu.setText(tn.getDisplayName());
|
||||||
@ -308,27 +288,18 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
globalSelectionModel.clearAndSelectAll(getGrouping().fileIds());
|
globalSelectionModel.clearAndSelectAll(getGrouping().fileIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** create the string to display in the group header */
|
||||||
* reset the text and icons to represent the currently filtered files
|
protected String getHeaderString() {
|
||||||
*/
|
if (getGrouping() == null) {
|
||||||
protected void resetHeaderString() {
|
return "";
|
||||||
if (grouping.get() == null) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
groupLabel.setText("");
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
int size = grouping.get().getSize();
|
String groupName = (getGrouping().groupKey.getAttribute() == DrawableAttribute.TAGS)
|
||||||
int hashHitCount = grouping.get().getFilesWithHashSetHitsCount();
|
? ((TagName) getGrouping().groupKey.getValue()).getDisplayName()
|
||||||
String groupName;
|
: getGrouping().groupKey.getValue().toString();
|
||||||
if (grouping.get().groupKey.getAttribute() == DrawableAttribute.TAGS) {
|
return StringUtils.defaultIfBlank(groupName, DrawableGroup.UNKNOWN) + " -- "
|
||||||
groupName = ((TagName) grouping.get().groupKey.getValue()).getDisplayName();
|
+ getGrouping().getFilesWithHashSetHitsCount() + " hash set hits / "
|
||||||
} else {
|
+ getGrouping().getSize() + " files";
|
||||||
groupName = grouping.get().groupKey.getValue().toString();
|
|
||||||
}
|
|
||||||
final String headerString = StringUtils.defaultIfBlank(groupName, DrawableGroup.UNKNOWN) + " -- " + hashHitCount + " hash set hits / " + size + " files";
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
groupLabel.setText(headerString);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,42 +326,6 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
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 'GroupHeader.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 'GroupHeader.fxml'.";
|
||||||
|
|
||||||
grouping.addListener(new InvalidationListener() {
|
|
||||||
private void updateFiles() {
|
|
||||||
final ObservableList<Long> fileIds = grouping.get().fileIds();
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
gridView.setItems(FXCollections.observableArrayList(fileIds));
|
|
||||||
});
|
|
||||||
resetHeaderString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void invalidated(Observable o) {
|
|
||||||
getScrollBar().ifPresent((scrollBar) -> {
|
|
||||||
scrollBar.setValue(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
//set the embeded header
|
|
||||||
resetHeaderString();
|
|
||||||
//and assign fileIDs to gridView
|
|
||||||
if (grouping.get() == null) {
|
|
||||||
Platform.runLater(gridView.getItems()::clear);
|
|
||||||
// Reset the DrawableCell listeners from the old case
|
|
||||||
if(! Case.isCaseOpen()){
|
|
||||||
for(GroupPane.DrawableCell cell:cellMap.values()){
|
|
||||||
cell.resetItem();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
grouping.get().fileIds().addListener((Observable observable) -> {
|
|
||||||
updateFiles();
|
|
||||||
});
|
|
||||||
|
|
||||||
updateFiles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//configure flashing glow animation on next unseen group button
|
//configure flashing glow animation on next unseen group button
|
||||||
flashAnimation.setCycleCount(Timeline.INDEFINITE);
|
flashAnimation.setCycleCount(Timeline.INDEFINITE);
|
||||||
flashAnimation.setAutoReverse(true);
|
flashAnimation.setAutoReverse(true);
|
||||||
@ -634,28 +569,53 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
* @param grouping the new grouping assigned to this group
|
* @param grouping the new grouping assigned to this group
|
||||||
*/
|
*/
|
||||||
void setViewState(GroupViewState viewState) {
|
void setViewState(GroupViewState viewState) {
|
||||||
if (viewState == null) {
|
if (nonNull(getGrouping())) {
|
||||||
|
getGrouping().fileIds().removeListener(filesSyncListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNull(viewState) || isNull(viewState.getGroup())) {
|
||||||
this.grouping.set(null);
|
this.grouping.set(null);
|
||||||
|
final List<Long> fileIds = Collections.emptyList();
|
||||||
|
final String header = "";
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
setCenter(null);
|
setCenter(null);
|
||||||
groupLabel.setText(null);
|
gridView.getItems().setAll(fileIds);
|
||||||
|
groupLabel.setText(header);
|
||||||
|
resetScrollBar();
|
||||||
|
if (false == Case.isCaseOpen()) {
|
||||||
|
cellMap.values().stream().forEach(DrawableCell::resetItem);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (this.grouping.get() != viewState.getGroup()) {
|
if (this.grouping.get() != viewState.getGroup()) {
|
||||||
this.grouping.set(viewState.getGroup());
|
this.grouping.set(viewState.getGroup());
|
||||||
|
filesSyncListener = new FilesSyncListener();
|
||||||
|
this.getGrouping().fileIds().addListener(filesSyncListener);
|
||||||
|
|
||||||
}
|
final String header = getHeaderString();
|
||||||
|
final ObservableList<Long> fileIds = getGrouping().fileIds();
|
||||||
if (viewState.getMode() == GroupViewMode.TILE) {
|
Platform.runLater(() -> {
|
||||||
activateTileViewer();
|
gridView.getItems().setAll(fileIds);
|
||||||
} else {
|
groupLabel.setText(header);
|
||||||
activateSlideShowViewer(viewState.getSlideShowfileID().orElse(null));
|
resetScrollBar();
|
||||||
|
if (viewState.getMode() == GroupViewMode.TILE) {
|
||||||
|
activateTileViewer();
|
||||||
|
} else {
|
||||||
|
activateSlideShowViewer(viewState.getSlideShowfileID().orElse(null));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
|
private void resetScrollBar() {
|
||||||
|
getScrollBar().ifPresent((scrollBar) -> {
|
||||||
|
scrollBar.setValue(0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private class DrawableCell extends GridCell<Long> {
|
private class DrawableCell extends GridCell<Long> {
|
||||||
|
|
||||||
private final DrawableTile tile = new DrawableTile(GroupPane.this);
|
private final DrawableTile tile = new DrawableTile(GroupPane.this);
|
||||||
@ -667,15 +627,15 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
tile.setFile(null);
|
tile.setFile(null);
|
||||||
}
|
}
|
||||||
if (newValue != null) {
|
if (newValue != null) {
|
||||||
if(cellMap.containsKey(newValue)){
|
if (cellMap.containsKey(newValue)) {
|
||||||
if(tile != null){
|
if (tile != null) {
|
||||||
// Clear out the old value to prevent out-of-date listeners
|
// Clear out the old value to prevent out-of-date listeners
|
||||||
// from activating.
|
// from activating.
|
||||||
cellMap.get(newValue).tile.setFile(null);
|
cellMap.get(newValue).tile.setFile(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cellMap.put(newValue, DrawableCell.this);
|
cellMap.put(newValue, DrawableCell.this);
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -687,8 +647,8 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
tile.setFile(item);
|
tile.setFile(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetItem(){
|
void resetItem() {
|
||||||
tile.setFile(null);
|
tile.setFile(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -812,6 +772,7 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ThreadConfined(type = ThreadType.JFX)
|
||||||
private Optional<ScrollBar> getScrollBar() {
|
private Optional<ScrollBar> getScrollBar() {
|
||||||
if (gridView == null || gridView.getSkin() == null) {
|
if (gridView == null || gridView.getSkin() == null) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
@ -835,4 +796,17 @@ public class GroupPane extends BorderPane implements GroupView {
|
|||||||
globalSelectionModel.clearAndSelect(newFileID);
|
globalSelectionModel.clearAndSelect(newFileID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class FilesSyncListener implements InvalidationListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidated(Observable observable) {
|
||||||
|
final String header = getHeaderString();
|
||||||
|
final List<Long> fileIds = ImmutableList.copyOf(getGrouping().fileIds());
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
gridView.getItems().setAll(fileIds);
|
||||||
|
groupLabel.setText(header);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user