Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix GroupTree wrongly displayed #9451

Merged
merged 8 commits into from
Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
- We fixed an issue where editing entry's "date" field in library mode "biblatex" causes an uncaught exception. [#8747](https://github.com/JabRef/jabref/issues/8747)
- We fixed an issue where journal abbreviations would not abbreviate journal titles with escaped ampersands (\\&). [#8948](https://github.com/JabRef/jabref/issues/8948)
- We fixed an issue where font size preferences did not apply correctly to preference dialog window and the menu bar. [#8386](https://github.com/JabRef/jabref/issues/8386) and [#9279](https://github.com/JabRef/jabref/issues/9279)
- We fixed an issue that JabRef displayed the wrong group tree after loading. [koppor#637](https://github.com/koppor/jabref/issues/637)
- We fixed an issue when using an unsafe character in the citation key, the auto-linking feature fails to link files. [#9267](https://github.com/JabRef/jabref/issues/9267)

### Removed
Expand Down
14 changes: 5 additions & 9 deletions src/main/java/org/jabref/gui/JabRefFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TimerTask;
Expand Down Expand Up @@ -1115,8 +1114,8 @@ public void addTab(LibraryTab libraryTab, boolean raisePanel) {
tabbedPane.getTabs().add(libraryTab);

libraryTab.setOnCloseRequest(event -> {
libraryTab.cancelLoading();
closeTab(libraryTab);
libraryTab.getDataLoadingTask().cancel();
event.consume();
});

Expand All @@ -1129,13 +1128,10 @@ public void addTab(LibraryTab libraryTab, boolean raisePanel) {
libraryTab.getUndoManager().registerListener(new UndoRedoEventManager());
}

private void trackOpenNewDatabase(LibraryTab libraryTab) {
Globals.getTelemetryClient().ifPresent(client -> client.trackEvent(
"OpenNewDatabase",
Map.of(),
Map.of("NumberOfEntries", (double) libraryTab.getBibDatabaseContext().getDatabase().getEntryCount())));
}

/**
* Opens a new tab with existing data.
* Asynchronous loading is done at {@link LibraryTab#createLibraryTab(JabRefFrame, PreferencesService, StateManager, ThemeManager, Path, BackgroundTask, ImportFormatReader)}.
*/
public LibraryTab addTab(BibDatabaseContext databaseContext, boolean raisePanel) {
Objects.requireNonNull(databaseContext);

Expand Down
54 changes: 29 additions & 25 deletions src/main/java/org/jabref/gui/LibraryTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,7 @@ public class LibraryTab extends Tab {

private Optional<DatabaseChangeMonitor> changeMonitor = Optional.empty();

// initializing it so we prevent NullPointerException
private BackgroundTask<ParserResult> dataLoadingTask = BackgroundTask.wrap(() -> null);
private BackgroundTask<ParserResult> dataLoadingTask;

private final IndexingTaskManager indexingTaskManager = new IndexingTaskManager(Globals.TASK_EXECUTOR);
private final ImportFormatReader importFormatReader;
Expand Down Expand Up @@ -189,15 +188,19 @@ private static void addSharedDbInformation(StringBuilder text, BibDatabaseContex
text.append("]");
}

public BackgroundTask<?> getDataLoadingTask() {
return dataLoadingTask;
}

public void setDataLoadingTask(BackgroundTask<ParserResult> dataLoadingTask) {
this.dataLoadingTask = dataLoadingTask;
}

/* The layout to display in the tab when it's loading*/
public void cancelLoading() {
if (dataLoadingTask != null) {
dataLoadingTask.cancel();
}
}

/**
* 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();
Expand All @@ -209,7 +212,6 @@ public Node createLoadingAnimationLayout() {
public void onDatabaseLoadingStarted() {
Node loadingLayout = createLoadingAnimationLayout();
getMainTable().placeholderProperty().setValue(loadingLayout);

frame.addTab(this, true);
}

Expand Down Expand Up @@ -239,11 +241,15 @@ public void feedData(BibDatabaseContext bibDatabaseContext) {
cleanUp();

this.bibDatabaseContext = Objects.requireNonNull(bibDatabaseContext);
// When you open an existing library, a library tab with a loading animation is added immediately.
// At that point, the library tab is given a temporary bibDatabaseContext with no entries.
// This line is necessary because, while there is already a binding that updates the active database when a new tab is added,
// it doesn't handle the case when a library is loaded asynchronously.
stateManager.setActiveDatabase(bibDatabaseContext);

if (this.getTabPane().getSelectionModel().selectedItemProperty().equals(this)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (this.getTabPane().getSelectionModel().selectedItemProperty().equals(this)) {
if (this.getTabPane().getSelectionModel().selectedItemProperty().get().equals(this)) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@calixtus prevented me from using frame.getCurrentLibraryTab().equals(this), because he wants to get rid of frame. Fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I did and I'm not even ashamed of it.

// When you open an existing library, a library tab with a loading animation is added immediately.
// At that point, the library tab is given a temporary bibDatabaseContext with no entries.
// This line is necessary because, while there is already a binding that updates the active database when a new tab is added,
// it doesn't handle the case when a library is loaded asynchronously.
// See org.jabref.gui.LibraryTab.createLibraryTab for the asynchronous loading.
stateManager.setActiveDatabase(bibDatabaseContext);
}

bibDatabaseContext.getDatabase().registerListener(this);
bibDatabaseContext.getMetaData().registerListener(this);
Expand Down Expand Up @@ -819,21 +825,19 @@ public void resetChangedProperties() {
this.changedProperty.setValue(false);
}

public static class Factory {
public LibraryTab createLibraryTab(JabRefFrame frame, PreferencesService preferencesService, StateManager stateManager, ThemeManager themeManager, Path file, BackgroundTask<ParserResult> dataLoadingTask, ImportFormatReader importFormatReader) {
BibDatabaseContext context = new BibDatabaseContext();
context.setDatabasePath(file);
public static LibraryTab createLibraryTab(JabRefFrame frame, PreferencesService preferencesService, StateManager stateManager, ThemeManager themeManager, Path file, BackgroundTask<ParserResult> dataLoadingTask, ImportFormatReader importFormatReader) {
BibDatabaseContext context = new BibDatabaseContext();
context.setDatabasePath(file);

LibraryTab newTab = new LibraryTab(frame, preferencesService, stateManager, themeManager, context, importFormatReader);
newTab.setDataLoadingTask(dataLoadingTask);
LibraryTab newTab = new LibraryTab(frame, preferencesService, stateManager, themeManager, context, importFormatReader);

dataLoadingTask.onRunning(newTab::onDatabaseLoadingStarted)
.onSuccess(newTab::onDatabaseLoadingSucceed)
.onFailure(newTab::onDatabaseLoadingFailed)
.executeWith(Globals.TASK_EXECUTOR);
newTab.setDataLoadingTask(dataLoadingTask);
dataLoadingTask.onRunning(newTab::onDatabaseLoadingStarted)
.onSuccess(newTab::onDatabaseLoadingSucceed)
.onFailure(newTab::onDatabaseLoadingFailed)
.executeWith(Globals.TASK_EXECUTOR);

return newTab;
}
return newTab;
}

private class GroupTreeListener {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jabref/gui/collab/DatabaseChange.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void setAccepted(boolean accepted) {
}

/**
* Convinience method for accepting changes
* Convenience method for accepting changes
* */
public void accept() {
setAccepted(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ public class DatabaseChangesResolverDialog extends BaseDialog<Boolean> {

@Inject private UndoManager undoManager;

/**
* A dialog going through given <code>changes</code>, which are diffs to the provided <code>database</code>.
* Each accepted change is written to the provided <code>database</code>.
*
* @param changes The list of changes
* @param database The database to apply the changes to
*/
public DatabaseChangesResolverDialog(List<DatabaseChange> changes, BibDatabaseContext database, DialogService dialogService, StateManager stateManager, ThemeManager themeManager, PreferencesService preferencesService, String dialogTitle) {
this.changes = changes;
this.databaseChangeDetailsViewFactory = new DatabaseChangeDetailsViewFactory(database, dialogService, stateManager, themeManager, preferencesService);
Expand Down
17 changes: 14 additions & 3 deletions src/main/java/org/jabref/gui/dialogs/BackupUIManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;

import javafx.scene.control.ButtonType;

import org.jabref.gui.DialogService;
import org.jabref.gui.Globals;
import org.jabref.gui.backup.BackupResolverDialog;
import org.jabref.gui.collab.DatabaseChange;
import org.jabref.gui.collab.DatabaseChangeList;
import org.jabref.gui.collab.DatabaseChangeResolverFactory;
import org.jabref.gui.collab.DatabaseChangesResolverDialog;
Expand Down Expand Up @@ -54,22 +56,31 @@ private static Optional<ButtonType> showBackupResolverDialog(DialogService dialo

private static Optional<ParserResult> showReviewBackupDialog(DialogService dialogService, Path originalPath, PreferencesService preferencesService) {
try {
Path backupPath = BackupFileUtil.getPathOfLatestExisingBackupFile(originalPath, BackupFileType.BACKUP).orElseThrow();
ImportFormatPreferences importFormatPreferences = Globals.prefs.getImportFormatPreferences();
ParserResult originalParserResult = OpenDatabase.loadDatabase(originalPath, importFormatPreferences, new DummyFileUpdateMonitor());

// The database of the originalParserResult will be modified
ParserResult originalParserResult = OpenDatabase.loadDatabase(originalPath, importFormatPreferences, Globals.getFileUpdateMonitor());
// This will be modified by using the `DatabaseChangesResolverDialog`.
BibDatabaseContext originalDatabase = originalParserResult.getDatabaseContext();

Path backupPath = BackupFileUtil.getPathOfLatestExisingBackupFile(originalPath, BackupFileType.BACKUP).orElseThrow();
BibDatabaseContext backupDatabase = OpenDatabase.loadDatabase(backupPath, importFormatPreferences, new DummyFileUpdateMonitor()).getDatabaseContext();

DatabaseChangeResolverFactory changeResolverFactory = new DatabaseChangeResolverFactory(dialogService, originalDatabase, preferencesService);

return DefaultTaskExecutor.runInJavaFXThread(() -> {
List<DatabaseChange> changes = DatabaseChangeList.compareAndGetChanges(originalDatabase, backupDatabase, changeResolverFactory);
DatabaseChangesResolverDialog reviewBackupDialog = new DatabaseChangesResolverDialog(
DatabaseChangeList.compareAndGetChanges(originalDatabase, backupDatabase, changeResolverFactory),
changes,
originalDatabase, dialogService, Globals.stateManager, Globals.getThemeManager(), Globals.prefs, "Review Backup"
);
var allChangesResolved = dialogService.showCustomDialogAndWait(reviewBackupDialog);
if (allChangesResolved.isEmpty() || !allChangesResolved.get()) {
// In case not all changes are resolved, start from scratch
return showRestoreBackupDialog(dialogService, originalPath, preferencesService);
}

// This does NOT return the original ParserResult, but a modified version with all changes accepted or rejected
return Optional.of(originalParserResult);
});
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import java.nio.file.Path;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -178,9 +177,7 @@ private void openTheFile(Path file) {
}

BackgroundTask<ParserResult> backgroundTask = BackgroundTask.wrap(() -> loadDatabase(file));
koppor marked this conversation as resolved.
Show resolved Hide resolved
LibraryTab.Factory libraryTabFactory = new LibraryTab.Factory();
LibraryTab newTab = libraryTabFactory.createLibraryTab(frame, preferencesService, stateManager, themeManager, file, backgroundTask, Globals.IMPORT_FORMAT_READER);

LibraryTab newTab = LibraryTab.createLibraryTab(frame, preferencesService, stateManager, themeManager, file, backgroundTask, Globals.IMPORT_FORMAT_READER);
backgroundTask.onFinished(() -> trackOpenNewDatabase(newTab));
}

Expand All @@ -195,49 +192,51 @@ private ParserResult loadDatabase(Path file) throws Exception {

preferencesService.getFilePreferences().setWorkingDirectory(fileToLoad.getParent());

ParserResult result = null;
ParserResult parserResult = null;
if (BackupManager.backupFileDiffers(fileToLoad)) {
result = BackupUIManager.showRestoreBackupDialog(dialogService, fileToLoad, preferencesService).orElse(null);
// In case the backup differs, ask the user what to do.
// In case the user opted for restoring a backup, the content of the backup is contained in parserResult.
parserResult = BackupUIManager.showRestoreBackupDialog(dialogService, fileToLoad, preferencesService).orElse(null);
}

try {
if (result == null) {
result = OpenDatabase.loadDatabase(fileToLoad,
if (parserResult == null) {
// No backup was restored, do the "normal" loading
Comment on lines +199 to +205
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well done for including the comments. I wrote that, but if I had to read it again in a year, I wouldn't know what is going on. I guess comments aren't useless after all.

parserResult = OpenDatabase.loadDatabase(fileToLoad,
preferencesService.getImportFormatPreferences(),
Globals.getFileUpdateMonitor());
}

if (result.hasWarnings()) {
if (parserResult.hasWarnings()) {
String content = Localization.lang("Please check your library file for wrong syntax.")
+ "\n\n" + result.getErrorMessage();
+ "\n\n" + parserResult.getErrorMessage();
DefaultTaskExecutor.runInJavaFXThread(() ->
dialogService.showWarningDialogAndWait(Localization.lang("Open library error"), content));
}
} catch (IOException e) {
result = ParserResult.fromError(e);
parserResult = ParserResult.fromError(e);
LOGGER.error("Error opening file '{}'", fileToLoad, e);
}

if (result.getDatabase().isShared()) {
if (parserResult.getDatabase().isShared()) {
try {
new SharedDatabaseUIManager(frame, preferencesService).openSharedDatabaseFromParserResult(result);
new SharedDatabaseUIManager(frame, preferencesService).openSharedDatabaseFromParserResult(parserResult);
} catch (SQLException | DatabaseNotSupportedException | InvalidDBMSConnectionPropertiesException |
NotASharedDatabaseException e) {
result.getDatabaseContext().clearDatabasePath(); // do not open the original file
result.getDatabase().clearSharedDatabaseID();
parserResult.getDatabaseContext().clearDatabasePath(); // do not open the original file
parserResult.getDatabase().clearSharedDatabaseID();
LOGGER.error("Connection error", e);

throw e;
}
}
return result;
return parserResult;
}

private void trackOpenNewDatabase(LibraryTab libraryTab) {
Map<String, String> properties = new HashMap<>();
Map<String, Double> measurements = new HashMap<>();
measurements.put("NumberOfEntries", (double) libraryTab.getBibDatabaseContext().getDatabase().getEntryCount());

Globals.getTelemetryClient().ifPresent(client -> client.trackEvent("OpenNewDatabase", properties, measurements));
Globals.getTelemetryClient().ifPresent(client -> client.trackEvent(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though we currently don't do any telemetry, I think this method needs to be added inside the LibraryTab to the BackgroundTask otherwise it's not excuted If I remember that correctly from my debug session

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user is still asked to provide his OK for telemetry. I only changed the code to a lambda function (which I found more readable).

The method is connected to the backgroudntask on line 181:

backgroundTask.onFinished(() -> trackOpenNewDatabase(newTab))

Thus, I do not see any need to change something here 😇

"OpenNewDatabase",
Map.of(),
Map.of("NumberOfEntries", (double) libraryTab.getBibDatabaseContext().getDatabase().getEntryCount())));
}
}