Skip to content

Commit

Permalink
Allow users to review backup changes before restoring them or merge t…
Browse files Browse the repository at this point in the history
…hem selectively (#9311)
  • Loading branch information
HoussemNasri committed Dec 5, 2022
1 parent 14058a6 commit e86839a
Show file tree
Hide file tree
Showing 38 changed files with 370 additions and 228 deletions.
21 changes: 15 additions & 6 deletions src/main/java/org/jabref/gui/Base.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@


.root {
-jr-row-odd-background: -fx-control-inner-background-alt;
-jr-row-even-background: -fx-control-inner-background;
Expand Down Expand Up @@ -256,7 +255,7 @@
}

.unchanged {
-rtfx-background-color:#0000;
-rtfx-background-color: #0000;
}

.updated {
Expand All @@ -280,18 +279,32 @@
* The base css file defining the style that is valid for every pane and dialog.
*/

TextFlow > * {
-fx-fill: -fx-text-background-color;
}

TextFlow > .hyperlink,
.hyperlink {
-fx-padding: 0;
-fx-underline: false;
-fx-border-style: null;
-fx-border-color: null;
-fx-text-fill: -jr-theme;
-fx-fill: -jr-theme;
}

TextFlow > .hyperlink:visited,
.hyperlink:visited {
-fx-text-fill: -jr-accent;
-fx-fill: -jr-accent;
}

.TextFlow > .hyperlink:hover,
.hyperlink:hover {
-fx-underline: true;
}


.glyph-icon {
/* This adjusts text alignment within the bounds of text nodes so that
the text is always vertically centered within the bounds. Based on
Expand Down Expand Up @@ -1331,10 +1344,6 @@ We want to have a look that matches our icons in the tool-bar */
-fx-label-padding: 5 0 10 10;
}

TextFlow * {
-fx-fill: -fx-text-background-color;
}

.chips-pane > .editor {
-fx-pref-height: 30px;
-fx-padding: 0px 0px 0px -8px;
Expand Down
67 changes: 67 additions & 0 deletions src/main/java/org/jabref/gui/backup/BackupResolverDialog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.jabref.gui.backup;

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

import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Hyperlink;

import org.jabref.gui.FXDialog;
import org.jabref.gui.Globals;
import org.jabref.gui.desktop.JabRefDesktop;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.BackupFileType;
import org.jabref.logic.util.io.BackupFileUtil;

import org.controlsfx.control.HyperlinkLabel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BackupResolverDialog extends FXDialog {
public static final ButtonType RESTORE_FROM_BACKUP = new ButtonType(Localization.lang("Restore from backup"), ButtonBar.ButtonData.OK_DONE);
public static final ButtonType REVIEW_BACKUP = new ButtonType(Localization.lang("Review backup"), ButtonBar.ButtonData.LEFT);
public static final ButtonType IGNORE_BACKUP = new ButtonType(Localization.lang("Ignore backup"), ButtonBar.ButtonData.CANCEL_CLOSE);

private static final Logger LOGGER = LoggerFactory.getLogger(BackupResolverDialog.class);

public BackupResolverDialog(Path originalPath) {
super(AlertType.CONFIRMATION, Localization.lang("Backup found"), true);
setHeaderText(null);
getDialogPane().setMinHeight(180);
getDialogPane().getButtonTypes().setAll(RESTORE_FROM_BACKUP, REVIEW_BACKUP, IGNORE_BACKUP);

Optional<Path> backupPathOpt = BackupFileUtil.getPathOfLatestExisingBackupFile(originalPath, BackupFileType.BACKUP);
String backupFilename = backupPathOpt.map(Path::getFileName).map(Path::toString).orElse(Localization.lang("File not found"));
String content = new StringBuilder()
.append(Localization.lang("A backup file for '%0' was found at [%1]",
originalPath.getFileName().toString(),
backupFilename))
.append("\n")
.append(Localization.lang("This could indicate that JabRef did not shut down cleanly last time the file was used."))
.append("\n\n")
.append(Localization.lang("Do you want to recover the library from the backup file?"))
.toString();
setContentText(content);

HyperlinkLabel contentLabel = new HyperlinkLabel(content);
contentLabel.setPrefWidth(360);
contentLabel.setOnAction((e) -> {
if (backupPathOpt.isPresent()) {
if (!(e.getSource() instanceof Hyperlink)) {
return;
}
String clickedLinkText = ((Hyperlink) (e.getSource())).getText();
if (backupFilename.equals(clickedLinkText)) {
try {
JabRefDesktop.openFolderAndSelectFile(backupPathOpt.get(), Globals.prefs, null);
} catch (IOException ex) {
LOGGER.error("Could not open backup folder", ex);
}
}
}
});
getDialogPane().setContent(contentLabel);
}
}
64 changes: 5 additions & 59 deletions src/main/java/org/jabref/gui/collab/ChangeScanner.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,10 @@
package org.jabref.gui.collab;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.jabref.gui.DialogService;
import org.jabref.gui.collab.entryadd.EntryAdd;
import org.jabref.gui.collab.entrychange.EntryChange;
import org.jabref.gui.collab.entrydelete.EntryDelete;
import org.jabref.gui.collab.groupchange.GroupChange;
import org.jabref.gui.collab.metedatachange.MetadataChange;
import org.jabref.gui.collab.preamblechange.PreambleChange;
import org.jabref.gui.collab.stringadd.BibTexStringAdd;
import org.jabref.gui.collab.stringchange.BibTexStringChange;
import org.jabref.gui.collab.stringdelete.BibTexStringDelete;
import org.jabref.gui.collab.stringrename.BibTexStringRename;
import org.jabref.logic.bibtex.comparator.BibDatabaseDiff;
import org.jabref.logic.bibtex.comparator.BibEntryDiff;
import org.jabref.logic.bibtex.comparator.BibStringDiff;
import org.jabref.logic.importer.ImportFormatPreferences;
import org.jabref.logic.importer.OpenDatabase;
import org.jabref.logic.importer.ParserResult;
Expand All @@ -34,73 +20,33 @@ public class ChangeScanner {
private static final Logger LOGGER = LoggerFactory.getLogger(ChangeScanner.class);
private final BibDatabaseContext database;
private final PreferencesService preferencesService;
private final ExternalChangeResolverFactory externalChangeResolverFactory;

private final DatabaseChangeResolverFactory databaseChangeResolverFactory;

public ChangeScanner(BibDatabaseContext database,
DialogService dialogService,
PreferencesService preferencesService) {
this.database = database;
this.preferencesService = preferencesService;
this.externalChangeResolverFactory = new ExternalChangeResolverFactory(dialogService, database, preferencesService);
this.databaseChangeResolverFactory = new DatabaseChangeResolverFactory(dialogService, database, preferencesService);
}

public List<ExternalChange> scanForChanges() {
public List<DatabaseChange> scanForChanges() {
if (database.getDatabasePath().isEmpty()) {
return Collections.emptyList();
}

try {
List<ExternalChange> changes = new ArrayList<>();

// Parse the modified file
// Important: apply all post-load actions
ImportFormatPreferences importFormatPreferences = preferencesService.getImportFormatPreferences();
ParserResult result = OpenDatabase.loadDatabase(database.getDatabasePath().get(), importFormatPreferences, new DummyFileUpdateMonitor());
BibDatabaseContext databaseOnDisk = result.getDatabaseContext();

// Start looking at changes.
BibDatabaseDiff differences = BibDatabaseDiff.compare(database, databaseOnDisk);
differences.getMetaDataDifferences().ifPresent(diff -> {
changes.add(new MetadataChange(diff, database, externalChangeResolverFactory));
diff.getGroupDifferences().ifPresent(groupDiff -> changes.add(new GroupChange(
groupDiff, database, externalChangeResolverFactory
)));
});
differences.getPreambleDifferences().ifPresent(diff -> changes.add(new PreambleChange(diff, database, externalChangeResolverFactory)));
differences.getBibStringDifferences().forEach(diff -> changes.add(createBibStringDiff(diff)));
differences.getEntryDifferences().forEach(diff -> changes.add(createBibEntryDiff(diff)));
return changes;
return DatabaseChangeList.compareAndGetChanges(database, databaseOnDisk, databaseChangeResolverFactory);
} catch (IOException e) {
LOGGER.warn("Error while parsing changed file.", e);
return Collections.emptyList();
}
}

private ExternalChange createBibStringDiff(BibStringDiff diff) {
if (diff.getOriginalString() == null) {
return new BibTexStringAdd(diff.getNewString(), database, externalChangeResolverFactory);
}

if (diff.getNewString() == null) {
return new BibTexStringDelete(diff.getOriginalString(), database, externalChangeResolverFactory);
}

if (diff.getOriginalString().getName().equals(diff.getNewString().getName())) {
return new BibTexStringChange(diff.getOriginalString(), diff.getNewString(), database, externalChangeResolverFactory);
}

return new BibTexStringRename(diff.getOriginalString(), diff.getNewString(), database, externalChangeResolverFactory);
}

private ExternalChange createBibEntryDiff(BibEntryDiff diff) {
if (diff.getOriginalEntry() == null) {
return new EntryAdd(diff.getNewEntry(), database, externalChangeResolverFactory);
}

if (diff.getNewEntry() == null) {
return new EntryDelete(diff.getOriginalEntry(), database, externalChangeResolverFactory);
}

return new EntryChange(diff.getOriginalEntry(), diff.getNewEntry(), database, externalChangeResolverFactory);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@
import org.jabref.gui.util.OptionalObjectProperty;
import org.jabref.model.database.BibDatabaseContext;

public sealed abstract class ExternalChange permits EntryAdd, EntryChange, EntryDelete, GroupChange, MetadataChange, PreambleChange, BibTexStringAdd, BibTexStringChange, BibTexStringDelete, BibTexStringRename {
public sealed abstract class DatabaseChange permits EntryAdd, EntryChange, EntryDelete, GroupChange, MetadataChange, PreambleChange, BibTexStringAdd, BibTexStringChange, BibTexStringDelete, BibTexStringRename {
protected final BibDatabaseContext databaseContext;
protected final OptionalObjectProperty<ExternalChangeResolver> externalChangeResolver = OptionalObjectProperty.empty();
protected final OptionalObjectProperty<DatabaseChangeResolver> externalChangeResolver = OptionalObjectProperty.empty();
private final BooleanProperty accepted = new SimpleBooleanProperty();
private final StringProperty name = new SimpleStringProperty();

protected ExternalChange(BibDatabaseContext databaseContext, ExternalChangeResolverFactory externalChangeResolverFactory) {
protected DatabaseChange(BibDatabaseContext databaseContext, DatabaseChangeResolverFactory databaseChangeResolverFactory) {
this.databaseContext = databaseContext;
setChangeName("Unnamed Change!");

if (externalChangeResolverFactory != null) {
externalChangeResolver.set(externalChangeResolverFactory.create(this));
if (databaseChangeResolverFactory != null) {
externalChangeResolver.set(databaseChangeResolverFactory.create(this));
}
}

Expand Down Expand Up @@ -63,7 +63,7 @@ protected void setChangeName(String changeName) {
name.set(changeName);
}

public Optional<ExternalChangeResolver> getExternalChangeResolver() {
public Optional<DatabaseChangeResolver> getExternalChangeResolver() {
return externalChangeResolver.get();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
import org.jabref.gui.collab.stringdelete.BibTexStringDeleteDetailsView;
import org.jabref.gui.collab.stringrename.BibTexStringRenameDetailsView;

public sealed abstract class ExternalChangeDetailsView extends AnchorPane permits EntryAddDetailsView, EntryChangeDetailsView, EntryDeleteDetailsView, GroupChangeDetailsView, MetadataChangeDetailsView, PreambleChangeDetailsView, BibTexStringAddDetailsView, BibTexStringChangeDetailsView, BibTexStringDeleteDetailsView, BibTexStringRenameDetailsView {
public sealed abstract class DatabaseChangeDetailsView extends AnchorPane permits EntryAddDetailsView, EntryChangeDetailsView, EntryDeleteDetailsView, GroupChangeDetailsView, MetadataChangeDetailsView, PreambleChangeDetailsView, BibTexStringAddDetailsView, BibTexStringChangeDetailsView, BibTexStringDeleteDetailsView, BibTexStringRenameDetailsView {
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,44 +26,44 @@
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.preferences.PreferencesService;

public class ExternalChangeDetailsViewFactory {
public class DatabaseChangeDetailsViewFactory {
private final BibDatabaseContext databaseContext;
private final DialogService dialogService;
private final StateManager stateManager;
private final ThemeManager themeManager;
private final PreferencesService preferencesService;

public ExternalChangeDetailsViewFactory(BibDatabaseContext databaseContext, DialogService dialogService, StateManager stateManager, ThemeManager themeManager, PreferencesService preferencesService) {
public DatabaseChangeDetailsViewFactory(BibDatabaseContext databaseContext, DialogService dialogService, StateManager stateManager, ThemeManager themeManager, PreferencesService preferencesService) {
this.databaseContext = databaseContext;
this.dialogService = dialogService;
this.stateManager = stateManager;
this.themeManager = themeManager;
this.preferencesService = preferencesService;
}

public ExternalChangeDetailsView create(ExternalChange externalChange) {
public DatabaseChangeDetailsView create(DatabaseChange databaseChange) {
// TODO: Use Pattern Matching for switch once it's out of preview
if (externalChange instanceof EntryChange entryChange) {
if (databaseChange instanceof EntryChange entryChange) {
return new EntryChangeDetailsView(entryChange, databaseContext, dialogService, stateManager, themeManager, preferencesService);
} else if (externalChange instanceof EntryAdd entryAdd) {
} else if (databaseChange instanceof EntryAdd entryAdd) {
return new EntryAddDetailsView(entryAdd, databaseContext, dialogService, stateManager, themeManager, preferencesService);
} else if (externalChange instanceof EntryDelete entryDelete) {
} else if (databaseChange instanceof EntryDelete entryDelete) {
return new EntryDeleteDetailsView(entryDelete, databaseContext, dialogService, stateManager, themeManager, preferencesService);
} else if (externalChange instanceof BibTexStringAdd stringAdd) {
} else if (databaseChange instanceof BibTexStringAdd stringAdd) {
return new BibTexStringAddDetailsView(stringAdd);
} else if (externalChange instanceof BibTexStringDelete stringDelete) {
} else if (databaseChange instanceof BibTexStringDelete stringDelete) {
return new BibTexStringDeleteDetailsView(stringDelete);
} else if (externalChange instanceof BibTexStringChange stringChange) {
} else if (databaseChange instanceof BibTexStringChange stringChange) {
return new BibTexStringChangeDetailsView(stringChange);
} else if (externalChange instanceof BibTexStringRename stringRename) {
} else if (databaseChange instanceof BibTexStringRename stringRename) {
return new BibTexStringRenameDetailsView(stringRename);
} else if (externalChange instanceof MetadataChange metadataChange) {
} else if (databaseChange instanceof MetadataChange metadataChange) {
return new MetadataChangeDetailsView(metadataChange, preferencesService);
} else if (externalChange instanceof GroupChange groupChange) {
} else if (databaseChange instanceof GroupChange groupChange) {
return new GroupChangeDetailsView(groupChange);
} else if (externalChange instanceof PreambleChange preambleChange) {
} else if (databaseChange instanceof PreambleChange preambleChange) {
return new PreambleChangeDetailsView(preambleChange);
}
throw new UnsupportedOperationException("Cannot preview the given change: " + externalChange.getName());
throw new UnsupportedOperationException("Cannot preview the given change: " + databaseChange.getName());
}
}

0 comments on commit e86839a

Please sign in to comment.