diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index d44f6307084..9c54e679995 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -1025,6 +1025,7 @@ public void addTab(LibraryTab libraryTab, boolean raisePanel) { libraryTab.setOnCloseRequest(event -> { closeTab(libraryTab); + libraryTab.getDataLoadingTask().cancel(); event.consume(); }); diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 2cb7174d95c..b8f1f8aabf5 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -14,17 +14,21 @@ import javafx.collections.ListChangeListener; import javafx.geometry.Orientation; import javafx.scene.Node; +import javafx.scene.control.ProgressIndicator; import javafx.scene.control.SplitPane; import javafx.scene.control.Tab; import javafx.scene.control.Tooltip; +import javafx.scene.layout.BorderPane; import org.jabref.gui.autocompleter.AutoCompletePreferences; import org.jabref.gui.autocompleter.PersonNameSuggestionProvider; import org.jabref.gui.autocompleter.SuggestionProviders; import org.jabref.gui.collab.DatabaseChangeMonitor; import org.jabref.gui.collab.DatabaseChangePane; +import org.jabref.gui.dialogs.AutosaveUiManager; import org.jabref.gui.entryeditor.EntryEditor; import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.maintable.MainTable; import org.jabref.gui.maintable.MainTableDataModel; import org.jabref.gui.specialfields.SpecialFieldDatabaseChangeListener; @@ -33,8 +37,12 @@ import org.jabref.gui.undo.UndoableFieldChange; import org.jabref.gui.undo.UndoableInsertEntries; import org.jabref.gui.undo.UndoableRemoveEntries; +import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.DefaultTaskExecutor; +import org.jabref.logic.autosaveandbackup.AutosaveManager; +import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.citationstyle.CitationStyleCache; +import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; import org.jabref.logic.pdf.FileAnnotationCache; import org.jabref.logic.search.SearchQuery; @@ -64,11 +72,11 @@ public class LibraryTab extends Tab { private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); - private final BibDatabaseContext bibDatabaseContext; - private final MainTableDataModel tableModel; + private BibDatabaseContext bibDatabaseContext; + private MainTableDataModel tableModel; - private final CitationStyleCache citationStyleCache; - private final FileAnnotationCache annotationCache; + private CitationStyleCache citationStyleCache; + private FileAnnotationCache annotationCache; private final JabRefFrame frame; private final CountingUndoManager undoManager; @@ -76,7 +84,7 @@ public class LibraryTab extends Tab { private final SidePaneManager sidePaneManager; private final ExternalFileTypes externalFileTypes; - private final EntryEditor entryEditor; + private EntryEditor entryEditor; private final DialogService dialogService; private final PreferencesService preferencesService; @@ -93,10 +101,13 @@ public class LibraryTab extends Tab { private BibEntry showing; private SuggestionProviders suggestionProviders; - @SuppressWarnings({"FieldCanBeLocal"}) private Subscription dividerPositionSubscription; + @SuppressWarnings({"FieldCanBeLocal"}) + private Subscription dividerPositionSubscription; // the query the user searches when this BasePanel is active private Optional currentSearchQuery = Optional.empty(); private Optional changeMonitor = Optional.empty(); + // initializing it so we prevent NullPointerException + private BackgroundTask dataLoadingTask = BackgroundTask.wrap(() -> null); public LibraryTab(JabRefFrame frame, PreferencesService preferencesService, @@ -140,15 +151,105 @@ public LibraryTab(JabRefFrame frame, }); } + public void setDataLoadingTask(BackgroundTask dataLoadingTask) { + this.dataLoadingTask = dataLoadingTask; + } + + public BackgroundTask getDataLoadingTask() { + return dataLoadingTask; + } + + /* The layout to display in the tab when it's loading*/ + public Node createLoadingAnimationLayout() { + ProgressIndicator progressIndicator = new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS); + BorderPane pane = new BorderPane(); + pane.setCenter(progressIndicator); + + return pane; + } + + public void onDatabaseLoadingStarted() { + Node loadingLayout = createLoadingAnimationLayout(); + getMainTable().placeholderProperty().setValue(loadingLayout); + + frame.addTab(this, true); + } + + public void onDatabaseLoadingSucceed(ParserResult result) { + BibDatabaseContext context = result.getDatabaseContext(); + OpenDatabaseAction.performPostOpenActions(this, result); + + feedData(context); + // a temporary workaround to update groups pane + Globals.stateManager.activeDatabaseProperty().bind( + EasyBind.map(frame.getTabbedPane().getSelectionModel().selectedItemProperty(), + selectedTab -> Optional.ofNullable(selectedTab) + .map(tab -> (LibraryTab) tab) + .map(LibraryTab::getBibDatabaseContext))); + } + + public void onDatabaseLoadingFailed(Exception ex) { + String title = Localization.lang("Connection error"); + String content = String.format("%s\n\n%s", ex.getMessage(), Localization.lang("A local copy will be opened.")); + + dialogService.showErrorDialogAndWait(title, content, ex); + } + + public void feedData(BibDatabaseContext bibDatabaseContext) { + cleanUp(); + + this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext); + + bibDatabaseContext.getDatabase().registerListener(this); + bibDatabaseContext.getMetaData().registerListener(this); + + this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferencesService, Globals.stateManager); + citationStyleCache = new CitationStyleCache(bibDatabaseContext); + annotationCache = new FileAnnotationCache(bibDatabaseContext, preferencesService.getFilePreferences()); + + setupMainPanel(); + setupAutoCompletion(); + + this.getDatabase().registerListener(new SearchListener()); + this.getDatabase().registerListener(new EntriesRemovedListener()); + + // ensure that at each addition of a new entry, the entry is added to the groups interface + this.bibDatabaseContext.getDatabase().registerListener(new GroupTreeListener()); + // ensure that all entry changes mark the panel as changed + this.bibDatabaseContext.getDatabase().registerListener(this); + + this.getDatabase().registerListener(new UpdateTimestampListener(preferencesService)); + + this.entryEditor = new EntryEditor(this, externalFileTypes); + + Platform.runLater(() -> { + EasyBind.subscribe(changedProperty, this::updateTabTitle); + Globals.stateManager.getOpenDatabases().addListener((ListChangeListener) c -> + updateTabTitle(changedProperty.getValue())); + }); + + if (isDatabaseReadyForAutoSave(bibDatabaseContext)) { + AutosaveManager autoSaver = AutosaveManager.start(bibDatabaseContext); + autoSaver.registerListener(new AutosaveUiManager(this)); + } + + BackupManager.start(this.bibDatabaseContext, Globals.entryTypesManager, Globals.prefs); + } + + private boolean isDatabaseReadyForAutoSave(BibDatabaseContext context) { + return ((context.getLocation() == DatabaseLocation.SHARED) || + ((context.getLocation() == DatabaseLocation.LOCAL) && preferencesService.getShouldAutosave())) + && + context.getDatabasePath().isPresent(); + } + /** - * Sets the title of the tab - * modification-asterisk filename – path-fragment - * - * The modification-asterisk (*) is shown if the file was modified since last save - * (path-fragment is only shown if filename is not (globally) unique) - * - * Example: - * *jabref-authors.bib – testbib + * Sets the title of the tab modification-asterisk filename – path-fragment + *

+ * The modification-asterisk (*) is shown if the file was modified since last save (path-fragment is only shown if + * filename is not (globally) unique) + *

+ * Example: *jabref-authors.bib – testbib */ public void updateTabTitle(boolean isChanged) { boolean isAutosaveEnabled = preferencesService.getShouldAutosave(); @@ -189,9 +290,9 @@ public void updateTabTitle(boolean isChanged) { // Unique path fragment List uniquePathParts = FileUtil.uniquePathSubstrings(collectAllDatabasePaths()); Optional uniquePathPart = uniquePathParts.stream() - .filter(part -> databasePath.toString().contains(part) - && !part.equals(fileName) && part.contains(File.separator)) - .findFirst(); + .filter(part -> databasePath.toString().contains(part) + && !part.equals(fileName) && part.contains(File.separator)) + .findFirst(); if (uniquePathPart.isPresent()) { String uniquePath = uniquePathPart.get(); // remove filename @@ -330,9 +431,8 @@ public void insertEntry(final BibEntry bibEntry) { } /** - * This method is called from JabRefFrame when the user wants to create a new entry or entries. - * It is necessary when the user would expect the added entry or one of the added entries - * to be selected in the entry editor + * This method is called from JabRefFrame when the user wants to create a new entry or entries. It is necessary when + * the user would expect the added entry or one of the added entries to be selected in the entry editor * * @param entries The new entries. */ @@ -585,6 +685,8 @@ private void saveDividerLocation(Number position) { */ public void cleanUp() { changeMonitor.ifPresent(DatabaseChangeMonitor::unregister); + AutosaveManager.shutdown(bibDatabaseContext); + BackupManager.shutdown(bibDatabaseContext); } /** @@ -742,4 +844,21 @@ public void listen(EntriesRemovedEvent removedEntriesEvent) { DefaultTaskExecutor.runInJavaFXThread(() -> frame.getGlobalSearchBar().performSearch()); } } + + public static class Factory { + public LibraryTab createLibraryTab(JabRefFrame frame, Path file, BackgroundTask dataLoadingTask) { + BibDatabaseContext context = new BibDatabaseContext(); + context.setDatabasePath(file); + + LibraryTab newTab = new LibraryTab(frame, frame.prefs(), context, ExternalFileTypes.getInstance()); + newTab.setDataLoadingTask(dataLoadingTask); + + dataLoadingTask.onRunning(newTab::onDatabaseLoadingStarted) + .onSuccess(newTab::onDatabaseLoadingSucceed) + .onFailure(newTab::onDatabaseLoadingFailed) + .executeWith(Globals.TASK_EXECUTOR); + + return newTab; + } + } } diff --git a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java index 8b2f79ec02d..133b436ef49 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -6,8 +6,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -17,8 +19,6 @@ import org.jabref.gui.LibraryTab; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.dialogs.BackupUIManager; -import org.jabref.gui.externalfiletype.ExternalFileTypes; -import org.jabref.gui.importer.ParserResultWarningDialog; import org.jabref.gui.shared.SharedDatabaseUIManager; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.FileDialogConfiguration; @@ -59,8 +59,8 @@ public OpenDatabaseAction(JabRefFrame frame) { /** * Go through the list of post open actions, and perform those that need to be performed. * - * @param libraryTab The BasePanel where the database is shown. - * @param result The result of the BIB file parse operation. + * @param libraryTab The BasePanel where the database is shown. + * @param result The result of the BIB file parse operation. */ public static void performPostOpenActions(LibraryTab libraryTab, ParserResult result) { for (GUIPostOpenAction action : OpenDatabaseAction.POST_OPEN_ACTIONS) { @@ -159,17 +159,15 @@ public void openFiles(List filesToOpen, boolean raisePanel) { */ private void openTheFile(Path file, boolean raisePanel) { Objects.requireNonNull(file); - if (Files.exists(file)) { - - BackgroundTask.wrap(() -> loadDatabase(file)) - .onSuccess(result -> { - LibraryTab libraryTab = addNewDatabase(result, file, raisePanel); - OpenDatabaseAction.performPostOpenActions(libraryTab, result); - }) - .onFailure(ex -> dialogService.showErrorDialogAndWait(Localization.lang("Connection error"), - ex.getMessage() + "\n\n" + Localization.lang("A local copy will be opened."))) - .executeWith(Globals.TASK_EXECUTOR); + if (!Files.exists(file)) { + return; } + + BackgroundTask backgroundTask = BackgroundTask.wrap(() -> loadDatabase(file)); + LibraryTab.Factory libraryTabFactory = new LibraryTab.Factory(); + LibraryTab newTab = libraryTabFactory.createLibraryTab(frame, file, backgroundTask); + + backgroundTask.onFinished(() -> trackOpenNewDatabase(newTab)); } private ParserResult loadDatabase(Path file) throws Exception { @@ -201,13 +199,11 @@ private ParserResult loadDatabase(Path file) throws Exception { return result; } - private LibraryTab addNewDatabase(ParserResult result, final Path file, boolean raisePanel) { - if (result.hasWarnings()) { - ParserResultWarningDialog.showParserResultWarningDialog(result, frame); - } + private void trackOpenNewDatabase(LibraryTab libraryTab) { + Map properties = new HashMap<>(); + Map measurements = new HashMap<>(); + measurements.put("NumberOfEntries", (double) libraryTab.getBibDatabaseContext().getDatabase().getEntryCount()); - LibraryTab libraryTab = new LibraryTab(frame, Globals.prefs, result.getDatabaseContext(), ExternalFileTypes.getInstance()); - frame.addTab(libraryTab, raisePanel); - return libraryTab; + Globals.getTelemetryClient().ifPresent(client -> client.trackEvent("OpenNewDatabase", properties, measurements)); } }