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 //register for category events
controller.getCategoryManager().registerListener(this); controller.getCategoryManager().registerListener(this);
handleCategoryChanged(null); new Thread(() -> handleCategoryChanged(null)).start();
} }
public SummaryTablePane(ImageGalleryController controller) { 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.GroupSortBy;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils;
import static org.sleuthkit.autopsy.imagegallery.utils.TaskUtils.addFXCallback;
import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.DataSource;
/** /**
@ -255,6 +256,7 @@ public class Toolbar extends ToolBar {
evt -> { evt -> {
Platform.runLater(() -> { Platform.runLater(() -> {
Optional<DataSource> selectedItem = dataSourceSelectionModel.getSelectedItem(); Optional<DataSource> selectedItem = dataSourceSelectionModel.getSelectedItem();
//restore selection once the sync is done.
syncDataSources().addListener(() -> dataSourceSelectionModel.select(selectedItem), Platform::runLater); syncDataSources().addListener(() -> dataSourceSelectionModel.select(selectedItem), Platform::runLater);
}); });
}); });
@ -269,33 +271,30 @@ public class Toolbar extends ToolBar {
} }
private void initTagMenuButton() { private void initTagMenuButton() {
ListenableFuture<TagGroupAction> future = exec.submit(() -> new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller)); addFXCallback(exec.submit(() -> new TagGroupAction(controller.getTagsManager().getFollowUpTagName(), controller)),
Futures.addCallback(future, new FutureCallback<TagGroupAction>() { followUpGroupAction -> {
@Override //on fx thread
public void onSuccess(TagGroupAction followUpGroupAction) { tagGroupMenuButton.setOnAction(followUpGroupAction);
tagGroupMenuButton.setOnAction(followUpGroupAction); tagGroupMenuButton.setText(followUpGroupAction.getText());
tagGroupMenuButton.setText(followUpGroupAction.getText()); tagGroupMenuButton.setGraphic(followUpGroupAction.getGraphic());
tagGroupMenuButton.setGraphic(followUpGroupAction.getGraphic()); },
} throwable -> {
/*
@Override * The problem appears to be a timing issue where a case is
public void onFailure(Throwable throwable) { * closed before this initialization is completed, which It
/* * appears to be harmless, so we are temporarily changing
* The problem appears to be a timing issue where a case is * this log message to a WARNING.
* closed before this initialization is completed, which It *
* appears to be harmless, so we are temporarily changing this * TODO (JIRA-3010): SEVERE error logged by image Gallery UI
* log message to a WARNING. */
* if (Case.isCaseOpen()) {
* TODO (JIRA-3010): SEVERE error logged by image Gallery UI logger.log(Level.WARNING, "Could not create Follow Up tag menu item", throwable); //NON-NLS
*/ } else {
if (Case.isCaseOpen()) { // don't add stack trace to log because it makes looking for real errors harder
logger.log(Level.WARNING, "Could not create Follow Up tag menu item", throwable); //NON-NLS logger.log(Level.INFO, "Unable to get tag name. Case is closed."); //NON-NLS
} else { }
// don't add stack trace to log because it makes looking for real errors harder
logger.log(Level.INFO, "Unable to get tag name. Case is closed."); //NON-NLS
} }
} );
}, Platform::runLater);
tagGroupMenuButton.showingProperty().addListener(showing -> { tagGroupMenuButton.showingProperty().addListener(showing -> {
if (tagGroupMenuButton.isShowing()) { if (tagGroupMenuButton.isShowing()) {
@ -303,24 +302,18 @@ public class Toolbar extends ToolBar {
return Lists.transform(controller.getTagsManager().getNonCategoryTagNames(), return Lists.transform(controller.getTagsManager().getNonCategoryTagNames(),
tagName -> GuiUtils.createAutoAssigningMenuItem(tagGroupMenuButton, new TagGroupAction(tagName, controller))); 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 addFXCallback(getTagsFuture,
public void onFailure(Throwable t) { menuItems -> tagGroupMenuButton.getItems().setAll(menuItems),
logger.log(Level.SEVERE, "Error getting non-gategory tag names.", t); throwable -> logger.log(Level.SEVERE, "Error getting non-gategory tag names.", throwable)
} );
}, Platform::runLater);
} }
}); });
} }
@ThreadConfined(type = ThreadConfined.ThreadType.ANY) @ThreadConfined(type = ThreadConfined.ThreadType.ANY)
private ListenableFuture<List<DataSource>> syncDataSources() { private ListenableFuture<List<DataSource>> syncDataSources() {
ListenableFuture<List<DataSource>> future = exec.submit(() -> { ListenableFuture<List<DataSource>> dataSourcesFuture = exec.submit(() -> {
List<DataSource> dataSourcesInCase = controller.getSleuthKitCase().getDataSources(); List<DataSource> dataSourcesInCase = controller.getSleuthKitCase().getDataSources();
synchronized (dataSourcesViewable) { synchronized (dataSourcesViewable) {
dataSourcesViewable.clear(); dataSourcesViewable.clear();
@ -331,22 +324,18 @@ public class Toolbar extends ToolBar {
} }
return dataSourcesInCase; return dataSourcesInCase;
}); });
Futures.addCallback(future, new FutureCallback<List<DataSource>>() { addFXCallback(dataSourcesFuture,
@Override result -> {
public void onSuccess(List<DataSource> result) { //on fx thread
List<Optional<DataSource>> newDataSources = new ArrayList<>(); List<Optional<DataSource>> newDataSources = new ArrayList<>(Lists.transform(result, Optional::of));
newDataSources.add(Optional.empty()); newDataSources.add(0, Optional.empty());
result.forEach(dataSource -> newDataSources.add(Optional.of(dataSource))); dataSources.setAll(newDataSources);
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. * selection.
*/ */
private void syncGroupControlsEnabledState(GroupViewState newViewState) { private void syncGroupControlsEnabledState(GroupViewState newViewState) {
boolean noGroupSelected = (null == newViewState) boolean noGroupSelected = (null == newViewState) || (null == newViewState.getGroup());
|| (null == newViewState.getGroup());
Platform.runLater(() -> { Platform.runLater(() -> {
tagGroupMenuButton.setDisable(noGroupSelected); tagGroupMenuButton.setDisable(noGroupSelected);
catGroupMenuButton.setDisable(noGroupSelected); catGroupMenuButton.setDisable(noGroupSelected);
@ -402,7 +390,5 @@ public class Toolbar extends ToolBar {
public Toolbar(ImageGalleryController controller) { public Toolbar(ImageGalleryController controller) {
this.controller = controller; this.controller = controller;
FXMLConstructor.construct(this, "Toolbar.fxml"); //NON-NLS 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.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.util.concurrent.FutureCallback; import static com.google.common.collect.Lists.transform;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import static java.util.Arrays.asList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -35,6 +35,7 @@ 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.Optional; import java.util.Optional;
import java.util.function.Consumer;
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;
@ -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.GroupViewMode;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState; import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils; import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
import static org.sleuthkit.autopsy.imagegallery.gui.GuiUtils.createAutoAssigningMenuItem;
import org.sleuthkit.autopsy.imagegallery.utils.TaskUtils; 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; import org.sleuthkit.datamodel.TskCoreException;
/** /**
@ -254,7 +258,7 @@ public class GroupPane extends BorderPane {
private final ReadOnlyObjectWrapper<DrawableGroup> grouping = new ReadOnlyObjectWrapper<>(); 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 * to determine whether fileIDs are visible or are offscreen. No entry
* indicates the given fileID is not displayed on screen. DrawableCells are * indicates the given fileID is not displayed on screen. DrawableCells are
* responsible for adding and removing themselves from this map. * responsible for adding and removing themselves from this map.
@ -371,7 +375,7 @@ public class GroupPane extends BorderPane {
case FIVE: case FIVE:
return cat5Toggle; return cat5Toggle;
default: 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); DoubleBinding cellSize = controller.thumbnailSizeProperty().add(75);
gridView.cellHeightProperty().bind(cellSize); gridView.cellHeightProperty().bind(cellSize);
gridView.cellWidthProperty().bind(cellSize); gridView.cellWidthProperty().bind(cellSize);
gridView.setCellFactory((GridView<Long> param) -> new DrawableCell()); gridView.setCellFactory(param -> new DrawableCell());
BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected()); BooleanBinding isSelectionEmpty = Bindings.isEmpty(selectionModel.getSelected());
catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); catSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty); tagSelectedSplitMenu.disableProperty().bind(isSelectionEmpty);
TagSelectedFilesAction followUpSelectedAction = new TagSelectedFilesAction(controller.getTagsManager().getFollowUpTagName(), controller); //NON-NLS addFXCallback(exec.submit(() -> controller.getTagsManager().getFollowUpTagName()),
Platform.runLater(() -> { followUpTagName -> {
tagSelectedSplitMenu.setText(followUpSelectedAction.getText()); //on fx thread
tagSelectedSplitMenu.setGraphic(followUpSelectedAction.getGraphic()); TagSelectedFilesAction followUpSelectedAction = new TagSelectedFilesAction(followUpTagName, controller);
tagSelectedSplitMenu.setOnAction(followUpSelectedAction); tagSelectedSplitMenu.setText(followUpSelectedAction.getText());
tagSelectedSplitMenu.showingProperty().addListener(showing -> { tagSelectedSplitMenu.setGraphic(followUpSelectedAction.getGraphic());
if (tagSelectedSplitMenu.isShowing()) { tagSelectedSplitMenu.setOnAction(followUpSelectedAction);
},
ListenableFuture<List<MenuItem>> getTagsFuture = exec.submit(() throwable -> logger.log(Level.SEVERE, "Error getting tag names.", throwable));
-> 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);
}
});
});
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); CategorizeSelectedFilesAction cat5SelectedAction = new CategorizeSelectedFilesAction(DhsImageCategory.FIVE, controller);
catSelectedSplitMenu.setOnAction(cat5SelectedAction); catSelectedSplitMenu.setOnAction(cat5SelectedAction);
catSelectedSplitMenu.setText(cat5SelectedAction.getText()); catSelectedSplitMenu.setText(cat5SelectedAction.getText());
catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic()); catSelectedSplitMenu.setGraphic(cat5SelectedAction.getGraphic());
catSelectedSplitMenu.showingProperty().addListener(showing -> {
if (catSelectedSplitMenu.isShowing()) { List<MenuItem> categoryMenues = transform(asList(DhsImageCategory.values()),
List<MenuItem> categoryMenues = Lists.transform(Arrays.asList(DhsImageCategory.values()), cat -> createAutoAssigningMenuItem(catSelectedSplitMenu, new CategorizeSelectedFilesAction(cat, controller)));
cat -> GuiUtils.createAutoAssigningMenuItem(catSelectedSplitMenu, new CategorizeSelectedFilesAction(cat, controller))); catSelectedSplitMenu.getItems().setAll(categoryMenues);
catSelectedSplitMenu.getItems().setAll(categoryMenues);
}
});
slideShowToggle.getStyleClass().remove("radio-button"); slideShowToggle.getStyleClass().remove("radio-button");
slideShowToggle.getStyleClass().add("toggle-button"); slideShowToggle.getStyleClass().add("toggle-button");
@ -681,7 +675,6 @@ public class GroupPane extends BorderPane {
} }
void makeSelection(Boolean shiftDown, Long newFileID) { void makeSelection(Boolean shiftDown, Long newFileID) {
if (shiftDown) { if (shiftDown) {
//TODO: do more hear to implement slicker multiselect //TODO: do more hear to implement slicker multiselect
int endIndex = grouping.get().getFileIDs().indexOf(newFileID); int endIndex = grouping.get().getFileIDs().indexOf(newFileID);
@ -694,7 +687,6 @@ public class GroupPane extends BorderPane {
} else { } else {
selectionAnchorIndex = null; selectionAnchorIndex = null;
selectionModel.clearAndSelect(newFileID); selectionModel.clearAndSelect(newFileID);
} }
} }

View File

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

View File

@ -131,8 +131,8 @@ public class SlideShowView extends DrawableTileBase {
getGroupPane().grouping().addListener(observable -> { getGroupPane().grouping().addListener(observable -> {
syncButtonVisibility(); syncButtonVisibility();
if (getGroupPane().getGroup() != null) { if (getGroupPane().getGroup() != null) {
getGroupPane().getGroup().getFileIDs().addListener((Observable observable1) -> getGroupPane().getGroup().getFileIDs().addListener((Observable observable1)
syncButtonVisibility()); -> syncButtonVisibility());
} }
}); });
} }
@ -215,9 +215,7 @@ public class SlideShowView extends DrawableTileBase {
mediaTask = null; mediaTask = null;
} }
}); });
myTask.setOnCancelled(cancelled -> { myTask.setOnCancelled(cancelled -> disposeContent());
disposeContent();
});
exec.execute(myTask); exec.execute(myTask);
return progressNode; return progressNode;
@ -245,7 +243,6 @@ public class SlideShowView extends DrawableTileBase {
/** /**
* *
* @param file the value of file
* @param imageTask the value of imageTask * @param imageTask the value of imageTask
*/ */
@Override @Override
@ -259,9 +256,6 @@ public class SlideShowView extends DrawableTileBase {
return maskerPane; return maskerPane;
} }
/**
* {@inheritDoc }
*/
@Override @Override
protected String getTextForLabel() { protected String getTextForLabel() {
return getFile().map(DrawableFile::getName).orElse("") + " " + getSupplementalText(); return getFile().map(DrawableFile::getName).orElse("") + " " + getSupplementalText();
@ -292,8 +286,8 @@ public class SlideShowView extends DrawableTileBase {
* of y" * of y"
*/ */
@NbBundle.Messages({"# {0} - file id number", @NbBundle.Messages({"# {0} - file id number",
"# {1} - number of file ids", "# {1} - number of file ids",
"SlideShowView.supplementalText={0} of {1} in group"}) "SlideShowView.supplementalText={0} of {1} in group"})
private String getSupplementalText() { private String getSupplementalText() {
final ObservableList<Long> fileIds = getGroupPane().getGroup().getFileIDs(); final ObservableList<Long> fileIds = getGroupPane().getGroup().getFileIDs();
return getFileID().map(fileID -> " ( " + Bundle.SlideShowView_supplementalText(fileIds.indexOf(fileID) + 1, fileIds.size()) + " )") return getFileID().map(fileID -> " ( " + Bundle.SlideShowView_supplementalText(fileIds.indexOf(fileID) + 1, fileIds.size()) + " )")
@ -301,9 +295,6 @@ public class SlideShowView extends DrawableTileBase {
} }
/**
* {@inheritDoc }
*/
@Override @Override
@ThreadConfined(type = ThreadType.ANY) @ThreadConfined(type = ThreadType.ANY)
public DhsImageCategory updateCategory() { public DhsImageCategory updateCategory() {

View File

@ -18,11 +18,16 @@
*/ */
package org.sleuthkit.autopsy.imagegallery.utils; 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.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.function.Consumer;
import javafx.application.Platform;
import javafx.concurrent.Task; import javafx.concurrent.Task;
/** /**
@ -42,8 +47,26 @@ public final class TaskUtils {
}; };
} }
public static ListeningExecutorService getExecutorForClass(Class<?> clazz) { public static ListeningExecutorService getExecutorForClass(Class<?> clazz) {
return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor( return MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder().setNameFormat("Image Gallery " + clazz.getSimpleName() + " BG Thread").build())); 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);
}
};
}
} }