Merge pull request #4186 from millmanorama/1054-populate_widgets_on_bg_thread

1054 make sure all db queries for widget initialization happen on bg threads.
This commit is contained in:
Brian Carrier 2018-10-09 10:40:51 -04:00 committed by GitHub
commit ac4778e7f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 107 additions and 119 deletions

View File

@ -82,7 +82,7 @@ public class SummaryTablePane extends AnchorPane {
//register for category events
controller.getCategoryManager().registerListener(this);
handleCategoryChanged(null);
new Thread(() -> handleCategoryChanged(null)).start();
}
public SummaryTablePane(ImageGalleryController controller) {

View File

@ -74,6 +74,7 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupSortBy;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
import org.sleuthkit.datamodel.DataSource;
/**
@ -255,6 +256,7 @@ public class Toolbar extends ToolBar {
evt -> {
Platform.runLater(() -> {
Optional<DataSource> selectedItem = dataSourceSelectionModel.getSelectedItem();
//restore selection once the sync is done.
syncDataSources().addListener(() -> dataSourceSelectionModel.select(selectedItem), Platform::runLater);
});
});
@ -269,22 +271,19 @@ public class Toolbar extends ToolBar {
}
private void initTagMenuButton() {
ListenableFuture<TagGroupAction> future = exec.submit(() -> new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller));
Futures.addCallback(future, new FutureCallback<TagGroupAction>() {
@Override
public void onSuccess(TagGroupAction followUpGroupAction) {
addFXCallback(exec.submit(() -> new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller)),
followUpGroupAction -> {
//on fx thread
tagGroupMenuButton.setOnAction(followUpGroupAction);
tagGroupMenuButton.setText(followUpGroupAction.getText());
tagGroupMenuButton.setGraphic(followUpGroupAction.getGraphic());
}
@Override
public void onFailure(Throwable throwable) {
},
throwable -> {
/*
* The problem appears to be a timing issue where a case is
* closed before this initialization is completed, which It
* appears to be harmless, so we are temporarily changing this
* log message to a WARNING.
* appears to be harmless, so we are temporarily changing
* this log message to a WARNING.
*
* TODO (JIRA-3010): SEVERE error logged by image Gallery UI
*/
@ -295,7 +294,7 @@ public class Toolbar extends ToolBar {
logger.log(Level.INFO, "Unable to get tag name. Case is closed."); //NON-NLS
}
}
}, Platform::runLater);
);
tagGroupMenuButton.showingProperty().addListener(showing -> {
if (tagGroupMenuButton.isShowing()) {
@ -303,24 +302,18 @@ public class Toolbar extends ToolBar {
return Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
tagName -> GuiUtils.createAutoAssigningMenuItem(tagGroupMenuButton, new TagGroupAction(tagName, controller)));
});
Futures.addCallback(getTagsFuture, new FutureCallback<List<MenuItem>>() {
@Override
public void onSuccess(List<MenuItem> result) {
tagGroupMenuButton.getItems().setAll(result);
}
@Override
public void onFailure(Throwable t) {
logger.log(Level.SEVERE, "Error getting non-gategory tag names.", t);
}
}, Platform::runLater);
addFXCallback(getTagsFuture,
menuItems -> tagGroupMenuButton.getItems().setAll(menuItems),
throwable -> logger.log(Level.SEVERE, "Error getting non-gategory tag names.", throwable)
);
}
});
}
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
private ListenableFuture<List<DataSource>> syncDataSources() {
ListenableFuture<List<DataSource>> future = exec.submit(() -> {
ListenableFuture<List<DataSource>> dataSourcesFuture = exec.submit(() -> {
List<DataSource> dataSourcesInCase = controller.getSleuthKitCase().getDataSources();
synchronized (dataSourcesViewable) {
dataSourcesViewable.clear();
@ -331,22 +324,18 @@ public class Toolbar extends ToolBar {
}
return dataSourcesInCase;
});
Futures.addCallback(future, new FutureCallback<List<DataSource>>() {
@Override
public void onSuccess(List<DataSource> result) {
List<Optional<DataSource>> newDataSources = new ArrayList<>();
newDataSources.add(Optional.empty());
result.forEach(dataSource -> newDataSources.add(Optional.of(dataSource)));
addFXCallback(dataSourcesFuture,
result -> {
//on fx thread
List<Optional<DataSource>> newDataSources = new ArrayList<>(Lists.transform(result, Optional::of));
newDataSources.add(0, Optional.empty());
dataSources.setAll(newDataSources);
}
},
throwable -> logger.log(Level.SEVERE, "Unable to get datasources for current case.", throwable) //NON-NLS
@Override
public void onFailure(Throwable t) {
logger.log(Level.SEVERE, "Unable to get datasources for current case.", t); //NON-NLS
}
}, Platform::runLater);
);
return future;
return dataSourcesFuture;
}
/**
@ -384,8 +373,7 @@ public class Toolbar extends ToolBar {
* selection.
*/
private void syncGroupControlsEnabledState(GroupViewState newViewState) {
boolean noGroupSelected = (null == newViewState)
|| (null == newViewState.getGroup());
boolean noGroupSelected = (null == newViewState) || (null == newViewState.getGroup());
Platform.runLater(() -> {
tagGroupMenuButton.setDisable(noGroupSelected);
catGroupMenuButton.setDisable(noGroupSelected);
@ -402,7 +390,5 @@ public class Toolbar extends ToolBar {
public Toolbar(ImageGalleryController controller) {
this.controller = controller;
FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS
}
}

View File

@ -21,12 +21,12 @@ package org.sleuthkit.autopsy.imagegallery.gui.drawableviews;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import static com.google.common.collect.Lists.transform;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.ArrayList;
import java.util.Arrays;
import static java.util.Arrays.asList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@ -35,6 +35,7 @@ import java.util.Map;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.IntStream;
import javafx.animation.Interpolator;
@ -135,7 +136,10 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewMode;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
import static org.sleuthkit.autopsy.imagegallery.gui.GuiUtils.createAutoAssigningMenuItem;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
@ -254,7 +258,7 @@ public class GroupPane extends BorderPane {
private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>();
/**
* 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
* indicates the given fileID is not displayed on screen. DrawableCells are
* responsible for adding and removing themselves from this map.
@ -371,7 +375,7 @@ public class GroupPane extends BorderPane {
case FIVE:
return cat5Toggle;
default:
throw new IllegalArgumentException(category.name());
throw new UnsupportedOperationException("Unknown category: " + category.name());
}
}
@ -425,51 +429,41 @@ public class GroupPane extends BorderPane {
DoubleBinding cellSize = controller.thumbnailSizeProperty().add(75);
gridView.cellHeightProperty().bind(cellSize);
gridView.cellWidthProperty().bind(cellSize);
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell());
gridView.setCellFactory(param -> new DrawableCell());
BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected());
catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
TagSelectedFilesAction followUpSelectedAction = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); //NON-NLS
Platform.runLater(() -> {
addFXCallback(exec.submit(() -> controller.getTagsManager().getFollowUpTagName()),
followUpTagName -> {
//on fx thread
TagSelectedFilesAction followUpSelectedAction = new TagSelectedFilesAction(followUpTagName, controller);
tagSelectedSplitMenu.setText(followUpSelectedAction.getText());
tagSelectedSplitMenu.setGraphic(followUpSelectedAction.getGraphic());
tagSelectedSplitMenu.setOnAction(followUpSelectedAction);
tagSelectedSplitMenu.showingProperty().addListener(showing -> {
if (tagSelectedSplitMenu.isShowing()) {
ListenableFuture<List<MenuItem>> getTagsFuture = exec.submit(()
-> Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
tagName -> GuiUtils.createAutoAssigningMenuItem(tagSelectedSplitMenu, new TagSelectedFilesAction(tagName, controller))));
Futures.addCallback(getTagsFuture, new FutureCallback<List<MenuItem>>() {
@Override
public void onSuccess(List<MenuItem> result) {
tagSelectedSplitMenu.getItems().setAll(result);
}
@Override
public void onFailure(Throwable throwable) {
logger.log(Level.SEVERE, "Error getting tag names.", throwable);
}
}, Platform::runLater);
}
});
});
},
throwable -> logger.log(Level.SEVERE, "Error getting tag names.", throwable));
addFXCallback(exec.submit(() -> controller.getTagsManager().getNonCategoryTagNames()),
tagNames -> {
//on fx thread
List<MenuItem> menuItems = transform(tagNames,
tagName -> createAutoAssigningMenuItem(tagSelectedSplitMenu, new TagSelectedFilesAction(tagName, controller)));
tagSelectedSplitMenu.getItems().setAll(menuItems);
},
throwable -> logger.log(Level.SEVERE, "Error getting tag names.", throwable)//NON-NLS
);
CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller);
catSelectedSplitMenu.setOnAction(cat5SelectedAction);
catSelectedSplitMenu.setText(cat5SelectedAction.getText());
catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic());
catSelectedSplitMenu.showingProperty().addListener(showing -> {
if (catSelectedSplitMenu.isShowing()) {
List<MenuItem> categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()),
cat -> GuiUtils.createAutoAssigningMenuItem(catSelectedSplitMenu, new CategorizeSelectedFilesAction(cat, controller)));
List<MenuItem> categoryMenues = transform(asList(DhsImageCategory.values()),
cat -> createAutoAssigningMenuItem(catSelectedSplitMenu, new CategorizeSelectedFilesAction(cat, controller)));
catSelectedSplitMenu.getItems().setAll(categoryMenues);
}
});
slideShowToggle.getStyleClass().remove("radio-button");
slideShowToggle.getStyleClass().add("toggle-button");
@ -681,7 +675,6 @@ public class GroupPane extends BorderPane {
}
void makeSelection(Boolean shiftDown, Long newFileID) {
if (shiftDown) {
//TODO: do more hear to implement slicker multiselect
int endIndex = grouping.get().getFileIDs().indexOf(newFileID);
@ -694,7 +687,6 @@ public class GroupPane extends BorderPane {
} else {
selectionAnchorIndex = null;
selectionModel.clearAndSelect(newFileID);
}
}

View File

@ -22,6 +22,7 @@ import com.google.common.eventbus.Subscribe;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import static java.util.Collections.singletonMap;
import java.util.List;
import java.util.Objects;
import static java.util.Objects.isNull;
@ -112,9 +113,7 @@ public class MetaDataPane extends DrawableUIBase {
});
copyMenuItem.setAccelerator(COPY_KEY_COMBINATION);
copyMenuItem.setOnAction(actionEvent -> {
copyValueToClipBoard();
});
copyMenuItem.setOnAction(actionEvent -> copyValueToClipBoard());
tableView.setContextMenu(contextMenu);
tableView.setOnKeyPressed((KeyEvent event) -> {
@ -220,9 +219,6 @@ public class MetaDataPane extends DrawableUIBase {
return imageBorder;
}
/**
* {@inheritDoc }
*/
@Subscribe
@Override
public void handleCategoryChanged(CategoryManager.CategoryChangeEvent evt) {
@ -256,9 +252,9 @@ public class MetaDataPane extends DrawableUIBase {
private void copyValueToClipBoard() {
Pair<DrawableAttribute<?>, Collection<?>> selectedItem = tableView.getSelectionModel().getSelectedItem();
if (nonNull(selectedItem)) {
Clipboard.getSystemClipboard().setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT,
getValueDisplayString(selectedItem)));
Clipboard.getSystemClipboard().setContent(
singletonMap(DataFormat.PLAIN_TEXT, getValueDisplayString(selectedItem))
);
}
}
}

View File

@ -131,8 +131,8 @@ public class SlideShowView extends DrawableTileBase {
getGroupPane().grouping().addListener(observable -> {
syncButtonVisibility();
if (getGroupPane().getGroup() != null) {
getGroupPane().getGroup().getFileIDs().addListener((Observable observable1) ->
syncButtonVisibility());
getGroupPane().getGroup().getFileIDs().addListener((Observable observable1)
-> syncButtonVisibility());
}
});
}
@ -215,9 +215,7 @@ public class SlideShowView extends DrawableTileBase {
mediaTask = null;
}
});
myTask.setOnCancelled(cancelled -> {
disposeContent();
});
myTask.setOnCancelled(cancelled -> disposeContent());
exec.execute(myTask);
return progressNode;
@ -245,7 +243,6 @@ public class SlideShowView extends DrawableTileBase {
/**
*
* @param file the value of file
* @param imageTask the value of imageTask
*/
@Override
@ -259,9 +256,6 @@ public class SlideShowView extends DrawableTileBase {
return maskerPane;
}
/**
* {@inheritDoc }
*/
@Override
protected String getTextForLabel() {
return getFile().map(DrawableFile::getName).orElse("") + " " + getSupplementalText();
@ -301,9 +295,6 @@ public class SlideShowView extends DrawableTileBase {
}
/**
* {@inheritDoc }
*/
@Override
@ThreadConfined(type = ThreadType.ANY)
public DhsImageCategory updateCategory() {

View File

@ -18,11 +18,16 @@
*/
package org.sleuthkit.autopsy.imagegallery.utils;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import javafx.application.Platform;
import javafx.concurrent.Task;
/**
@ -46,4 +51,22 @@ public final class TaskUtils {
return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder().setNameFormat("Image Gallery " + clazz.getSimpleName() + " BG Thread").build()));
}
public static <X> void addFXCallback(ListenableFuture<X> future, Consumer<X> onSuccess, Consumer<Throwable> onFailure) {
Futures.addCallback(future, makeFutureCallBack(onSuccess, onFailure), Platform::runLater);
}
public static <X> FutureCallback< X> makeFutureCallBack(Consumer<X> onSuccess, Consumer<Throwable> onFailure) {
return new FutureCallback<X>() {
@Override
public void onSuccess(X result) {
onSuccess.accept(result);
}
@Override
public void onFailure(Throwable t) {
onFailure.accept(t);
}
};
}
}