From 094252cbb36bbc79fba808a8990b33b258dfe04c Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Wed, 23 Oct 2024 20:17:52 +0200 Subject: [PATCH 01/84] l'ajout de la classe BackUpManagerJGit --- build.gradle | 2 +- .../autosaveandbackup/BackUpManagerJGit.java | 157 ++++++++++++++++++ .../gui/autosaveandbackup/BackupManager.java | 19 ++- 3 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java diff --git a/build.gradle b/build.gradle index 0c7558981c8..e6d69d6976e 100644 --- a/build.gradle +++ b/build.gradle @@ -153,7 +153,7 @@ jacoco { dependencies { // Include all jar-files in the 'lib' folder as dependencies implementation fileTree(dir: 'lib', includes: ['*.jar']) - + implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r' def pdfbox = "3.0.3" implementation ("org.apache.pdfbox:pdfbox:$pdfbox") { exclude group: 'commons-logging' diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java new file mode 100644 index 00000000000..13cf7b03628 --- /dev/null +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java @@ -0,0 +1,157 @@ +package org.jabref.gui.autosaveandbackup; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +import org.jabref.gui.LibraryTab; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.util.CoarseChangeFilter; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntryTypesManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BackUpManagerJGit { + + private static final Logger LOGGER = LoggerFactory.getLogger(BackupManager.class); + + + private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; + + private static Set runningInstances = new HashSet<>(); + + private final BibDatabaseContext bibDatabaseContext; + private final CliPreferences preferences; + private final ScheduledThreadPoolExecutor executor; + private final CoarseChangeFilter changeFilter; + private final BibEntryTypesManager entryTypesManager; + private final LibraryTab libraryTab; + + // Contains a list of all backup paths + // During writing, the less recent backup file is deleted + //private final Queue backupFilesQueue = new LinkedBlockingQueue<>(); + private boolean needsBackup = false; + + public BackUpManagerJGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { + this.bibDatabaseContext = bibDatabaseContext; + this.entryTypesManager = entryTypesManager; + this.preferences = preferences; + this.executor = new ScheduledThreadPoolExecutor(2); + this.libraryTab = libraryTab; + + changeFilter = new CoarseChangeFilter(bibDatabaseContext); + changeFilter.registerListener(this); + } + /** + * Starts the BackupManager which is associated with the given {@link BibDatabaseContext}. As long as no database + * file is present in {@link BibDatabaseContext}, the {@link BackupManager} will do nothing. + * + * This method is not thread-safe. The caller has to ensure that this method is not called in parallel. + * + * @param bibDatabaseContext Associated {@link BibDatabaseContext} + */ + + public static BackUpManagerJGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { + BackUpManagerJGit backupManagerJGit = new BackUpManagerJGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + backupManagerJGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory()); + runningInstances.add(backupManagerJGit); + return backupManagerJGit; + } + /** + * Shuts down the BackupManager which is associated with the given {@link BibDatabaseContext}. + * + * @param bibDatabaseContext Associated {@link BibDatabaseContext} + * @param createBackup True, if a backup should be created + * @param backupDir The path to the backup directory + */ + public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdown(backupDir, createBackup)); + runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); + } + /** + * Checks whether a backup file exists for the given database file. If it exists, it is checked whether it is + * newer and different from the original. + * + * In case a discarded file is present, the method also returns false, See also {@link #discardBackup(Path)}. + * + * @param originalPath Path to the file a backup should be checked for. Example: jabref.bib. + * + * @return true if backup file exists AND differs from originalPath. false is the + * "default" return value in the good case. In case a discarded file exists, false is returned, too. + * In the case of an exception true is returned to ensure that the user checks the output. + */ + public static boolean backupGitDiffers(Path originalPath, Path backupDir) { + //à implementer + Path discardedFile = determineDiscardedFile(originalPath, backupDir); + if (Files.exists(discardedFile)) { + try { + Files.delete(discardedFile); + } catch ( + IOException e) { + LOGGER.error("Could not remove discarded file {}", discardedFile, e); + return true; + } + return false; + } + return getLatestBackupPath(originalPath, backupDir).map(latestBackupPath -> { + FileTime latestBackupFileLastModifiedTime; + try { + latestBackupFileLastModifiedTime = Files.getLastModifiedTime(latestBackupPath); + } catch (IOException e) { + LOGGER.debug("Could not get timestamp of backup file {}", latestBackupPath, e); + // If we cannot get the timestamp, we do show any warning + return false; + } + FileTime currentFileLastModifiedTime; + try { + currentFileLastModifiedTime = Files.getLastModifiedTime(originalPath); + } catch (IOException e) { + LOGGER.debug("Could not get timestamp of current file file {}", originalPath, e); + // If we cannot get the timestamp, we do show any warning + return false; + } + if (latestBackupFileLastModifiedTime.compareTo(currentFileLastModifiedTime) <= 0) { + // Backup is older than current file + // We treat the backup as non-different (even if it could differ) + return false; + } + try { + boolean result = Files.mismatch(originalPath, latestBackupPath) != -1L; + if (result) { + LOGGER.info("Backup file {} differs from current file {}", latestBackupPath, originalPath); + } + return result; + } catch (IOException e) { + LOGGER.debug("Could not compare original file and backup file.", e); + // User has to investigate in this case + return true; + } + }).orElse(false); + } + /** + * Restores the backup file by copying and overwriting the original one. + * + * @param originalPath Path to the file which should be equalized to the backup file. + */ + public static void restoreBackup(Path originalPath, Path backupDir) { + Optional backupPath = getLatestBackupPath(originalPath, backupDir); + if (backupPath.isEmpty()) { + LOGGER.error("There is no backup file"); + return; + } + try { + Files.copy(backupPath.get(), originalPath, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + LOGGER.error("Error while restoring the backup file.", e); + } + } + +} diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java index acae02c01c8..849ecbccac7 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java @@ -236,7 +236,9 @@ void performBackup(Path backupPath) { LOGGER.error("Could not delete backup file {}", oldestBackupFile, e); } } - + //l'ordre dans lequel les entrées BibTeX doivent être écrites dans le fichier de sauvegarde. + // Si l'utilisateur a trié la table d'affichage des entrées dans JabRef, cet ordre est récupéré. + // Sinon, un ordre par défaut est utilisé. // code similar to org.jabref.gui.exporter.SaveDatabaseAction.saveDatabase SelfContainedSaveOrder saveOrder = bibDatabaseContext .getMetaData().getSaveOrder() @@ -256,6 +258,10 @@ void performBackup(Path backupPath) { } }) .orElse(SaveOrder.getDefaultSaveOrder()); + + //Elle configure la sauvegarde, en indiquant qu'aucune sauvegarde supplémentaire (backup) ne doit être créée, + // que l'ordre de sauvegarde doit être celui défini, et que les entrées doivent être formatées selon les préférences + // utilisateur. SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() .withMakeBackup(false) .withSaveOrder(saveOrder) @@ -263,13 +269,15 @@ void performBackup(Path backupPath) { // "Clone" the database context // We "know" that "only" the BibEntries might be changed during writing (see [org.jabref.logic.exporter.BibDatabaseWriter.savePartOfDatabase]) + //Chaque entrée BibTeX (comme un article, livre, etc.) est clonée en utilisant la méthode clone(). + // Cela garantit que les modifications faites pendant la sauvegarde n'affecteront pas l'entrée originale. List list = bibDatabaseContext.getDatabase().getEntries().stream() .map(BibEntry::clone) .map(BibEntry.class::cast) .toList(); BibDatabase bibDatabaseClone = new BibDatabase(list); BibDatabaseContext bibDatabaseContextClone = new BibDatabaseContext(bibDatabaseClone, bibDatabaseContext.getMetaData()); - + //Elle définit l'encodage à utiliser pour écrire le fichier. Cela garantit que les caractères spéciaux sont bien sauvegardés. Charset encoding = bibDatabaseContext.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8); // We want to have successful backups only // Thus, we do not use a plain "FileWriter", but the "AtomicFileWriter" @@ -335,7 +343,7 @@ public synchronized void listen(@SuppressWarnings("unused") BibDatabaseContextCh } private void startBackupTask(Path backupDir) { - fillQueue(backupDir); + fillQueue(backupDir);//remplie backupFilesQueue les files .sav de le meme bibl executor.scheduleAtFixedRate( // We need to determine the backup path on each action, because we use the timestamp in the filename @@ -344,7 +352,8 @@ private void startBackupTask(Path backupDir) { DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, TimeUnit.SECONDS); } - +//La méthode fillQueue(backupDir) est définie dans le code et son rôle est de lister et d'ajouter +// les fichiers de sauvegarde existants dans une file d'attente, private void fillQueue(Path backupDir) { if (!Files.exists(backupDir)) { return; @@ -355,7 +364,7 @@ private void fillQueue(Path backupDir) { try { List allSavFiles = Files.list(backupDir) // just list the .sav belonging to the given targetFile - .filter(p -> p.getFileName().toString().startsWith(prefix)) + .filter(p -> p.getFileName().toString().startsWith(prefix))//tous les files .sav commencerait par ce prefix .sorted().toList(); backupFilesQueue.addAll(allSavFiles); } catch (IOException e) { From d0f6346a0075c515c6547ee2ebc31bb93278d538 Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Wed, 23 Oct 2024 20:20:12 +0200 Subject: [PATCH 02/84] l'ajout de la classe BackUpManagerJGit --- .../gui/autosaveandbackup/BackUpManagerJGit.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java index 13cf7b03628..8ff36227d25 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java @@ -90,16 +90,7 @@ public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDi */ public static boolean backupGitDiffers(Path originalPath, Path backupDir) { //à implementer - Path discardedFile = determineDiscardedFile(originalPath, backupDir); - if (Files.exists(discardedFile)) { - try { - Files.delete(discardedFile); - } catch ( - IOException e) { - LOGGER.error("Could not remove discarded file {}", discardedFile, e); - return true; - } - return false; + } return getLatestBackupPath(originalPath, backupDir).map(latestBackupPath -> { FileTime latestBackupFileLastModifiedTime; From 9ab37a2e9af9f9750d5793427b8c985b68180477 Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Wed, 23 Oct 2024 20:29:39 +0200 Subject: [PATCH 03/84] l'ajout de la classe BackUpManagerJGit --- .../gui/autosaveandbackup/BackUpManagerJGit.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java index 8ff36227d25..90d99ef2894 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java @@ -133,16 +133,9 @@ public static boolean backupGitDiffers(Path originalPath, Path backupDir) { * @param originalPath Path to the file which should be equalized to the backup file. */ public static void restoreBackup(Path originalPath, Path backupDir) { - Optional backupPath = getLatestBackupPath(originalPath, backupDir); - if (backupPath.isEmpty()) { - LOGGER.error("There is no backup file"); - return; - } - try { - Files.copy(backupPath.get(), originalPath, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - LOGGER.error("Error while restoring the backup file.", e); - } + /** + * à implementer + * */ } } From d8812efe196f53269189c3c6088aa92923fd2e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 6 Nov 2024 10:14:56 +0100 Subject: [PATCH 04/84] Add the class JGIT --- .../org/jabref/gui/autosaveandbackup/BackupManagerJGit.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java new file mode 100644 index 00000000000..19a82b2b8b2 --- /dev/null +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -0,0 +1,4 @@ +package org.jabref.gui.autosaveandbackup; + +public class BackupManagerJGit { +} From af50c7c70d7ee65bf4d1ad271a186078c7be5eb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 6 Nov 2024 10:39:13 +0100 Subject: [PATCH 05/84] first git implementation, not tested --- .../autosaveandbackup/BackupManagerJGit.java | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java index 19a82b2b8b2..ce4b032645a 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -1,4 +1,129 @@ package org.jabref.gui.autosaveandbackup; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.jabref.gui.LibraryTab; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.util.CoarseChangeFilter; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntryTypesManager; + public class BackupManagerJGit { + + private static final Logger LOGGER = LoggerFactory.getLogger(BackupManager.class); + + private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; + + private static Set runningInstances = new HashSet(); + + private final BibDatabaseContext bibDatabaseContext; + private final CliPreferences preferences; + private final ScheduledThreadPoolExecutor executor; + private final CoarseChangeFilter changeFilter; + private final BibEntryTypesManager entryTypesManager; + private final LibraryTab libraryTab; + private final Git git; + + private boolean needsBackup = false; + + BackupManagerJGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { + this.bibDatabaseContext = bibDatabaseContext; + this.entryTypesManager = entryTypesManager; + this.preferences = preferences; + this.executor = new ScheduledThreadPoolExecutor(2); + this.libraryTab = libraryTab; + + changeFilter = new CoarseChangeFilter(bibDatabaseContext); + changeFilter.registerListener(this); + + // Initialize Git repository + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + git = new Git(builder.setGitDir(new File(preferences.getFilePreferences().getBackupDirectory().toFile(), ".git")) + .readEnvironment() + .findGitDir() + .build()); + if (git.getRepository().getObjectDatabase().exists()) { + LOGGER.info("Git repository already exists"); + } else { + git.init().call(); + LOGGER.info("Initialized new Git repository"); + } + } + + public static BackupManagerJGit startJGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { + BackupManagerJGit backupManagerJGit = new BackupManagerJGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + backupManagerJGit.startBackupTaskJGit(preferences.getFilePreferences().getBackupDirectory()); + runningInstances.add(backupManagerJGit); + return backupManagerJGit; + } + + public static void shutdownJGit(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup)); + runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); + } + + private void startBackupTaskJGit(Path backupDir) { + executor.scheduleAtFixedRate( + () -> { + try { + performBackup(backupDir); + } catch (IOException | GitAPIException e) { + LOGGER.error("Error during backup", e); + } + }, + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, + TimeUnit.SECONDS); + } + + private void performBackup(Path backupDir) throws IOException, GitAPIException { + if (!needsBackup) { + return; + } + + // Add and commit changes + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("Backup at " + System.currentTimeMillis()).call(); + LOGGER.info("Committed backup: {}", commit.getId()); + + // Reset the backup flag + this.needsBackup = false; + } + + public static void restoreBackup(Path originalPath, Path backupDir) { + try { + Git git = Git.open(backupDir.toFile()); + git.checkout().setName("HEAD").call(); + LOGGER.info("Restored backup from Git repository"); + } catch (IOException | GitAPIException e) { + LOGGER.error("Error while restoring the backup", e); + } + } + + private void shutdownJGit(Path backupDir, boolean createBackup) { + changeFilter.unregisterListener(this); + changeFilter.shutdown(); + executor.shutdown(); + + if (createBackup) { + try { + performBackup(backupDir); + } catch (IOException | GitAPIException e) { + LOGGER.error("Error during shutdown backup", e); + } + } + } } From e72d40803790b98650cf3179b2cf9a49258dbea0 Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Wed, 6 Nov 2024 14:12:10 +0100 Subject: [PATCH 06/84] ajout de la methide backupGitDiffers --- buildres/abbrv.jabref.org | 2 +- jabref | 1 + .../autosaveandbackup/BackupManagerJGit.java | 50 ++++++++++++++++--- src/main/resources/csl-locales | 2 +- src/main/resources/csl-styles | 2 +- 5 files changed, 46 insertions(+), 11 deletions(-) create mode 160000 jabref diff --git a/buildres/abbrv.jabref.org b/buildres/abbrv.jabref.org index 0fdf99147a8..d87037495de 160000 --- a/buildres/abbrv.jabref.org +++ b/buildres/abbrv.jabref.org @@ -1 +1 @@ -Subproject commit 0fdf99147a8a5fc8ae7ccd79ad4e0029e736e4a3 +Subproject commit d87037495de7213b896dbb6a20170387de170709 diff --git a/jabref b/jabref new file mode 160000 index 00000000000..4705977685c --- /dev/null +++ b/jabref @@ -0,0 +1 @@ +Subproject commit 4705977685c6b0551a8d40458abced473501d245 diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java index ce4b032645a..e2d8d3e047d 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -1,16 +1,10 @@ package org.jabref.gui.autosaveandbackup; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -21,9 +15,20 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class BackupManagerJGit { - private static final Logger LOGGER = LoggerFactory.getLogger(BackupManager.class); + private static final Logger LOGGER = LoggerFactory.getLogger(BackupManagerJGit.class); private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; @@ -90,6 +95,10 @@ private void startBackupTaskJGit(Path backupDir) { } private void performBackup(Path backupDir) throws IOException, GitAPIException { + /* + + il faut initialiser needsBackup + */ if (!needsBackup) { return; } @@ -113,6 +122,31 @@ public static void restoreBackup(Path originalPath, Path backupDir) { } } + /* + compare what is in originalPath and last commit + */ + + public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws IOException, GitAPIException { + + File repoDir = backupDir.toFile(); + Repository repository = new FileRepositoryBuilder() + .setGitDir(new File(repoDir, ".git")) + .build(); + try (Git git = new Git(repository)) { + ObjectId headCommitId = repository.resolve("HEAD"); // to get the latest commit id + if (headCommitId == null) { + // No commits in the repository, so there's no previous backup + return true; + } + git.add().addFilepattern(originalPath.getFileName().toString()).call(); + String relativePath = backupDir.relativize(originalPath).toString(); + List diffs = git.diff() + .setPathFilter(PathFilter.create(relativePath)) // Utiliser PathFilter ici + .call(); + return !diffs.isEmpty(); + } + } + private void shutdownJGit(Path backupDir, boolean createBackup) { changeFilter.unregisterListener(this); changeFilter.shutdown(); diff --git a/src/main/resources/csl-locales b/src/main/resources/csl-locales index 4753e3a9aca..8bc2af16f51 160000 --- a/src/main/resources/csl-locales +++ b/src/main/resources/csl-locales @@ -1 +1 @@ -Subproject commit 4753e3a9aca4b806ac0e3036ed727d47bf8f678e +Subproject commit 8bc2af16f5180a8e4fb591c2be916650f75bb8f6 diff --git a/src/main/resources/csl-styles b/src/main/resources/csl-styles index 49af15c4f5b..b413a778b81 160000 --- a/src/main/resources/csl-styles +++ b/src/main/resources/csl-styles @@ -1 +1 @@ -Subproject commit 49af15c4f5bca025b6b18ca48c447016586f01e7 +Subproject commit b413a778b8170cf5aebbb9aeffec62cfd068e19e From d427e058178240b5520bdf5d7f20a00f91d8843f Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Wed, 6 Nov 2024 14:26:07 +0100 Subject: [PATCH 07/84] Added Pop up to choose backup --- .../jabref/gui/backup/BackupChoiceDialog.java | 94 +++++++++++++++++++ .../org/jabref/gui/backup/BackupEntry.java | 42 +++++++++ .../gui/backup/BackupResolverDialog.java | 7 +- .../jabref/gui/dialogs/BackupUIManager.java | 17 +++- .../jabref/logic/bst/BstVMVisitorTest.java | 8 +- .../importer/fileformat/CffImporterTest.java | 8 +- .../io/CitationKeyBasedFileFinderTest.java | 4 +- 7 files changed, 166 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java create mode 100644 src/main/java/org/jabref/gui/backup/BackupEntry.java diff --git a/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java b/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java new file mode 100644 index 00000000000..bf0b729ba6d --- /dev/null +++ b/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java @@ -0,0 +1,94 @@ +package org.jabref.gui.backup; + +import java.nio.file.Path; +import java.util.Optional; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.layout.VBox; + +import org.jabref.gui.FXDialog; +import org.jabref.gui.collab.DatabaseChange; +import org.jabref.gui.frame.ExternalApplicationsPreferences; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.BackupFileType; +import org.jabref.logic.util.io.BackupFileUtil; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BackupChoiceDialog extends FXDialog { + public static final ButtonType RESTORE_BACKUP = new ButtonType(Localization.lang("Restore from backup"), ButtonBar.ButtonData.OK_DONE); + 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); + + @FXML + private TableView backupTableView; + + public BackupChoiceDialog(Path originalPath, Path backupDir, ExternalApplicationsPreferences externalApplicationsPreferences) { + super(AlertType.CONFIRMATION, Localization.lang("Choose backup"), true); + setHeaderText(null); + getDialogPane().setMinHeight(180); + getDialogPane().getButtonTypes().setAll(RESTORE_BACKUP, IGNORE_BACKUP); + + Optional backupPathOpt = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); + String backupFilename = backupPathOpt.map(Path::getFileName).map(Path::toString).orElse(Localization.lang("File not found")); + String content = Localization.lang("The :") + "\n" + + Localization.lang("Here are some backup versions you can revert to"); + + // Create a TableView for backups + TableView backupTableView = new TableView<>(); + // Define columns + TableColumn dateColumn = new TableColumn<>("Date of Backup"); + dateColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty()); + + TableColumn sizeColumn = new TableColumn<>("Size of Backup"); + sizeColumn.setCellValueFactory(cellData -> cellData.getValue().sizeProperty()); + + TableColumn entriesColumn = new TableColumn<>("Number of Entries"); + entriesColumn.setCellValueFactory(cellData -> cellData.getValue().entriesProperty().asObject()); + + // Add columns to the table + backupTableView.getColumns().addAll(dateColumn, sizeColumn, entriesColumn); + + // Sample data + ObservableList data = FXCollections.observableArrayList( + new BackupEntry("2023-11-01", "500 MB", 120), + new BackupEntry("2023-10-15", "300 MB", 80), + new BackupEntry("2023-10-01", "250 MB", 60), + new BackupEntry("2023-11-01", "500 MB", 120), + new BackupEntry("2023-10-15", "300 MB", 80), + new BackupEntry("2023-11-01", "500 MB", 120), + new BackupEntry("2023-10-15", "300 MB", 80), + new BackupEntry("2023-11-01", "500 MB", 120), + new BackupEntry("2023-10-15", "300 MB", 80), + new BackupEntry("2023-11-01", "500 MB", 120), + new BackupEntry("2023-10-15", "300 MB", 80), + new BackupEntry("2023-11-01", "500 MB", 120), + new BackupEntry("2023-10-15", "300 MB", 80), + new BackupEntry("2023-11-01", "500 MB", 120), + new BackupEntry("2023-10-15", "300 MB", 80), + new BackupEntry("2023-11-01", "500 MB", 120), + new BackupEntry("2023-10-15", "300 MB", 80) + ); + + backupTableView.setItems(data); + + setContentText(content); + + // Create a VBox to hold the table and additional content + VBox contentBox = new VBox(10); + contentBox.getChildren().addAll(new Label(content), backupTableView); + + // Add the VBox to the dialog's content + getDialogPane().setContent(contentBox); + } +} + diff --git a/src/main/java/org/jabref/gui/backup/BackupEntry.java b/src/main/java/org/jabref/gui/backup/BackupEntry.java new file mode 100644 index 00000000000..9a6c3fe817c --- /dev/null +++ b/src/main/java/org/jabref/gui/backup/BackupEntry.java @@ -0,0 +1,42 @@ +package org.jabref.gui.backup; + +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +public class BackupEntry { + private final StringProperty date; + private final StringProperty size; + private final IntegerProperty entries; + + public BackupEntry(String date, String size, int entries) { + this.date = new SimpleStringProperty(date); + this.size = new SimpleStringProperty(size); + this.entries = new SimpleIntegerProperty(entries); + } + + public String getDate() { + return date.get(); + } + + public StringProperty dateProperty() { + return date; + } + + public String getSize() { + return size.get(); + } + + public StringProperty sizeProperty() { + return size; + } + + public int getEntries() { + return entries.get(); + } + + public IntegerProperty entriesProperty() { + return entries; + } +} diff --git a/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java b/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java index cf35336765d..c7aad7811ba 100644 --- a/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java +++ b/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java @@ -20,9 +20,10 @@ 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 RESTORE_FROM_BACKUP = new ButtonType(Localization.lang("Restore from latest backup"), ButtonBar.ButtonData.OK_DONE); + public static final ButtonType REVIEW_BACKUP = new ButtonType(Localization.lang("Review latest backup"), ButtonBar.ButtonData.LEFT); public static final ButtonType IGNORE_BACKUP = new ButtonType(Localization.lang("Ignore backup"), ButtonBar.ButtonData.CANCEL_CLOSE); + public static final ButtonType COMPARE_OLDER_BACKUP = new ButtonType("Compare older backup", ButtonBar.ButtonData.LEFT); private static final Logger LOGGER = LoggerFactory.getLogger(BackupResolverDialog.class); @@ -30,7 +31,7 @@ public BackupResolverDialog(Path originalPath, Path backupDir, ExternalApplicati super(AlertType.CONFIRMATION, Localization.lang("Backup found"), true); setHeaderText(null); getDialogPane().setMinHeight(180); - getDialogPane().getButtonTypes().setAll(RESTORE_FROM_BACKUP, REVIEW_BACKUP, IGNORE_BACKUP); + getDialogPane().getButtonTypes().setAll(RESTORE_FROM_BACKUP, REVIEW_BACKUP, IGNORE_BACKUP, COMPARE_OLDER_BACKUP); Optional backupPathOpt = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); String backupFilename = backupPathOpt.map(Path::getFileName).map(Path::toString).orElse(Localization.lang("File not found")); diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 08dd120ab78..b02561cd5ef 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -13,6 +13,7 @@ import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.autosaveandbackup.BackupManager; +import org.jabref.gui.backup.BackupChoiceDialog; import org.jabref.gui.backup.BackupResolverDialog; import org.jabref.gui.collab.DatabaseChange; import org.jabref.gui.collab.DatabaseChangeList; @@ -59,9 +60,15 @@ public static Optional showRestoreBackupDialog(DialogService dialo if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); return Optional.empty(); + } else if (action == BackupResolverDialog.COMPARE_OLDER_BACKUP) { + var actions = showBackupChoiceDialog( + dialogService, + preferences.getExternalApplicationsPreferences(), + originalPath, + preferences.getFilePreferences().getBackupDirectory()); } else if (action == BackupResolverDialog.REVIEW_BACKUP) { return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); - } + } return Optional.empty(); }); } @@ -74,6 +81,14 @@ private static Optional showBackupResolverDialog(DialogService dialo () -> dialogService.showCustomDialogAndWait(new BackupResolverDialog(originalPath, backupDir, externalApplicationsPreferences))); } + private static Optional showBackupChoiceDialog(DialogService dialogService, + ExternalApplicationsPreferences externalApplicationsPreferences, + Path originalPath, + Path backupDir) { + return UiTaskExecutor.runInJavaFXThread( + () -> dialogService.showCustomDialogAndWait(new BackupChoiceDialog(originalPath, backupDir, externalApplicationsPreferences))); + } + private static Optional showReviewBackupDialog( DialogService dialogService, Path originalPath, diff --git a/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java index c8c57f250db..23c0f7e0eed 100644 --- a/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java +++ b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java @@ -203,8 +203,8 @@ void visitIdentifier() { FUNCTION { test } { #1 'local.variable := #2 'variable := - "TEST" 'local.label := - "TEST-GLOBAL" 'label := + "COMPARE_OLDER_BACKUP" 'local.label := + "COMPARE_OLDER_BACKUP-GLOBAL" 'label := local.label local.variable label variable } @@ -215,9 +215,9 @@ void visitIdentifier() { vm.render(testEntries); assertEquals(2, vm.getStack().pop()); - assertEquals("TEST-GLOBAL", vm.getStack().pop()); + assertEquals("COMPARE_OLDER_BACKUP-GLOBAL", vm.getStack().pop()); assertEquals(1, vm.getStack().pop()); - assertEquals("TEST", vm.getStack().pop()); + assertEquals("COMPARE_OLDER_BACKUP", vm.getStack().pop()); assertEquals(0, vm.getStack().size()); } diff --git a/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java b/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java index 195d7bd713b..bca58a77401 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java @@ -175,7 +175,7 @@ void importEntriesPreferredCitation() throws IOException, URISyntaxException { BibEntry expectedPreferred = new BibEntry(StandardEntryType.InProceedings) .withCitationKey(citeKey) .withField(StandardField.AUTHOR, "Jonathan von Duke and Jim Kingston, Jr.") - .withField(StandardField.DOI, "10.0001/TEST") + .withField(StandardField.DOI, "10.0001/COMPARE_OLDER_BACKUP") .withField(StandardField.URL, "www.github.com"); assertEquals(mainEntry, expectedMain); @@ -198,13 +198,13 @@ void importEntriesReferences() throws IOException, URISyntaxException { .withCitationKey(citeKey1) .withField(StandardField.AUTHOR, "Jonathan von Duke and Jim Kingston, Jr.") .withField(StandardField.YEAR, "2007") - .withField(StandardField.DOI, "10.0001/TEST") + .withField(StandardField.DOI, "10.0001/COMPARE_OLDER_BACKUP") .withField(StandardField.URL, "www.example.com"); BibEntry expectedReference2 = new BibEntry(StandardEntryType.Manual) .withCitationKey(citeKey2) .withField(StandardField.AUTHOR, "Arthur Clark, Jr. and Luca von Diamond") - .withField(StandardField.DOI, "10.0002/TEST") + .withField(StandardField.DOI, "10.0002/COMPARE_OLDER_BACKUP") .withField(StandardField.URL, "www.facebook.com"); assertEquals(mainEntry, expectedMain); @@ -218,7 +218,7 @@ public BibEntry getPopulatedEntry() { .withField(StandardField.TITLE, "Test") .withField(StandardField.URL, "www.google.com") .withField(BiblatexSoftwareField.REPOSITORY, "www.github.com") - .withField(StandardField.DOI, "10.0000/TEST") + .withField(StandardField.DOI, "10.0000/COMPARE_OLDER_BACKUP") .withField(StandardField.DATE, "2000-07-02") .withField(StandardField.COMMENT, "Test entry.") .withField(StandardField.ABSTRACT, "Test abstract.") diff --git a/src/test/java/org/jabref/logic/util/io/CitationKeyBasedFileFinderTest.java b/src/test/java/org/jabref/logic/util/io/CitationKeyBasedFileFinderTest.java index 3f29211f4e0..7133acaa60e 100644 --- a/src/test/java/org/jabref/logic/util/io/CitationKeyBasedFileFinderTest.java +++ b/src/test/java/org/jabref/logic/util/io/CitationKeyBasedFileFinderTest.java @@ -47,8 +47,8 @@ void setUp(@TempDir Path temporaryFolder) throws IOException { Files.createFile(dir2003.resolve("Paper by HipKro03.pdf")); Path dirTest = Files.createDirectory(rootDir.resolve("test")); - Files.createFile(dirTest.resolve(".TEST")); - Files.createFile(dirTest.resolve("TEST[")); + Files.createFile(dirTest.resolve(".COMPARE_OLDER_BACKUP")); + Files.createFile(dirTest.resolve("COMPARE_OLDER_BACKUP[")); Files.createFile(dirTest.resolve("TE.ST")); Files.createFile(dirTest.resolve("foo.dat")); From 0737f485671f8247ab3f0e14ff305f910b47e515 Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Wed, 6 Nov 2024 16:06:41 +0100 Subject: [PATCH 08/84] adding methods 06/11 --- src/main/java/module-info.java | 1 + .../autosaveandbackup/BackupManagerJGit.java | 40 ++++++++++++++++++- .../jabref/gui/dialogs/BackupUIManager.java | 3 ++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index f0151b8988e..901beace0e9 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -193,5 +193,6 @@ requires mslinks; requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; + requires gradle.api; // endregion } diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java index e2d8d3e047d..d8fc87c3706 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -1,6 +1,8 @@ package org.jabref.gui.autosaveandbackup; import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; import java.util.HashSet; @@ -18,6 +20,7 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; @@ -42,6 +45,7 @@ public class BackupManagerJGit { private final LibraryTab libraryTab; private final Git git; + private boolean needsBackup = false; BackupManagerJGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { @@ -112,10 +116,11 @@ private void performBackup(Path backupDir) throws IOException, GitAPIException { this.needsBackup = false; } - public static void restoreBackup(Path originalPath, Path backupDir) { + public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { try { + Git git = Git.open(backupDir.toFile()); - git.checkout().setName("HEAD").call(); + git.checkout().setName(objectId.getName()).call(); LOGGER.info("Restored backup from Git repository"); } catch (IOException | GitAPIException e) { LOGGER.error("Error while restoring the backup", e); @@ -147,6 +152,37 @@ public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws } } + @SuppressWarnings("checkstyle:RegexpMultiline") + public void showDiffersJGit(Path originalPath, Path backupDir, String CommitId) throws IOException, GitAPIException { + + File repoDir = backupDir.toFile(); + Repository repository = new FileRepositoryBuilder() + .setGitDir(new File(repoDir, ".git")) + .build(); + /* + il faut une classe qui affiche les dix dernier backup avec les data: date/ size / number of entries + */ + + ObjectId oldCommit = repository.resolve(CommitId); + ObjectId newCommit = repository.resolve("HEAD"); + + FileOutputStream fos = new FileOutputStream(FileDescriptor.out); + DiffFormatter diffFr = new DiffFormatter(fos); + diffFr.setRepository(repository); + diffFr.scan(oldCommit, newCommit); + } + + + + /* + + faire une methode qui accepte commit id et retourne les diff differences avec la version actuelle + methode qui renvoie n derniers indice de commit + methode ayant idcommit retourne data + + */ + + private void shutdownJGit(Path backupDir, boolean createBackup) { changeFilter.unregisterListener(this); changeFilter.shutdown(); diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 08dd120ab78..b9f9e21d681 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -22,6 +22,7 @@ import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.util.UiTaskExecutor; +import org.eclipse.jgit.lib.ObjectId; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.ParserResult; @@ -32,6 +33,7 @@ import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; +import org.gradle.internal.impldep.org.eclipse.jgit.lib.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,6 +59,7 @@ public static Optional showRestoreBackupDialog(DialogService dialo preferences.getFilePreferences().getBackupDirectory()); return actionOpt.flatMap(action -> { if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { + BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); return Optional.empty(); } else if (action == BackupResolverDialog.REVIEW_BACKUP) { From f68236fc9e5cb0d1cfbd2a3d37380db518c8af3e Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Thu, 7 Nov 2024 17:41:30 +0100 Subject: [PATCH 09/84] methods aded and changed : retrieveCommitDetails , retreiveCommits --- .../autosaveandbackup/BackupManagerJGit.java | 111 +++++++++++++++++- 1 file changed, 105 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java index d8fc87c3706..6ec5a709637 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -5,6 +5,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -24,6 +26,7 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.slf4j.Logger; @@ -116,11 +119,34 @@ private void performBackup(Path backupDir) throws IOException, GitAPIException { this.needsBackup = false; } - public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { + public static void restoreBackupJGit(Path originalPath, Path backupDir, ObjectId objectId) { + try { + Git git = Git.open(backupDir.toFile()); + + // Extraire le contenu de l'objet spécifié (commit) dans le répertoire de travail + git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); + + // Ajouter les modifications au staging + git.add().addFilepattern(".").call(); + + // Faire un commit avec un message explicite + git.commit().setMessage("Restored content from commit: " + objectId.getName()).call(); + + LOGGER.info("Restored backup from Git repository and committed the changes"); + } catch (IOException | GitAPIException e) { + LOGGER.error("Error while restoring the backup", e); + } + } + + public static void restoreBackupj(Path originalPath, Path backupDir, ObjectId objectId) { try { Git git = Git.open(backupDir.toFile()); git.checkout().setName(objectId.getName()).call(); + /* + faut une methode pour evite le branch nouveau + + */ LOGGER.info("Restored backup from Git repository"); } catch (IOException | GitAPIException e) { LOGGER.error("Error while restoring the backup", e); @@ -152,8 +178,7 @@ public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws } } - @SuppressWarnings("checkstyle:RegexpMultiline") - public void showDiffersJGit(Path originalPath, Path backupDir, String CommitId) throws IOException, GitAPIException { + public List showDiffersJGit(Path originalPath, Path backupDir, String CommitId) throws IOException, GitAPIException { File repoDir = backupDir.toFile(); Repository repository = new FileRepositoryBuilder() @@ -169,14 +194,88 @@ public void showDiffersJGit(Path originalPath, Path backupDir, String CommitId) FileOutputStream fos = new FileOutputStream(FileDescriptor.out); DiffFormatter diffFr = new DiffFormatter(fos); diffFr.setRepository(repository); - diffFr.scan(oldCommit, newCommit); + return diffFr.scan(oldCommit, newCommit); } +// n sera un conteur qui incremente de 1 si l'utilisateur a demandé de voir d'autres versions plus anciens(paquet de 10) +// et decremente de 1 si il veut voir le paquet de 10 versions les plus recentes +// le scroll bas : n->n+1 ; le scroll en haut : n->n-1 + public List retreiveCommits(Path backupDir, int n) throws IOException, GitAPIException { + List retrievedCommits = new ArrayList<>(); + // Ouvrir le dépôt Git + try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { + // Utiliser RevWalk pour parcourir l'historique des commits + try (RevWalk revWalk = new RevWalk(repository)) { + // Commencer depuis HEAD + RevCommit startCommit = revWalk.parseCommit(repository.resolve("HEAD")); + revWalk.markStart(startCommit); + + int count = 0; + int startIndex = n * 10; + int endIndex = startIndex + 10; + + for (RevCommit commit : revWalk) { + // Ignorer les commits jusqu'à l'index de départ + if (count < startIndex) { + count++; + continue; + } + // Arrêter lorsque nous avons atteint l'index de fin + if (count >= endIndex) { + break; + } + // Ajouter les commits à la liste principale + retrievedCommits.add(commit); + count++; + } + } + + + return retrievedCommits; + } + + public List> retrieveCommitDetails(List commits, Repository repository) throws IOException, GitAPIException { + List> commitDetails = new ArrayList<>(); + + // Parcourir la liste des commits fournie en paramètre + for (RevCommit commit : commits) { + // Liste pour stocker les détails du commit + List commitInfo = new ArrayList<>(); + commitInfo.add(commit.getName()); // ID du commit + + // Récupérer la taille des fichiers modifiés par le commit + try (TreeWalk treeWalk = new TreeWalk(repository)) { + treeWalk.addTree(commit.getTree()); + treeWalk.setRecursive(true); + long totalSize = 0; + + while (treeWalk.next()) { + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + totalSize += loader.getSize(); // Calculer la taille en octets + } + + // Convertir la taille en Ko ou Mo + String sizeFormatted = (totalSize > 1024 * 1024) + ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) + : String.format("%.2f Ko", totalSize / 1024.0); + + commitInfo.add(sizeFormatted); // Ajouter la taille formatée + } + + // Ajouter la liste des détails à la liste principale + commitDetails.add(commitInfo); + } + + return commitDetails; + } + + + /* - faire une methode qui accepte commit id et retourne les diff differences avec la version actuelle + faire une methode qui accepte commit id et retourne les diff differences avec la version actuelle( fait) methode qui renvoie n derniers indice de commit methode ayant idcommit retourne data @@ -196,4 +295,4 @@ private void shutdownJGit(Path backupDir, boolean createBackup) { } } } -} + From 129d1756c2e674e2f0ca0f2da4e6492ca8371192 Mon Sep 17 00:00:00 2001 From: Ait Lamine Ilias Date: Mon, 11 Nov 2024 14:35:19 +0100 Subject: [PATCH 10/84] I start switching French comments to English --- buildres/abbrv.jabref.org | 2 +- src/jmh/java/module-info.java | 8 ++++++++ .../jabref/gui/autosaveandbackup/BackupManagerJGit.java | 5 +++-- src/main/resources/csl-styles | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 src/jmh/java/module-info.java diff --git a/buildres/abbrv.jabref.org b/buildres/abbrv.jabref.org index d87037495de..28506356e1e 160000 --- a/buildres/abbrv.jabref.org +++ b/buildres/abbrv.jabref.org @@ -1 +1 @@ -Subproject commit d87037495de7213b896dbb6a20170387de170709 +Subproject commit 28506356e1eeaef34b65afbf5a85562bc8aaebc3 diff --git a/src/jmh/java/module-info.java b/src/jmh/java/module-info.java new file mode 100644 index 00000000000..4c0962156dc --- /dev/null +++ b/src/jmh/java/module-info.java @@ -0,0 +1,8 @@ +/** + * + */ +/** + * + */ +module JabRef { +} \ No newline at end of file diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java index 6ec5a709637..43be980fffe 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -24,10 +23,12 @@ import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -126,7 +127,7 @@ public static void restoreBackupJGit(Path originalPath, Path backupDir, ObjectId // Extraire le contenu de l'objet spécifié (commit) dans le répertoire de travail git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); - // Ajouter les modifications au staging + // Add modifications to staging area git.add().addFilepattern(".").call(); // Faire un commit avec un message explicite diff --git a/src/main/resources/csl-styles b/src/main/resources/csl-styles index b413a778b81..8bcd8fb2ee3 160000 --- a/src/main/resources/csl-styles +++ b/src/main/resources/csl-styles @@ -1 +1 @@ -Subproject commit b413a778b8170cf5aebbb9aeffec62cfd068e19e +Subproject commit 8bcd8fb2ee3151e1c5ec2dcf477fa36b26c647c0 From b82682aa2e4f9dc82f5abbe95bb5858c02a58d32 Mon Sep 17 00:00:00 2001 From: Ait Lamine Ilias Date: Mon, 11 Nov 2024 15:09:33 +0100 Subject: [PATCH 11/84] finish switching comments from French to English --- .../autosaveandbackup/BackupManagerJGit.java | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java index 43be980fffe..aa903a52727 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -104,8 +104,7 @@ private void startBackupTaskJGit(Path backupDir) { private void performBackup(Path backupDir) throws IOException, GitAPIException { /* - - il faut initialiser needsBackup + needsBackup must be initialized */ if (!needsBackup) { return; @@ -124,13 +123,13 @@ public static void restoreBackupJGit(Path originalPath, Path backupDir, ObjectId try { Git git = Git.open(backupDir.toFile()); - // Extraire le contenu de l'objet spécifié (commit) dans le répertoire de travail + //Extract the content of the object (commit) in the work repository git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); - // Add modifications to staging area + // Add commits to staging Area git.add().addFilepattern(".").call(); - // Faire un commit avec un message explicite + // Commit with a message git.commit().setMessage("Restored content from commit: " + objectId.getName()).call(); LOGGER.info("Restored backup from Git repository and committed the changes"); @@ -146,7 +145,7 @@ public static void restoreBackupj(Path originalPath, Path backupDir, ObjectId ob git.checkout().setName(objectId.getName()).call(); /* faut une methode pour evite le branch nouveau - + need a method to avoid the new branch */ LOGGER.info("Restored backup from Git repository"); } catch (IOException | GitAPIException e) { @@ -186,7 +185,7 @@ public List showDiffersJGit(Path originalPath, Path backupDir, String .setGitDir(new File(repoDir, ".git")) .build(); /* - il faut une classe qui affiche les dix dernier backup avec les data: date/ size / number of entries + need a class to show the last ten backups indicating: date/ size/ number of entries */ ObjectId oldCommit = repository.resolve(CommitId); @@ -199,16 +198,16 @@ public List showDiffersJGit(Path originalPath, Path backupDir, String } -// n sera un conteur qui incremente de 1 si l'utilisateur a demandé de voir d'autres versions plus anciens(paquet de 10) -// et decremente de 1 si il veut voir le paquet de 10 versions les plus recentes -// le scroll bas : n->n+1 ; le scroll en haut : n->n-1 +// n is a counter incrementing by 1 when the user asks to see older versions (packs of 10) +// and decrements by 1 when the user asks to see the pack of the 10 earlier versions +// the scroll down: n->n+1 ; the scroll up: n->n-1 public List retreiveCommits(Path backupDir, int n) throws IOException, GitAPIException { List retrievedCommits = new ArrayList<>(); - // Ouvrir le dépôt Git + // Open Git depository try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { - // Utiliser RevWalk pour parcourir l'historique des commits + // Use RevWalk to go through all commits try (RevWalk revWalk = new RevWalk(repository)) { - // Commencer depuis HEAD + // Start from HEAD RevCommit startCommit = revWalk.parseCommit(repository.resolve("HEAD")); revWalk.markStart(startCommit); @@ -217,16 +216,16 @@ public List retreiveCommits(Path backupDir, int n) throws IOException int endIndex = startIndex + 10; for (RevCommit commit : revWalk) { - // Ignorer les commits jusqu'à l'index de départ + // Ignore commits before starting index if (count < startIndex) { count++; continue; } - // Arrêter lorsque nous avons atteint l'index de fin + // Stop at endIndex if (count >= endIndex) { break; } - // Ajouter les commits à la liste principale + // Add commits to the main list retrievedCommits.add(commit); count++; } @@ -239,13 +238,13 @@ public List retreiveCommits(Path backupDir, int n) throws IOException public List> retrieveCommitDetails(List commits, Repository repository) throws IOException, GitAPIException { List> commitDetails = new ArrayList<>(); - // Parcourir la liste des commits fournie en paramètre + // Browse the list of commits given as a parameter for (RevCommit commit : commits) { - // Liste pour stocker les détails du commit + // A list to stock details about the commit List commitInfo = new ArrayList<>(); - commitInfo.add(commit.getName()); // ID du commit + commitInfo.add(commit.getName()); // ID of commit - // Récupérer la taille des fichiers modifiés par le commit + // Get the size of files changes by the commit try (TreeWalk treeWalk = new TreeWalk(repository)) { treeWalk.addTree(commit.getTree()); treeWalk.setRecursive(true); @@ -253,18 +252,18 @@ public List> retrieveCommitDetails(List commits, Reposit while (treeWalk.next()) { ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); - totalSize += loader.getSize(); // Calculer la taille en octets + totalSize += loader.getSize(); // size in bytes } - // Convertir la taille en Ko ou Mo + // Convert the size to Kb or Mb String sizeFormatted = (totalSize > 1024 * 1024) ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) : String.format("%.2f Ko", totalSize / 1024.0); - commitInfo.add(sizeFormatted); // Ajouter la taille formatée + commitInfo.add(sizeFormatted); // Add Formatted size } - // Ajouter la liste des détails à la liste principale + // Add list of details to the main list commitDetails.add(commitInfo); } From 34a288922f50576904e89e297290742435c9a02d Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Tue, 12 Nov 2024 19:09:00 +0100 Subject: [PATCH 12/84] Added table Removed BackupManagerJGit to do UI changes --- .../autosaveandbackup/BackUpManagerJGit.java | 141 ------------------ .../jabref/gui/backup/BackupChoiceDialog.java | 57 +++---- .../jabref/gui/dialogs/BackupUIManager.java | 26 ++-- 3 files changed, 40 insertions(+), 184 deletions(-) delete mode 100644 src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java deleted file mode 100644 index 90d99ef2894..00000000000 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.jabref.gui.autosaveandbackup; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileTime; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ScheduledThreadPoolExecutor; - -import org.jabref.gui.LibraryTab; -import org.jabref.logic.preferences.CliPreferences; -import org.jabref.logic.util.CoarseChangeFilter; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntryTypesManager; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class BackUpManagerJGit { - - private static final Logger LOGGER = LoggerFactory.getLogger(BackupManager.class); - - - private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; - - private static Set runningInstances = new HashSet<>(); - - private final BibDatabaseContext bibDatabaseContext; - private final CliPreferences preferences; - private final ScheduledThreadPoolExecutor executor; - private final CoarseChangeFilter changeFilter; - private final BibEntryTypesManager entryTypesManager; - private final LibraryTab libraryTab; - - // Contains a list of all backup paths - // During writing, the less recent backup file is deleted - //private final Queue backupFilesQueue = new LinkedBlockingQueue<>(); - private boolean needsBackup = false; - - public BackUpManagerJGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { - this.bibDatabaseContext = bibDatabaseContext; - this.entryTypesManager = entryTypesManager; - this.preferences = preferences; - this.executor = new ScheduledThreadPoolExecutor(2); - this.libraryTab = libraryTab; - - changeFilter = new CoarseChangeFilter(bibDatabaseContext); - changeFilter.registerListener(this); - } - /** - * Starts the BackupManager which is associated with the given {@link BibDatabaseContext}. As long as no database - * file is present in {@link BibDatabaseContext}, the {@link BackupManager} will do nothing. - * - * This method is not thread-safe. The caller has to ensure that this method is not called in parallel. - * - * @param bibDatabaseContext Associated {@link BibDatabaseContext} - */ - - public static BackUpManagerJGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { - BackUpManagerJGit backupManagerJGit = new BackUpManagerJGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - backupManagerJGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory()); - runningInstances.add(backupManagerJGit); - return backupManagerJGit; - } - /** - * Shuts down the BackupManager which is associated with the given {@link BibDatabaseContext}. - * - * @param bibDatabaseContext Associated {@link BibDatabaseContext} - * @param createBackup True, if a backup should be created - * @param backupDir The path to the backup directory - */ - public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdown(backupDir, createBackup)); - runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); - } - /** - * Checks whether a backup file exists for the given database file. If it exists, it is checked whether it is - * newer and different from the original. - * - * In case a discarded file is present, the method also returns false, See also {@link #discardBackup(Path)}. - * - * @param originalPath Path to the file a backup should be checked for. Example: jabref.bib. - * - * @return true if backup file exists AND differs from originalPath. false is the - * "default" return value in the good case. In case a discarded file exists, false is returned, too. - * In the case of an exception true is returned to ensure that the user checks the output. - */ - public static boolean backupGitDiffers(Path originalPath, Path backupDir) { - //à implementer - - } - return getLatestBackupPath(originalPath, backupDir).map(latestBackupPath -> { - FileTime latestBackupFileLastModifiedTime; - try { - latestBackupFileLastModifiedTime = Files.getLastModifiedTime(latestBackupPath); - } catch (IOException e) { - LOGGER.debug("Could not get timestamp of backup file {}", latestBackupPath, e); - // If we cannot get the timestamp, we do show any warning - return false; - } - FileTime currentFileLastModifiedTime; - try { - currentFileLastModifiedTime = Files.getLastModifiedTime(originalPath); - } catch (IOException e) { - LOGGER.debug("Could not get timestamp of current file file {}", originalPath, e); - // If we cannot get the timestamp, we do show any warning - return false; - } - if (latestBackupFileLastModifiedTime.compareTo(currentFileLastModifiedTime) <= 0) { - // Backup is older than current file - // We treat the backup as non-different (even if it could differ) - return false; - } - try { - boolean result = Files.mismatch(originalPath, latestBackupPath) != -1L; - if (result) { - LOGGER.info("Backup file {} differs from current file {}", latestBackupPath, originalPath); - } - return result; - } catch (IOException e) { - LOGGER.debug("Could not compare original file and backup file.", e); - // User has to investigate in this case - return true; - } - }).orElse(false); - } - /** - * Restores the backup file by copying and overwriting the original one. - * - * @param originalPath Path to the file which should be equalized to the backup file. - */ - public static void restoreBackup(Path originalPath, Path backupDir) { - /** - * à implementer - * */ - } - -} diff --git a/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java b/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java index bf0b729ba6d..84d45f0aeaa 100644 --- a/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java +++ b/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java @@ -13,9 +13,9 @@ import javafx.scene.control.TableView; import javafx.scene.layout.VBox; -import org.jabref.gui.FXDialog; import org.jabref.gui.collab.DatabaseChange; import org.jabref.gui.frame.ExternalApplicationsPreferences; +import org.jabref.gui.util.BaseDialog; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.BackupFileType; import org.jabref.logic.util.io.BackupFileUtil; @@ -23,62 +23,46 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BackupChoiceDialog extends FXDialog { +public class BackupChoiceDialog extends BaseDialog { public static final ButtonType RESTORE_BACKUP = new ButtonType(Localization.lang("Restore from backup"), ButtonBar.ButtonData.OK_DONE); 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); + private static final Logger LOGGER = LoggerFactory.getLogger(BackupChoiceDialog.class); @FXML private TableView backupTableView; public BackupChoiceDialog(Path originalPath, Path backupDir, ExternalApplicationsPreferences externalApplicationsPreferences) { - super(AlertType.CONFIRMATION, Localization.lang("Choose backup"), true); + setTitle(Localization.lang("Choose backup file")); setHeaderText(null); getDialogPane().setMinHeight(180); + getDialogPane().setMinWidth(600); getDialogPane().getButtonTypes().setAll(RESTORE_BACKUP, IGNORE_BACKUP); Optional backupPathOpt = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); String backupFilename = backupPathOpt.map(Path::getFileName).map(Path::toString).orElse(Localization.lang("File not found")); - String content = Localization.lang("The :") + "\n" + - Localization.lang("Here are some backup versions you can revert to"); + String content = Localization.lang("Here are some backup files you can revert to."); - // Create a TableView for backups + // Builds TableView TableView backupTableView = new TableView<>(); - // Define columns - TableColumn dateColumn = new TableColumn<>("Date of Backup"); + + TableColumn dateColumn = new TableColumn<>(Localization.lang("Date of Backup")); dateColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty()); - TableColumn sizeColumn = new TableColumn<>("Size of Backup"); + TableColumn sizeColumn = new TableColumn<>(Localization.lang("Size of Backup")); sizeColumn.setCellValueFactory(cellData -> cellData.getValue().sizeProperty()); - TableColumn entriesColumn = new TableColumn<>("Number of Entries"); + TableColumn entriesColumn = new TableColumn<>(Localization.lang("Number of Entries")); entriesColumn.setCellValueFactory(cellData -> cellData.getValue().entriesProperty().asObject()); - // Add columns to the table backupTableView.getColumns().addAll(dateColumn, sizeColumn, entriesColumn); + backupTableView.getSelectionModel().selectFirst(); // Sample data - ObservableList data = FXCollections.observableArrayList( - new BackupEntry("2023-11-01", "500 MB", 120), - new BackupEntry("2023-10-15", "300 MB", 80), - new BackupEntry("2023-10-01", "250 MB", 60), - new BackupEntry("2023-11-01", "500 MB", 120), - new BackupEntry("2023-10-15", "300 MB", 80), - new BackupEntry("2023-11-01", "500 MB", 120), - new BackupEntry("2023-10-15", "300 MB", 80), - new BackupEntry("2023-11-01", "500 MB", 120), - new BackupEntry("2023-10-15", "300 MB", 80), - new BackupEntry("2023-11-01", "500 MB", 120), - new BackupEntry("2023-10-15", "300 MB", 80), - new BackupEntry("2023-11-01", "500 MB", 120), - new BackupEntry("2023-10-15", "300 MB", 80), - new BackupEntry("2023-11-01", "500 MB", 120), - new BackupEntry("2023-10-15", "300 MB", 80), - new BackupEntry("2023-11-01", "500 MB", 120), - new BackupEntry("2023-10-15", "300 MB", 80) - ); - + ObservableList data = FXCollections.observableArrayList(); + for (int i = 0; i < 20; i++) { // Adjust 20 to however many entries you want + data.add(new BackupEntry("2023-11-01", String.valueOf(i) + " MB", i)); + } backupTableView.setItems(data); setContentText(content); @@ -86,9 +70,16 @@ public BackupChoiceDialog(Path originalPath, Path backupDir, ExternalApplication // Create a VBox to hold the table and additional content VBox contentBox = new VBox(10); contentBox.getChildren().addAll(new Label(content), backupTableView); - + contentBox.setPrefWidth(380); // Add the VBox to the dialog's content getDialogPane().setContent(contentBox); + + setResultConverter(dialogButton -> { + if (dialogButton == RESTORE_BACKUP) { + return (BackupEntry) backupTableView.getSelectionModel().getSelectedItem(); + } + return null; + }); } } diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index b02561cd5ef..18009807de8 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -14,6 +14,7 @@ import org.jabref.gui.StateManager; import org.jabref.gui.autosaveandbackup.BackupManager; import org.jabref.gui.backup.BackupChoiceDialog; +import org.jabref.gui.backup.BackupEntry; import org.jabref.gui.backup.BackupResolverDialog; import org.jabref.gui.collab.DatabaseChange; import org.jabref.gui.collab.DatabaseChangeList; @@ -61,11 +62,17 @@ public static Optional showRestoreBackupDialog(DialogService dialo BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); return Optional.empty(); } else if (action == BackupResolverDialog.COMPARE_OLDER_BACKUP) { - var actions = showBackupChoiceDialog( - dialogService, - preferences.getExternalApplicationsPreferences(), - originalPath, - preferences.getFilePreferences().getBackupDirectory()); + var test = showBackupChoiceDialog(dialogService, originalPath, preferences); + if (test.isPresent()) { + LOGGER.warn(String.valueOf(test.get().getEntries())); + showBackupResolverDialog( + dialogService, + preferences.getExternalApplicationsPreferences(), + originalPath, + preferences.getFilePreferences().getBackupDirectory()); + } else { + LOGGER.warn("Empty"); + } } else if (action == BackupResolverDialog.REVIEW_BACKUP) { return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); } @@ -81,12 +88,11 @@ private static Optional showBackupResolverDialog(DialogService dialo () -> dialogService.showCustomDialogAndWait(new BackupResolverDialog(originalPath, backupDir, externalApplicationsPreferences))); } - private static Optional showBackupChoiceDialog(DialogService dialogService, - ExternalApplicationsPreferences externalApplicationsPreferences, - Path originalPath, - Path backupDir) { + private static Optional showBackupChoiceDialog(DialogService dialogService, + Path originalPath, + GuiPreferences preferences) { return UiTaskExecutor.runInJavaFXThread( - () -> dialogService.showCustomDialogAndWait(new BackupChoiceDialog(originalPath, backupDir, externalApplicationsPreferences))); + () -> dialogService.showCustomDialogAndWait(new BackupChoiceDialog(originalPath, preferences.getFilePreferences().getBackupDirectory(), preferences.getExternalApplicationsPreferences()))); } private static Optional showReviewBackupDialog( From b299e5faf2d31b8871451a1167b0a10351b4df18 Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Wed, 13 Nov 2024 04:54:37 +0100 Subject: [PATCH 13/84] Navigation through dialogs implemented --- .../jabref/gui/backup/BackupChoiceDialog.java | 89 ++++++++++--------- .../gui/backup/BackupChoiceDialogRecord.java | 8 ++ .../jabref/gui/dialogs/BackupUIManager.java | 37 ++++++-- 3 files changed, 85 insertions(+), 49 deletions(-) create mode 100644 src/main/java/org/jabref/gui/backup/BackupChoiceDialogRecord.java diff --git a/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java b/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java index 84d45f0aeaa..8049e5888a6 100644 --- a/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java +++ b/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java @@ -1,7 +1,6 @@ package org.jabref.gui.backup; import java.nio.file.Path; -import java.util.Optional; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -9,77 +8,81 @@ import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.scene.control.Label; +import javafx.scene.control.Pagination; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.layout.VBox; -import org.jabref.gui.collab.DatabaseChange; -import org.jabref.gui.frame.ExternalApplicationsPreferences; import org.jabref.gui.util.BaseDialog; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.util.BackupFileType; -import org.jabref.logic.util.io.BackupFileUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class BackupChoiceDialog extends BaseDialog { +public class BackupChoiceDialog extends BaseDialog { public static final ButtonType RESTORE_BACKUP = new ButtonType(Localization.lang("Restore from backup"), ButtonBar.ButtonData.OK_DONE); public static final ButtonType IGNORE_BACKUP = new ButtonType(Localization.lang("Ignore backup"), ButtonBar.ButtonData.CANCEL_CLOSE); + public static final ButtonType REVIEW_BACKUP = new ButtonType(Localization.lang("Review backup"), ButtonBar.ButtonData.LEFT); - private static final Logger LOGGER = LoggerFactory.getLogger(BackupChoiceDialog.class); + private static final int ROWS_PER_PAGE = 10; // Define number of rows per page @FXML - private TableView backupTableView; + private final TableView backupTableView; - public BackupChoiceDialog(Path originalPath, Path backupDir, ExternalApplicationsPreferences externalApplicationsPreferences) { + public BackupChoiceDialog(Path originalPath, Path backupDir) { setTitle(Localization.lang("Choose backup file")); setHeaderText(null); - getDialogPane().setMinHeight(180); - getDialogPane().setMinWidth(600); - getDialogPane().getButtonTypes().setAll(RESTORE_BACKUP, IGNORE_BACKUP); - - Optional backupPathOpt = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); - String backupFilename = backupPathOpt.map(Path::getFileName).map(Path::toString).orElse(Localization.lang("File not found")); - String content = Localization.lang("Here are some backup files you can revert to."); - - // Builds TableView - TableView backupTableView = new TableView<>(); - - TableColumn dateColumn = new TableColumn<>(Localization.lang("Date of Backup")); - dateColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty()); + getDialogPane().setMinHeight(150); + getDialogPane().setMinWidth(450); + getDialogPane().getButtonTypes().setAll(RESTORE_BACKUP, IGNORE_BACKUP, REVIEW_BACKUP); + String content = Localization.lang("It looks like JabRef did not shut down cleanly last time the file was used.") + "\n\n" + + Localization.lang("Do you want to recover the library from a backup file?"); - TableColumn sizeColumn = new TableColumn<>(Localization.lang("Size of Backup")); - sizeColumn.setCellValueFactory(cellData -> cellData.getValue().sizeProperty()); - - TableColumn entriesColumn = new TableColumn<>(Localization.lang("Number of Entries")); - entriesColumn.setCellValueFactory(cellData -> cellData.getValue().entriesProperty().asObject()); - - backupTableView.getColumns().addAll(dateColumn, sizeColumn, entriesColumn); - backupTableView.getSelectionModel().selectFirst(); + backupTableView = new TableView(); + setupBackupTableView(); // Sample data ObservableList data = FXCollections.observableArrayList(); - for (int i = 0; i < 20; i++) { // Adjust 20 to however many entries you want - data.add(new BackupEntry("2023-11-01", String.valueOf(i) + " MB", i)); + for (int i = 0; i < 100; i++) { // Adjust 20 to however many entries you want + data.add(new BackupEntry("2023-11-01", i + " MB", i)); } - backupTableView.setItems(data); - setContentText(content); - // Create a VBox to hold the table and additional content + // Pagination control + int pageCount = (int) Math.ceil(data.size() / (double) ROWS_PER_PAGE); + Pagination pagination = new Pagination(pageCount, 0); + pagination.setPageFactory(pageIndex -> { + int start = pageIndex * ROWS_PER_PAGE; + int end = Math.min(start + ROWS_PER_PAGE, data.size()); + backupTableView.setItems(FXCollections.observableArrayList(data.subList(start, end))); + backupTableView.getSelectionModel().selectFirst(); + return new VBox(backupTableView); + }); + + // VBox content to hold the pagination and the label VBox contentBox = new VBox(10); - contentBox.getChildren().addAll(new Label(content), backupTableView); + contentBox.getChildren().addAll(new Label(content), pagination); contentBox.setPrefWidth(380); - // Add the VBox to the dialog's content + + // Set the dialog content getDialogPane().setContent(contentBox); setResultConverter(dialogButton -> { - if (dialogButton == RESTORE_BACKUP) { - return (BackupEntry) backupTableView.getSelectionModel().getSelectedItem(); + if (dialogButton == RESTORE_BACKUP || dialogButton == REVIEW_BACKUP) { + return new BackupChoiceDialogRecord(backupTableView.getSelectionModel().getSelectedItem(), dialogButton); } - return null; + return new BackupChoiceDialogRecord(null, dialogButton); }); } + + private void setupBackupTableView() { + TableColumn dateColumn = new TableColumn<>(Localization.lang("Date of Backup")); + dateColumn.setCellValueFactory(cellData -> cellData.getValue().dateProperty()); + + TableColumn sizeColumn = new TableColumn<>(Localization.lang("Size of Backup")); + sizeColumn.setCellValueFactory(cellData -> cellData.getValue().sizeProperty()); + + TableColumn entriesColumn = new TableColumn<>(Localization.lang("Number of Entries")); + entriesColumn.setCellValueFactory(cellData -> cellData.getValue().entriesProperty().asObject()); + + backupTableView.getColumns().addAll(dateColumn, sizeColumn, entriesColumn); + } } diff --git a/src/main/java/org/jabref/gui/backup/BackupChoiceDialogRecord.java b/src/main/java/org/jabref/gui/backup/BackupChoiceDialogRecord.java new file mode 100644 index 00000000000..0e3cf772825 --- /dev/null +++ b/src/main/java/org/jabref/gui/backup/BackupChoiceDialogRecord.java @@ -0,0 +1,8 @@ +package org.jabref.gui.backup; + +import javafx.scene.control.ButtonType; + +public record BackupChoiceDialogRecord( + BackupEntry entry, + ButtonType action) { +} diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 18009807de8..9f5be274700 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -14,7 +14,7 @@ import org.jabref.gui.StateManager; import org.jabref.gui.autosaveandbackup.BackupManager; import org.jabref.gui.backup.BackupChoiceDialog; -import org.jabref.gui.backup.BackupEntry; +import org.jabref.gui.backup.BackupChoiceDialogRecord; import org.jabref.gui.backup.BackupResolverDialog; import org.jabref.gui.collab.DatabaseChange; import org.jabref.gui.collab.DatabaseChangeList; @@ -57,6 +57,31 @@ public static Optional showRestoreBackupDialog(DialogService dialo preferences.getExternalApplicationsPreferences(), originalPath, preferences.getFilePreferences().getBackupDirectory()); + return actionOpt.flatMap(action -> { + if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { + BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); + return Optional.empty(); + } else if (action == BackupResolverDialog.REVIEW_BACKUP) { + return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); + } else if (action == BackupResolverDialog.COMPARE_OLDER_BACKUP) { + var recordBackupChoice = showBackupChoiceDialog(dialogService, originalPath, preferences); + if (recordBackupChoice.isEmpty()) { + return Optional.empty(); + } + if (recordBackupChoice.get().action() == BackupChoiceDialog.RESTORE_BACKUP) { + LOGGER.warn(recordBackupChoice.get().entry().getSize()); + BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); + return Optional.empty(); + } + if (recordBackupChoice.get().action() == BackupChoiceDialog.REVIEW_BACKUP) { + LOGGER.warn(recordBackupChoice.get().entry().getSize()); + return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); + } + } + return Optional.empty(); + }); + } + /* return actionOpt.flatMap(action -> { if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); @@ -78,8 +103,8 @@ public static Optional showRestoreBackupDialog(DialogService dialo } return Optional.empty(); }); - } + */ private static Optional showBackupResolverDialog(DialogService dialogService, ExternalApplicationsPreferences externalApplicationsPreferences, Path originalPath, @@ -88,11 +113,11 @@ private static Optional showBackupResolverDialog(DialogService dialo () -> dialogService.showCustomDialogAndWait(new BackupResolverDialog(originalPath, backupDir, externalApplicationsPreferences))); } - private static Optional showBackupChoiceDialog(DialogService dialogService, - Path originalPath, - GuiPreferences preferences) { + private static Optional showBackupChoiceDialog(DialogService dialogService, + Path originalPath, + GuiPreferences preferences) { return UiTaskExecutor.runInJavaFXThread( - () -> dialogService.showCustomDialogAndWait(new BackupChoiceDialog(originalPath, preferences.getFilePreferences().getBackupDirectory(), preferences.getExternalApplicationsPreferences()))); + () -> dialogService.showCustomDialogAndWait(new BackupChoiceDialog(originalPath, preferences.getFilePreferences().getBackupDirectory()))); } private static Optional showReviewBackupDialog( From d2b3619f6f606fbd866035ecbea805e59522cac4 Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Tue, 19 Nov 2024 20:44:12 +0100 Subject: [PATCH 14/84] some modif --- .../autosaveandbackup/BackupManagerJGit.java | 91 +++++++++---------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java index 6ec5a709637..2e4c50fd672 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -24,10 +23,12 @@ import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -91,7 +92,7 @@ private void startBackupTaskJGit(Path backupDir) { executor.scheduleAtFixedRate( () -> { try { - performBackup(backupDir); + performBackup(backupDir,originalPath); } catch (IOException | GitAPIException e) { LOGGER.error("Error during backup", e); } @@ -101,11 +102,12 @@ private void startBackupTaskJGit(Path backupDir) { TimeUnit.SECONDS); } - private void performBackup(Path backupDir) throws IOException, GitAPIException { + private void performBackup(Path backupDir,Path originalPath) throws IOException, GitAPIException { /* il faut initialiser needsBackup */ + needsBackup=BackupManagerJGit.backupGitDiffers(backupDir,originalPath); if (!needsBackup) { return; } @@ -124,7 +126,7 @@ public static void restoreBackupJGit(Path originalPath, Path backupDir, ObjectId Git git = Git.open(backupDir.toFile()); // Extraire le contenu de l'objet spécifié (commit) dans le répertoire de travail - git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); + git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); // Ajouter les modifications au staging git.add().addFilepattern(".").call(); @@ -138,20 +140,6 @@ public static void restoreBackupJGit(Path originalPath, Path backupDir, ObjectId } } - public static void restoreBackupj(Path originalPath, Path backupDir, ObjectId objectId) { - try { - - Git git = Git.open(backupDir.toFile()); - git.checkout().setName(objectId.getName()).call(); - /* - faut une methode pour evite le branch nouveau - - */ - LOGGER.info("Restored backup from Git repository"); - } catch (IOException | GitAPIException e) { - LOGGER.error("Error while restoring the backup", e); - } - } /* compare what is in originalPath and last commit @@ -167,7 +155,7 @@ public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws ObjectId headCommitId = repository.resolve("HEAD"); // to get the latest commit id if (headCommitId == null) { // No commits in the repository, so there's no previous backup - return true; + return false; } git.add().addFilepattern(originalPath.getFileName().toString()).call(); String relativePath = backupDir.relativize(originalPath).toString(); @@ -213,7 +201,7 @@ public List retreiveCommits(Path backupDir, int n) throws IOException int count = 0; int startIndex = n * 10; - int endIndex = startIndex + 10; + int endIndex = startIndex + 9; for (RevCommit commit : revWalk) { // Ignorer les commits jusqu'à l'index de départ @@ -222,7 +210,7 @@ public List retreiveCommits(Path backupDir, int n) throws IOException continue; } // Arrêter lorsque nous avons atteint l'index de fin - if (count >= endIndex) { + if (count > endIndex) { break; } // Ajouter les commits à la liste principale @@ -230,45 +218,50 @@ public List retreiveCommits(Path backupDir, int n) throws IOException count++; } } + } return retrievedCommits; } - public List> retrieveCommitDetails(List commits, Repository repository) throws IOException, GitAPIException { - List> commitDetails = new ArrayList<>(); + public List> retrieveCommitDetails(List Commits, Path backupDir) throws IOException, GitAPIException + { - // Parcourir la liste des commits fournie en paramètre - for (RevCommit commit : commits) { - // Liste pour stocker les détails du commit - List commitInfo = new ArrayList<>(); - commitInfo.add(commit.getName()); // ID du commit + try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { + List> commitDetails = new ArrayList<>(); - // Récupérer la taille des fichiers modifiés par le commit - try (TreeWalk treeWalk = new TreeWalk(repository)) { - treeWalk.addTree(commit.getTree()); - treeWalk.setRecursive(true); - long totalSize = 0; + // Parcourir la liste des commits fournie en paramètre + for (RevCommit commit : Commits) { + // Liste pour stocker les détails du commit + List commitInfo = new ArrayList<>(); + commitInfo.add(commit.getName()); // ID du commit - while (treeWalk.next()) { - ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); - totalSize += loader.getSize(); // Calculer la taille en octets - } + // Récupérer la taille des fichiers modifiés par le commit + try (TreeWalk treeWalk = new TreeWalk(repository)) { + treeWalk.addTree(commit.getTree()); + treeWalk.setRecursive(true); + long totalSize = 0; - // Convertir la taille en Ko ou Mo - String sizeFormatted = (totalSize > 1024 * 1024) - ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) - : String.format("%.2f Ko", totalSize / 1024.0); + while (treeWalk.next()) { + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + totalSize += loader.getSize(); // Calculer la taille en octets + } - commitInfo.add(sizeFormatted); // Ajouter la taille formatée - } + // Convertir la taille en Ko ou Mo + String sizeFormatted = (totalSize > 1024 * 1024) + ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) + : String.format("%.2f Ko", totalSize / 1024.0); - // Ajouter la liste des détails à la liste principale - commitDetails.add(commitInfo); - } + commitInfo.add(sizeFormatted); // Ajouter la taille formatée + } - return commitDetails; - } + // Ajouter la liste des détails à la liste principale + commitDetails.add(commitInfo); + } + + return commitDetails; + } + } @@ -289,7 +282,7 @@ private void shutdownJGit(Path backupDir, boolean createBackup) { if (createBackup) { try { - performBackup(backupDir); + performBackup(backupDir,originalPath); } catch (IOException | GitAPIException e) { LOGGER.error("Error during shutdown backup", e); } From 54fa60a3429538997bcfd3bcf434edc83769a58d Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Tue, 19 Nov 2024 21:13:58 +0100 Subject: [PATCH 15/84] fix some issues --- .../autosaveandbackup/BackupManagerJGit.java | 167 +++++++----------- 1 file changed, 59 insertions(+), 108 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java index 1e9ddf0fffe..fdf77df33d3 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -49,7 +49,6 @@ public class BackupManagerJGit { private final LibraryTab libraryTab; private final Git git; - private boolean needsBackup = false; BackupManagerJGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { @@ -76,24 +75,28 @@ public class BackupManagerJGit { } } - public static BackupManagerJGit startJGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { + public BackupManagerJGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { BackupManagerJGit backupManagerJGit = new BackupManagerJGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - backupManagerJGit.startBackupTaskJGit(preferences.getFilePreferences().getBackupDirectory()); + backupManagerJGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); runningInstances.add(backupManagerJGit); return backupManagerJGit; } - public static void shutdownJGit(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup)); + public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup,Path originalPath) { + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup , originalPath)); runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); } - private void startBackupTaskJGit(Path backupDir) { + @SuppressWarnings({"checkstyle:WhitespaceAfter", "checkstyle:LeftCurly"}) + private void startBackupTask(Path backupDir, Path originalPath) + { executor.scheduleAtFixedRate( () -> { try { - performBackup(backupDir,originalPath); - } catch (IOException | GitAPIException e) { + performBackup(backupDir, originalPath); + } catch ( + IOException | + GitAPIException e) { LOGGER.error("Error during backup", e); } }, @@ -102,11 +105,11 @@ private void startBackupTaskJGit(Path backupDir) { TimeUnit.SECONDS); } - private void performBackup(Path backupDir,Path originalPath) throws IOException, GitAPIException { + private void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { /* needsBackup must be initialized */ - needsBackup=BackupManagerJGit.backupGitDiffers(backupDir,originalPath); + needsBackup = BackupManagerJGit.backupGitDiffers(backupDir, originalPath); if (!needsBackup) { return; } @@ -120,18 +123,12 @@ private void performBackup(Path backupDir,Path originalPath) throws IOException, this.needsBackup = false; } - public static void restoreBackupJGit(Path originalPath, Path backupDir, ObjectId objectId) { + @SuppressWarnings("checkstyle:TodoComment") + public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { try { Git git = Git.open(backupDir.toFile()); -<<<<<<< HEAD - // Extraire le contenu de l'objet spécifié (commit) dans le répertoire de travail - git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); -======= - //Extract the content of the object (commit) in the work repository git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); ->>>>>>> b82682aa2e4f9dc82f5abbe95bb5858c02a58d32 - // Add commits to staging Area git.add().addFilepattern(".").call(); @@ -139,13 +136,13 @@ public static void restoreBackupJGit(Path originalPath, Path backupDir, ObjectId git.commit().setMessage("Restored content from commit: " + objectId.getName()).call(); LOGGER.info("Restored backup from Git repository and committed the changes"); - } catch (IOException | GitAPIException e) { + } catch ( + IOException | + GitAPIException e) { LOGGER.error("Error while restoring the backup", e); } } -<<<<<<< HEAD -======= public static void restoreBackupj(Path originalPath, Path backupDir, ObjectId objectId) { try { @@ -156,12 +153,12 @@ public static void restoreBackupj(Path originalPath, Path backupDir, ObjectId ob need a method to avoid the new branch */ LOGGER.info("Restored backup from Git repository"); - } catch (IOException | GitAPIException e) { + } catch ( + IOException | + GitAPIException e) { LOGGER.error("Error while restoring the backup", e); } } ->>>>>>> b82682aa2e4f9dc82f5abbe95bb5858c02a58d32 - /* compare what is in originalPath and last commit */ @@ -187,7 +184,7 @@ public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws } } - public List showDiffersJGit(Path originalPath, Path backupDir, String CommitId) throws IOException, GitAPIException { + public List showDiffers(Path originalPath, Path backupDir, String CommitId) throws IOException, GitAPIException { File repoDir = backupDir.toFile(); Repository repository = new FileRepositoryBuilder() @@ -206,8 +203,7 @@ need a class to show the last ten backups indicating: date/ size/ number of entr return diffFr.scan(oldCommit, newCommit); } - -// n is a counter incrementing by 1 when the user asks to see older versions (packs of 10) + // n is a counter incrementing by 1 when the user asks to see older versions (packs of 10) // and decrements by 1 when the user asks to see the pack of the 10 earlier versions // the scroll down: n->n+1 ; the scroll up: n->n-1 public List retreiveCommits(Path backupDir, int n) throws IOException, GitAPIException { @@ -230,13 +226,7 @@ public List retreiveCommits(Path backupDir, int n) throws IOException count++; continue; } -<<<<<<< HEAD - // Arrêter lorsque nous avons atteint l'index de fin - if (count > endIndex) { -======= - // Stop at endIndex if (count >= endIndex) { ->>>>>>> b82682aa2e4f9dc82f5abbe95bb5858c02a58d32 break; } // Add commits to the main list @@ -246,92 +236,47 @@ public List retreiveCommits(Path backupDir, int n) throws IOException } } - return retrievedCommits; } - public List> retrieveCommitDetails(List Commits, Path backupDir) throws IOException, GitAPIException - { - -<<<<<<< HEAD - try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { - List> commitDetails = new ArrayList<>(); - - // Parcourir la liste des commits fournie en paramètre - for (RevCommit commit : Commits) { - // Liste pour stocker les détails du commit - List commitInfo = new ArrayList<>(); - commitInfo.add(commit.getName()); // ID du commit - - // Récupérer la taille des fichiers modifiés par le commit - try (TreeWalk treeWalk = new TreeWalk(repository)) { - treeWalk.addTree(commit.getTree()); - treeWalk.setRecursive(true); - long totalSize = 0; - - while (treeWalk.next()) { - ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); - totalSize += loader.getSize(); // Calculer la taille en octets - } - - // Convertir la taille en Ko ou Mo - String sizeFormatted = (totalSize > 1024 * 1024) - ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) - : String.format("%.2f Ko", totalSize / 1024.0); - - commitInfo.add(sizeFormatted); // Ajouter la taille formatée + public List> retrieveCommitDetails(List commits, Path backupDir) throws IOException, GitAPIException { + List> commitDetails; + try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { + commitDetails = new ArrayList<>(); + + // Browse the list of commits given as a parameter + for (RevCommit commit : commits) { + // A list to stock details about the commit + List commitInfo = new ArrayList<>(); + commitInfo.add(commit.getName()); // ID of commit + + // Get the size of files changes by the commit + try (TreeWalk treeWalk = new TreeWalk(repository)) { + treeWalk.addTree(commit.getTree()); + treeWalk.setRecursive(true); + long totalSize = 0; + + while (treeWalk.next()) { + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + totalSize += loader.getSize(); // size in bytes } - // Ajouter la liste des détails à la liste principale - commitDetails.add(commitInfo); - } + // Convert the size to Kb or Mb + String sizeFormatted = (totalSize > 1024 * 1024) + ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) + : String.format("%.2f Ko", totalSize / 1024.0); - return commitDetails; - } -======= - // Browse the list of commits given as a parameter - for (RevCommit commit : commits) { - // A list to stock details about the commit - List commitInfo = new ArrayList<>(); - commitInfo.add(commit.getName()); // ID of commit - - // Get the size of files changes by the commit - try (TreeWalk treeWalk = new TreeWalk(repository)) { - treeWalk.addTree(commit.getTree()); - treeWalk.setRecursive(true); - long totalSize = 0; - - while (treeWalk.next()) { - ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); - totalSize += loader.getSize(); // size in bytes + commitInfo.add(sizeFormatted); // Add Formatted size } - // Convert the size to Kb or Mb - String sizeFormatted = (totalSize > 1024 * 1024) - ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) - : String.format("%.2f Ko", totalSize / 1024.0); - - commitInfo.add(sizeFormatted); // Add Formatted size + // Add list of details to the main list + commitDetails.add(commitInfo); } - - // Add list of details to the main list - commitDetails.add(commitInfo); ->>>>>>> b82682aa2e4f9dc82f5abbe95bb5858c02a58d32 } - - - - /* - - faire une methode qui accepte commit id et retourne les diff differences avec la version actuelle( fait) - methode qui renvoie n derniers indice de commit - methode ayant idcommit retourne data - - */ - - - private void shutdownJGit(Path backupDir, boolean createBackup) { + return commitDetails; + } + private void shutdownJGit(Path backupDir, boolean createBackup, Path originalPath) { changeFilter.unregisterListener(this); changeFilter.shutdown(); executor.shutdown(); @@ -344,4 +289,10 @@ private void shutdownJGit(Path backupDir, boolean createBackup) { } } } +} + + + + + From 9a8e615489f6d9346ec56a0a11a96bddb4e18da0 Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Tue, 19 Nov 2024 21:22:04 +0100 Subject: [PATCH 16/84] adding date details --- .../org/jabref/gui/autosaveandbackup/BackupManagerJGit.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java index fdf77df33d3..062a95e3aa7 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -269,6 +270,9 @@ public List> retrieveCommitDetails(List commits, Path ba commitInfo.add(sizeFormatted); // Add Formatted size } + // adding date detail + Date date= commit.getAuthorIdent().getWhen(); + commitInfo.add(date.toString()); // Add list of details to the main list commitDetails.add(commitInfo); } From 0f9fcb932e92deada32246de1565a5382c6278ab Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Tue, 19 Nov 2024 23:41:21 +0100 Subject: [PATCH 17/84] Revert "Merge branch 'branch-1' of https://github.com/khola22/jabref into branch-1" This reverts commit df21a4d479b5842b9572ea6bf756a38ba4360ebd, reversing changes made to 62a8beb05b66576e69fd9604e1712b36c15d86bb. --- src/jmh/java/module-info.java | 8 - src/main/java/module-info.java | 1 - .../autosaveandbackup/BackupManagerJGit.java | 173 ++---------------- .../jabref/gui/dialogs/BackupUIManager.java | 3 - 4 files changed, 17 insertions(+), 168 deletions(-) delete mode 100644 src/jmh/java/module-info.java diff --git a/src/jmh/java/module-info.java b/src/jmh/java/module-info.java deleted file mode 100644 index 4c0962156dc..00000000000 --- a/src/jmh/java/module-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * - */ -/** - * - */ -module JabRef { -} \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 901beace0e9..f0151b8988e 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -193,6 +193,5 @@ requires mslinks; requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; - requires gradle.api; // endregion } diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java index 062a95e3aa7..e2d8d3e047d 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -1,12 +1,8 @@ package org.jabref.gui.autosaveandbackup; import java.io.File; -import java.io.FileDescriptor; -import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -22,14 +18,10 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; -import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,28 +68,24 @@ public class BackupManagerJGit { } } - public BackupManagerJGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { + public static BackupManagerJGit startJGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { BackupManagerJGit backupManagerJGit = new BackupManagerJGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - backupManagerJGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); + backupManagerJGit.startBackupTaskJGit(preferences.getFilePreferences().getBackupDirectory()); runningInstances.add(backupManagerJGit); return backupManagerJGit; } - public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup,Path originalPath) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup , originalPath)); + public static void shutdownJGit(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup)); runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); } - @SuppressWarnings({"checkstyle:WhitespaceAfter", "checkstyle:LeftCurly"}) - private void startBackupTask(Path backupDir, Path originalPath) - { + private void startBackupTaskJGit(Path backupDir) { executor.scheduleAtFixedRate( () -> { try { - performBackup(backupDir, originalPath); - } catch ( - IOException | - GitAPIException e) { + performBackup(backupDir); + } catch (IOException | GitAPIException e) { LOGGER.error("Error during backup", e); } }, @@ -106,11 +94,11 @@ private void startBackupTask(Path backupDir, Path originalPath) TimeUnit.SECONDS); } - private void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { + private void performBackup(Path backupDir) throws IOException, GitAPIException { /* - needsBackup must be initialized + + il faut initialiser needsBackup */ - needsBackup = BackupManagerJGit.backupGitDiffers(backupDir, originalPath); if (!needsBackup) { return; } @@ -124,42 +112,16 @@ private void performBackup(Path backupDir, Path originalPath) throws IOException this.needsBackup = false; } - @SuppressWarnings("checkstyle:TodoComment") - public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { + public static void restoreBackup(Path originalPath, Path backupDir) { try { Git git = Git.open(backupDir.toFile()); - - git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); - // Add commits to staging Area - git.add().addFilepattern(".").call(); - - // Commit with a message - git.commit().setMessage("Restored content from commit: " + objectId.getName()).call(); - - LOGGER.info("Restored backup from Git repository and committed the changes"); - } catch ( - IOException | - GitAPIException e) { - LOGGER.error("Error while restoring the backup", e); - } - } - - public static void restoreBackupj(Path originalPath, Path backupDir, ObjectId objectId) { - try { - - Git git = Git.open(backupDir.toFile()); - git.checkout().setName(objectId.getName()).call(); - /* - faut une methode pour evite le branch nouveau - need a method to avoid the new branch - */ + git.checkout().setName("HEAD").call(); LOGGER.info("Restored backup from Git repository"); - } catch ( - IOException | - GitAPIException e) { + } catch (IOException | GitAPIException e) { LOGGER.error("Error while restoring the backup", e); } } + /* compare what is in originalPath and last commit */ @@ -174,7 +136,7 @@ public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws ObjectId headCommitId = repository.resolve("HEAD"); // to get the latest commit id if (headCommitId == null) { // No commits in the repository, so there's no previous backup - return false; + return true; } git.add().addFilepattern(originalPath.getFileName().toString()).call(); String relativePath = backupDir.relativize(originalPath).toString(); @@ -185,118 +147,17 @@ public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws } } - public List showDiffers(Path originalPath, Path backupDir, String CommitId) throws IOException, GitAPIException { - - File repoDir = backupDir.toFile(); - Repository repository = new FileRepositoryBuilder() - .setGitDir(new File(repoDir, ".git")) - .build(); - /* - need a class to show the last ten backups indicating: date/ size/ number of entries - */ - - ObjectId oldCommit = repository.resolve(CommitId); - ObjectId newCommit = repository.resolve("HEAD"); - - FileOutputStream fos = new FileOutputStream(FileDescriptor.out); - DiffFormatter diffFr = new DiffFormatter(fos); - diffFr.setRepository(repository); - return diffFr.scan(oldCommit, newCommit); - } - - // n is a counter incrementing by 1 when the user asks to see older versions (packs of 10) -// and decrements by 1 when the user asks to see the pack of the 10 earlier versions -// the scroll down: n->n+1 ; the scroll up: n->n-1 - public List retreiveCommits(Path backupDir, int n) throws IOException, GitAPIException { - List retrievedCommits = new ArrayList<>(); - // Open Git depository - try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { - // Use RevWalk to go through all commits - try (RevWalk revWalk = new RevWalk(repository)) { - // Start from HEAD - RevCommit startCommit = revWalk.parseCommit(repository.resolve("HEAD")); - revWalk.markStart(startCommit); - - int count = 0; - int startIndex = n * 10; - int endIndex = startIndex + 9; - - for (RevCommit commit : revWalk) { - // Ignore commits before starting index - if (count < startIndex) { - count++; - continue; - } - if (count >= endIndex) { - break; - } - // Add commits to the main list - retrievedCommits.add(commit); - count++; - } - } - } - - return retrievedCommits; - } - - public List> retrieveCommitDetails(List commits, Path backupDir) throws IOException, GitAPIException { - List> commitDetails; - try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { - commitDetails = new ArrayList<>(); - - // Browse the list of commits given as a parameter - for (RevCommit commit : commits) { - // A list to stock details about the commit - List commitInfo = new ArrayList<>(); - commitInfo.add(commit.getName()); // ID of commit - - // Get the size of files changes by the commit - try (TreeWalk treeWalk = new TreeWalk(repository)) { - treeWalk.addTree(commit.getTree()); - treeWalk.setRecursive(true); - long totalSize = 0; - - while (treeWalk.next()) { - ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); - totalSize += loader.getSize(); // size in bytes - } - - // Convert the size to Kb or Mb - String sizeFormatted = (totalSize > 1024 * 1024) - ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) - : String.format("%.2f Ko", totalSize / 1024.0); - - commitInfo.add(sizeFormatted); // Add Formatted size - } - - // adding date detail - Date date= commit.getAuthorIdent().getWhen(); - commitInfo.add(date.toString()); - // Add list of details to the main list - commitDetails.add(commitInfo); - } - } - - return commitDetails; - } - private void shutdownJGit(Path backupDir, boolean createBackup, Path originalPath) { + private void shutdownJGit(Path backupDir, boolean createBackup) { changeFilter.unregisterListener(this); changeFilter.shutdown(); executor.shutdown(); if (createBackup) { try { - performBackup(backupDir,originalPath); + performBackup(backupDir); } catch (IOException | GitAPIException e) { LOGGER.error("Error during shutdown backup", e); } } } } - - - - - - diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index d5748d7e5fe..9f5be274700 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -24,7 +24,6 @@ import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.util.UiTaskExecutor; -import org.eclipse.jgit.lib.ObjectId; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.ParserResult; @@ -35,7 +34,6 @@ import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; -import org.gradle.internal.impldep.org.eclipse.jgit.lib.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,7 +59,6 @@ public static Optional showRestoreBackupDialog(DialogService dialo preferences.getFilePreferences().getBackupDirectory()); return actionOpt.flatMap(action -> { if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { - BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); return Optional.empty(); } else if (action == BackupResolverDialog.REVIEW_BACKUP) { From 7b6ee3f22ec660b3a85cc7934ea6692500df149d Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Wed, 20 Nov 2024 00:27:11 +0100 Subject: [PATCH 18/84] Reapply "Merge branch 'branch-1' of https://github.com/khola22/jabref into branch-1" This reverts commit 0f9fcb932e92deada32246de1565a5382c6278ab. --- src/jmh/java/module-info.java | 8 + src/main/java/module-info.java | 1 + .../autosaveandbackup/BackupManagerJGit.java | 173 ++++++++++++++++-- .../jabref/gui/dialogs/BackupUIManager.java | 3 + 4 files changed, 168 insertions(+), 17 deletions(-) create mode 100644 src/jmh/java/module-info.java diff --git a/src/jmh/java/module-info.java b/src/jmh/java/module-info.java new file mode 100644 index 00000000000..4c0962156dc --- /dev/null +++ b/src/jmh/java/module-info.java @@ -0,0 +1,8 @@ +/** + * + */ +/** + * + */ +module JabRef { +} \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index f0151b8988e..901beace0e9 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -193,5 +193,6 @@ requires mslinks; requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; + requires gradle.api; // endregion } diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java index e2d8d3e047d..062a95e3aa7 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -1,8 +1,12 @@ package org.jabref.gui.autosaveandbackup; import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -18,10 +22,14 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,24 +76,28 @@ public class BackupManagerJGit { } } - public static BackupManagerJGit startJGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { + public BackupManagerJGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { BackupManagerJGit backupManagerJGit = new BackupManagerJGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - backupManagerJGit.startBackupTaskJGit(preferences.getFilePreferences().getBackupDirectory()); + backupManagerJGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); runningInstances.add(backupManagerJGit); return backupManagerJGit; } - public static void shutdownJGit(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup)); + public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup,Path originalPath) { + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup , originalPath)); runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); } - private void startBackupTaskJGit(Path backupDir) { + @SuppressWarnings({"checkstyle:WhitespaceAfter", "checkstyle:LeftCurly"}) + private void startBackupTask(Path backupDir, Path originalPath) + { executor.scheduleAtFixedRate( () -> { try { - performBackup(backupDir); - } catch (IOException | GitAPIException e) { + performBackup(backupDir, originalPath); + } catch ( + IOException | + GitAPIException e) { LOGGER.error("Error during backup", e); } }, @@ -94,11 +106,11 @@ private void startBackupTaskJGit(Path backupDir) { TimeUnit.SECONDS); } - private void performBackup(Path backupDir) throws IOException, GitAPIException { + private void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { /* - - il faut initialiser needsBackup + needsBackup must be initialized */ + needsBackup = BackupManagerJGit.backupGitDiffers(backupDir, originalPath); if (!needsBackup) { return; } @@ -112,16 +124,42 @@ private void performBackup(Path backupDir) throws IOException, GitAPIException { this.needsBackup = false; } - public static void restoreBackup(Path originalPath, Path backupDir) { + @SuppressWarnings("checkstyle:TodoComment") + public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { try { Git git = Git.open(backupDir.toFile()); - git.checkout().setName("HEAD").call(); - LOGGER.info("Restored backup from Git repository"); - } catch (IOException | GitAPIException e) { + + git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); + // Add commits to staging Area + git.add().addFilepattern(".").call(); + + // Commit with a message + git.commit().setMessage("Restored content from commit: " + objectId.getName()).call(); + + LOGGER.info("Restored backup from Git repository and committed the changes"); + } catch ( + IOException | + GitAPIException e) { LOGGER.error("Error while restoring the backup", e); } } + public static void restoreBackupj(Path originalPath, Path backupDir, ObjectId objectId) { + try { + + Git git = Git.open(backupDir.toFile()); + git.checkout().setName(objectId.getName()).call(); + /* + faut une methode pour evite le branch nouveau + need a method to avoid the new branch + */ + LOGGER.info("Restored backup from Git repository"); + } catch ( + IOException | + GitAPIException e) { + LOGGER.error("Error while restoring the backup", e); + } + } /* compare what is in originalPath and last commit */ @@ -136,7 +174,7 @@ public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws ObjectId headCommitId = repository.resolve("HEAD"); // to get the latest commit id if (headCommitId == null) { // No commits in the repository, so there's no previous backup - return true; + return false; } git.add().addFilepattern(originalPath.getFileName().toString()).call(); String relativePath = backupDir.relativize(originalPath).toString(); @@ -147,17 +185,118 @@ public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws } } - private void shutdownJGit(Path backupDir, boolean createBackup) { + public List showDiffers(Path originalPath, Path backupDir, String CommitId) throws IOException, GitAPIException { + + File repoDir = backupDir.toFile(); + Repository repository = new FileRepositoryBuilder() + .setGitDir(new File(repoDir, ".git")) + .build(); + /* + need a class to show the last ten backups indicating: date/ size/ number of entries + */ + + ObjectId oldCommit = repository.resolve(CommitId); + ObjectId newCommit = repository.resolve("HEAD"); + + FileOutputStream fos = new FileOutputStream(FileDescriptor.out); + DiffFormatter diffFr = new DiffFormatter(fos); + diffFr.setRepository(repository); + return diffFr.scan(oldCommit, newCommit); + } + + // n is a counter incrementing by 1 when the user asks to see older versions (packs of 10) +// and decrements by 1 when the user asks to see the pack of the 10 earlier versions +// the scroll down: n->n+1 ; the scroll up: n->n-1 + public List retreiveCommits(Path backupDir, int n) throws IOException, GitAPIException { + List retrievedCommits = new ArrayList<>(); + // Open Git depository + try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { + // Use RevWalk to go through all commits + try (RevWalk revWalk = new RevWalk(repository)) { + // Start from HEAD + RevCommit startCommit = revWalk.parseCommit(repository.resolve("HEAD")); + revWalk.markStart(startCommit); + + int count = 0; + int startIndex = n * 10; + int endIndex = startIndex + 9; + + for (RevCommit commit : revWalk) { + // Ignore commits before starting index + if (count < startIndex) { + count++; + continue; + } + if (count >= endIndex) { + break; + } + // Add commits to the main list + retrievedCommits.add(commit); + count++; + } + } + } + + return retrievedCommits; + } + + public List> retrieveCommitDetails(List commits, Path backupDir) throws IOException, GitAPIException { + List> commitDetails; + try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { + commitDetails = new ArrayList<>(); + + // Browse the list of commits given as a parameter + for (RevCommit commit : commits) { + // A list to stock details about the commit + List commitInfo = new ArrayList<>(); + commitInfo.add(commit.getName()); // ID of commit + + // Get the size of files changes by the commit + try (TreeWalk treeWalk = new TreeWalk(repository)) { + treeWalk.addTree(commit.getTree()); + treeWalk.setRecursive(true); + long totalSize = 0; + + while (treeWalk.next()) { + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + totalSize += loader.getSize(); // size in bytes + } + + // Convert the size to Kb or Mb + String sizeFormatted = (totalSize > 1024 * 1024) + ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) + : String.format("%.2f Ko", totalSize / 1024.0); + + commitInfo.add(sizeFormatted); // Add Formatted size + } + + // adding date detail + Date date= commit.getAuthorIdent().getWhen(); + commitInfo.add(date.toString()); + // Add list of details to the main list + commitDetails.add(commitInfo); + } + } + + return commitDetails; + } + private void shutdownJGit(Path backupDir, boolean createBackup, Path originalPath) { changeFilter.unregisterListener(this); changeFilter.shutdown(); executor.shutdown(); if (createBackup) { try { - performBackup(backupDir); + performBackup(backupDir,originalPath); } catch (IOException | GitAPIException e) { LOGGER.error("Error during shutdown backup", e); } } } } + + + + + + diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 9f5be274700..d5748d7e5fe 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -24,6 +24,7 @@ import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.util.UiTaskExecutor; +import org.eclipse.jgit.lib.ObjectId; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.ParserResult; @@ -34,6 +35,7 @@ import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; +import org.gradle.internal.impldep.org.eclipse.jgit.lib.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,6 +61,7 @@ public static Optional showRestoreBackupDialog(DialogService dialo preferences.getFilePreferences().getBackupDirectory()); return actionOpt.flatMap(action -> { if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { + BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); return Optional.empty(); } else if (action == BackupResolverDialog.REVIEW_BACKUP) { From b67b2d97fa0863db49a82a1af5803fa069d55aba Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Wed, 20 Nov 2024 03:03:13 +0100 Subject: [PATCH 19/84] minor fixes --- src/main/java/module-info.java | 1 - src/main/java/org/jabref/gui/dialogs/BackupUIManager.java | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 901beace0e9..f0151b8988e 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -193,6 +193,5 @@ requires mslinks; requires org.antlr.antlr4.runtime; requires org.libreoffice.uno; - requires gradle.api; // endregion } diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index d5748d7e5fe..9f5be274700 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -24,7 +24,6 @@ import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.util.UiTaskExecutor; -import org.eclipse.jgit.lib.ObjectId; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.ParserResult; @@ -35,7 +34,6 @@ import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; -import org.gradle.internal.impldep.org.eclipse.jgit.lib.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,7 +59,6 @@ public static Optional showRestoreBackupDialog(DialogService dialo preferences.getFilePreferences().getBackupDirectory()); return actionOpt.flatMap(action -> { if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { - BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); return Optional.empty(); } else if (action == BackupResolverDialog.REVIEW_BACKUP) { From 3f9584edb83af0cef8c96364c87322ea7935b778 Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Wed, 20 Nov 2024 11:27:13 +0100 Subject: [PATCH 20/84] Minor Synthax fixes --- .../gui/autosaveandbackup/BackupManagerJGit.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java index 062a95e3aa7..231757acf3b 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java @@ -83,14 +83,13 @@ public BackupManagerJGit start(LibraryTab libraryTab, BibDatabaseContext bibData return backupManagerJGit; } - public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup,Path originalPath) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup , originalPath)); + public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup, Path originalPath) { + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup, originalPath)); runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); } @SuppressWarnings({"checkstyle:WhitespaceAfter", "checkstyle:LeftCurly"}) - private void startBackupTask(Path backupDir, Path originalPath) - { + private void startBackupTask(Path backupDir, Path originalPath) { executor.scheduleAtFixedRate( () -> { try { @@ -271,7 +270,7 @@ public List> retrieveCommitDetails(List commits, Path ba } // adding date detail - Date date= commit.getAuthorIdent().getWhen(); + Date date = commit.getAuthorIdent().getWhen(); commitInfo.add(date.toString()); // Add list of details to the main list commitDetails.add(commitInfo); @@ -280,6 +279,7 @@ public List> retrieveCommitDetails(List commits, Path ba return commitDetails; } + private void shutdownJGit(Path backupDir, boolean createBackup, Path originalPath) { changeFilter.unregisterListener(this); changeFilter.shutdown(); @@ -287,7 +287,7 @@ private void shutdownJGit(Path backupDir, boolean createBackup, Path originalPat if (createBackup) { try { - performBackup(backupDir,originalPath); + performBackup(backupDir, originalPath); } catch (IOException | GitAPIException e) { LOGGER.error("Error during shutdown backup", e); } From 76b6f92152a84c679f94b3008e67910f957d9a8a Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Wed, 20 Nov 2024 11:32:43 +0100 Subject: [PATCH 21/84] supression de classe BackupManagerJGit --- .../gui/autosaveandbackup/BackupManager.java | 527 ++++++++---------- .../autosaveandbackup/BackupManagerJGit.java | 302 ---------- .../autosaveandbackup/BackupManagerTest.java | 2 +- 3 files changed, 220 insertions(+), 611 deletions(-) delete mode 100644 src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java index 849ecbccac7..fe0c16df42e 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java @@ -1,65 +1,46 @@ package org.jabref.gui.autosaveandbackup; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileTime; +import java.util.ArrayList; +import java.util.Date; import java.util.HashSet; import java.util.List; -import java.util.Optional; -import java.util.Queue; import java.util.Set; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import javafx.scene.control.TableColumn; - import org.jabref.gui.LibraryTab; -import org.jabref.gui.maintable.BibEntryTableViewModel; -import org.jabref.gui.maintable.columns.MainTableColumn; -import org.jabref.logic.bibtex.InvalidFieldValueException; -import org.jabref.logic.exporter.AtomicFileWriter; -import org.jabref.logic.exporter.BibWriter; -import org.jabref.logic.exporter.BibtexDatabaseWriter; -import org.jabref.logic.exporter.SelfContainedSaveConfiguration; import org.jabref.logic.preferences.CliPreferences; -import org.jabref.logic.util.BackupFileType; import org.jabref.logic.util.CoarseChangeFilter; -import org.jabref.logic.util.io.BackupFileUtil; -import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.database.event.BibDatabaseContextChangedEvent; -import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.metadata.SaveOrder; -import org.jabref.model.metadata.SelfContainedSaveOrder; -import com.google.common.eventbus.Subscribe; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Backups the given bib database file from {@link BibDatabaseContext} on every {@link BibDatabaseContextChangedEvent}. - * An intelligent {@link ExecutorService} with a {@link BlockingQueue} prevents a high load while making backups and - * rejects all redundant backup tasks. This class does not manage the .bak file which is created when opening a - * database. - */ public class BackupManager { private static final Logger LOGGER = LoggerFactory.getLogger(BackupManager.class); - private static final int MAXIMUM_BACKUP_FILE_COUNT = 10; - private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; - private static Set runningInstances = new HashSet<>(); + private static Set runningInstances = new HashSet(); private final BibDatabaseContext bibDatabaseContext; private final CliPreferences preferences; @@ -67,13 +48,11 @@ public class BackupManager { private final CoarseChangeFilter changeFilter; private final BibEntryTypesManager entryTypesManager; private final LibraryTab libraryTab; + private final Git git; - // Contains a list of all backup paths - // During writing, the less recent backup file is deleted - private final Queue backupFilesQueue = new LinkedBlockingQueue<>(); private boolean needsBackup = false; - BackupManager(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { + BackupManager(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { this.bibDatabaseContext = bibDatabaseContext; this.entryTypesManager = entryTypesManager; this.preferences = preferences; @@ -82,312 +61,244 @@ public class BackupManager { changeFilter = new CoarseChangeFilter(bibDatabaseContext); changeFilter.registerListener(this); - } - - /** - * Determines the most recent backup file name - */ - static Path getBackupPathForNewBackup(Path originalPath, Path backupDir) { - return BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(originalPath, BackupFileType.BACKUP, backupDir); - } - /** - * Determines the most recent existing backup file name - */ - static Optional getLatestBackupPath(Path originalPath, Path backupDir) { - return BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); - } - - /** - * Starts the BackupManager which is associated with the given {@link BibDatabaseContext}. As long as no database - * file is present in {@link BibDatabaseContext}, the {@link BackupManager} will do nothing. - * - * This method is not thread-safe. The caller has to ensure that this method is not called in parallel. - * - * @param bibDatabaseContext Associated {@link BibDatabaseContext} - */ - public static BackupManager start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { - BackupManager backupManager = new BackupManager(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - backupManager.startBackupTask(preferences.getFilePreferences().getBackupDirectory()); - runningInstances.add(backupManager); - return backupManager; + // Initialize Git repository + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + git = new Git(builder.setGitDir(new File(preferences.getFilePreferences().getBackupDirectory().toFile(), ".git")) + .readEnvironment() + .findGitDir() + .build()); + if (git.getRepository().getObjectDatabase().exists()) { + LOGGER.info("Git repository already exists"); + } else { + git.init().call(); + LOGGER.info("Initialized new Git repository"); + } } - /** - * Marks the backup as discarded at the library which is associated with the given {@link BibDatabaseContext}. - * - * @param bibDatabaseContext Associated {@link BibDatabaseContext} - */ - public static void discardBackup(BibDatabaseContext bibDatabaseContext, Path backupDir) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.discardBackup(backupDir)); + public BackupManager start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { + BackupManager BackupManager = new BackupManager(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + BackupManager.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); + runningInstances.add(BackupManager); + return BackupManager; } - /** - * Shuts down the BackupManager which is associated with the given {@link BibDatabaseContext}. - * - * @param bibDatabaseContext Associated {@link BibDatabaseContext} - * @param createBackup True, if a backup should be created - * @param backupDir The path to the backup directory - */ - public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdown(backupDir, createBackup)); + @SuppressWarnings({"checkstyle:NoWhitespaceBefore", "checkstyle:WhitespaceAfter"}) + public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup, Path originalPath) { + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup, originalPath)); runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); } - /** - * Checks whether a backup file exists for the given database file. If it exists, it is checked whether it is - * newer and different from the original. - * - * In case a discarded file is present, the method also returns false, See also {@link #discardBackup(Path)}. - * - * @param originalPath Path to the file a backup should be checked for. Example: jabref.bib. - * - * @return true if backup file exists AND differs from originalPath. false is the - * "default" return value in the good case. In case a discarded file exists, false is returned, too. - * In the case of an exception true is returned to ensure that the user checks the output. - */ - public static boolean backupFileDiffers(Path originalPath, Path backupDir) { - Path discardedFile = determineDiscardedFile(originalPath, backupDir); - if (Files.exists(discardedFile)) { - try { - Files.delete(discardedFile); - } catch (IOException e) { - LOGGER.error("Could not remove discarded file {}", discardedFile, e); - return true; - } - return false; - } - return getLatestBackupPath(originalPath, backupDir).map(latestBackupPath -> { - FileTime latestBackupFileLastModifiedTime; - try { - latestBackupFileLastModifiedTime = Files.getLastModifiedTime(latestBackupPath); - } catch (IOException e) { - LOGGER.debug("Could not get timestamp of backup file {}", latestBackupPath, e); - // If we cannot get the timestamp, we do show any warning - return false; - } - FileTime currentFileLastModifiedTime; - try { - currentFileLastModifiedTime = Files.getLastModifiedTime(originalPath); - } catch (IOException e) { - LOGGER.debug("Could not get timestamp of current file file {}", originalPath, e); - // If we cannot get the timestamp, we do show any warning - return false; - } - if (latestBackupFileLastModifiedTime.compareTo(currentFileLastModifiedTime) <= 0) { - // Backup is older than current file - // We treat the backup as non-different (even if it could differ) - return false; - } - try { - boolean result = Files.mismatch(originalPath, latestBackupPath) != -1L; - if (result) { - LOGGER.info("Backup file {} differs from current file {}", latestBackupPath, originalPath); - } - return result; - } catch (IOException e) { - LOGGER.debug("Could not compare original file and backup file.", e); - // User has to investigate in this case - return true; - } - }).orElse(false); + @SuppressWarnings({"checkstyle:WhitespaceAfter", "checkstyle:LeftCurly"}) + private void startBackupTask(Path backupDir, Path originalPath) { + executor.scheduleAtFixedRate( + () -> { + try { + performBackup(backupDir, originalPath); + } catch ( + IOException | + GitAPIException e) { + LOGGER.error("Error during backup", e); + } + }, + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, + TimeUnit.SECONDS); } - /** - * Restores the backup file by copying and overwriting the original one. - * - * @param originalPath Path to the file which should be equalized to the backup file. - */ - public static void restoreBackup(Path originalPath, Path backupDir) { - Optional backupPath = getLatestBackupPath(originalPath, backupDir); - if (backupPath.isEmpty()) { - LOGGER.error("There is no backup file"); + private void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { + /* + needsBackup must be initialized + */ + needsBackup = BackupManager.backupGitDiffers(backupDir, originalPath); + if (!needsBackup) { return; } + + // Add and commit changes + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("Backup at " + System.currentTimeMillis()).call(); + LOGGER.info("Committed backup: {}", commit.getId()); + + // Reset the backup flag + this.needsBackup = false; + } + + @SuppressWarnings("checkstyle:TodoComment") + public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { try { - Files.copy(backupPath.get(), originalPath, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - LOGGER.error("Error while restoring the backup file.", e); + Git git = Git.open(backupDir.toFile()); + + git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); + // Add commits to staging Area + git.add().addFilepattern(".").call(); + + // Commit with a message + git.commit().setMessage("Restored content from commit: " + objectId.getName()).call(); + + LOGGER.info("Restored backup from Git repository and committed the changes"); + } catch ( + IOException | + GitAPIException e) { + LOGGER.error("Error while restoring the backup", e); } } - Optional determineBackupPathForNewBackup(Path backupDir) { - return bibDatabaseContext.getDatabasePath().map(path -> BackupManager.getBackupPathForNewBackup(path, backupDir)); - } + public static void restoreBackupj(Path originalPath, Path backupDir, ObjectId objectId) { + try { - /** - * This method is called as soon as the scheduler says: "Do the backup" - * - * SIDE EFFECT: Deletes oldest backup file - * - * @param backupPath the full path to the file where the library should be backed up to - */ - void performBackup(Path backupPath) { - if (!needsBackup) { - return; + Git git = Git.open(backupDir.toFile()); + git.checkout().setName(objectId.getName()).call(); + /* + faut une methode pour evite le branch nouveau + need a method to avoid the new branch + */ + LOGGER.info("Restored backup from Git repository"); + } catch ( + IOException | + GitAPIException e) { + LOGGER.error("Error while restoring the backup", e); } - - // We opted for "while" to delete backups in case there are more than 10 - while (backupFilesQueue.size() >= MAXIMUM_BACKUP_FILE_COUNT) { - Path oldestBackupFile = backupFilesQueue.poll(); - try { - Files.delete(oldestBackupFile); - } catch (IOException e) { - LOGGER.error("Could not delete backup file {}", oldestBackupFile, e); + } + /* + compare what is in originalPath and last commit + */ + + public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws IOException, GitAPIException { + + File repoDir = backupDir.toFile(); + Repository repository = new FileRepositoryBuilder() + .setGitDir(new File(repoDir, ".git")) + .build(); + try (Git git = new Git(repository)) { + ObjectId headCommitId = repository.resolve("HEAD"); // to get the latest commit id + if (headCommitId == null) { + // No commits in the repository, so there's no previous backup + return false; } - } - //l'ordre dans lequel les entrées BibTeX doivent être écrites dans le fichier de sauvegarde. - // Si l'utilisateur a trié la table d'affichage des entrées dans JabRef, cet ordre est récupéré. - // Sinon, un ordre par défaut est utilisé. - // code similar to org.jabref.gui.exporter.SaveDatabaseAction.saveDatabase - SelfContainedSaveOrder saveOrder = bibDatabaseContext - .getMetaData().getSaveOrder() - .map(so -> { - if (so.getOrderType() == SaveOrder.OrderType.TABLE) { - // We need to "flatten out" SaveOrder.OrderType.TABLE as BibWriter does not have access to preferences - List> sortOrder = libraryTab.getMainTable().getSortOrder(); - return new SelfContainedSaveOrder( - SaveOrder.OrderType.SPECIFIED, - sortOrder.stream() - .filter(col -> col instanceof MainTableColumn) - .map(column -> ((MainTableColumn) column).getModel()) - .flatMap(model -> model.getSortCriteria().stream()) - .toList()); - } else { - return SelfContainedSaveOrder.of(so); - } - }) - .orElse(SaveOrder.getDefaultSaveOrder()); - - //Elle configure la sauvegarde, en indiquant qu'aucune sauvegarde supplémentaire (backup) ne doit être créée, - // que l'ordre de sauvegarde doit être celui défini, et que les entrées doivent être formatées selon les préférences - // utilisateur. - SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() - .withMakeBackup(false) - .withSaveOrder(saveOrder) - .withReformatOnSave(preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); - - // "Clone" the database context - // We "know" that "only" the BibEntries might be changed during writing (see [org.jabref.logic.exporter.BibDatabaseWriter.savePartOfDatabase]) - //Chaque entrée BibTeX (comme un article, livre, etc.) est clonée en utilisant la méthode clone(). - // Cela garantit que les modifications faites pendant la sauvegarde n'affecteront pas l'entrée originale. - List list = bibDatabaseContext.getDatabase().getEntries().stream() - .map(BibEntry::clone) - .map(BibEntry.class::cast) - .toList(); - BibDatabase bibDatabaseClone = new BibDatabase(list); - BibDatabaseContext bibDatabaseContextClone = new BibDatabaseContext(bibDatabaseClone, bibDatabaseContext.getMetaData()); - //Elle définit l'encodage à utiliser pour écrire le fichier. Cela garantit que les caractères spéciaux sont bien sauvegardés. - Charset encoding = bibDatabaseContext.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8); - // We want to have successful backups only - // Thus, we do not use a plain "FileWriter", but the "AtomicFileWriter" - // Example: What happens if one hard powers off the machine (or kills the jabref process) during writing of the backup? - // This MUST NOT create a broken backup file that then jabref wants to "restore" from? - try (Writer writer = new AtomicFileWriter(backupPath, encoding, false)) { - BibWriter bibWriter = new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator()); - new BibtexDatabaseWriter( - bibWriter, - saveConfiguration, - preferences.getFieldPreferences(), - preferences.getCitationKeyPatternPreferences(), - entryTypesManager) - // we save the clone to prevent the original database (and thus the UI) from being changed - .saveDatabase(bibDatabaseContextClone); - backupFilesQueue.add(backupPath); - - // We wrote the file successfully - // Thus, we currently do not need any new backup - this.needsBackup = false; - } catch (IOException e) { - logIfCritical(backupPath, e); + git.add().addFilepattern(originalPath.getFileName().toString()).call(); + String relativePath = backupDir.relativize(originalPath).toString(); + List diffs = git.diff() + .setPathFilter(PathFilter.create(relativePath)) // Utiliser PathFilter ici + .call(); + return !diffs.isEmpty(); } } - private static Path determineDiscardedFile(Path file, Path backupDir) { - return backupDir.resolve(BackupFileUtil.getUniqueFilePrefix(file) + "--" + file.getFileName() + "--discarded"); - } + public List showDiffers(Path originalPath, Path backupDir, String CommitId) throws IOException, GitAPIException { - /** - * Marks the backups as discarded. - * - * We do not delete any files, because the user might want to recover old backup files. - * Therefore, we mark discarded backups by a --discarded file. - */ - public void discardBackup(Path backupDir) { - Path path = determineDiscardedFile(bibDatabaseContext.getDatabasePath().get(), backupDir); - try { - Files.createFile(path); - } catch (IOException e) { - LOGGER.info("Could not create backup file {}", path, e); - } - } + File repoDir = backupDir.toFile(); + Repository repository = new FileRepositoryBuilder() + .setGitDir(new File(repoDir, ".git")) + .build(); + /* + need a class to show the last ten backups indicating: date/ size/ number of entries + */ - private void logIfCritical(Path backupPath, IOException e) { - Throwable innermostCause = e; - while (innermostCause.getCause() != null) { - innermostCause = innermostCause.getCause(); - } - boolean isErrorInField = innermostCause instanceof InvalidFieldValueException; + ObjectId oldCommit = repository.resolve(CommitId); + ObjectId newCommit = repository.resolve("HEAD"); - // do not print errors in field values into the log during autosave - if (!isErrorInField) { - LOGGER.error("Error while saving to file {}", backupPath, e); - } + FileOutputStream fos = new FileOutputStream(FileDescriptor.out); + DiffFormatter diffFr = new DiffFormatter(fos); + diffFr.setRepository(repository); + return diffFr.scan(oldCommit, newCommit); } - @Subscribe - public synchronized void listen(@SuppressWarnings("unused") BibDatabaseContextChangedEvent event) { - if (!event.isFilteredOut()) { - this.needsBackup = true; + // n is a counter incrementing by 1 when the user asks to see older versions (packs of 10) +// and decrements by 1 when the user asks to see the pack of the 10 earlier versions +// the scroll down: n->n+1 ; the scroll up: n->n-1 + public List retreiveCommits(Path backupDir, int n) throws IOException, GitAPIException { + List retrievedCommits = new ArrayList<>(); + // Open Git depository + try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { + // Use RevWalk to go through all commits + try (RevWalk revWalk = new RevWalk(repository)) { + // Start from HEAD + RevCommit startCommit = revWalk.parseCommit(repository.resolve("HEAD")); + revWalk.markStart(startCommit); + + int count = 0; + int startIndex = n * 10; + int endIndex = startIndex + 9; + + for (RevCommit commit : revWalk) { + // Ignore commits before starting index + if (count < startIndex) { + count++; + continue; + } + if (count >= endIndex) { + break; + } + // Add commits to the main list + retrievedCommits.add(commit); + count++; + } + } } + + return retrievedCommits; } - private void startBackupTask(Path backupDir) { - fillQueue(backupDir);//remplie backupFilesQueue les files .sav de le meme bibl + @SuppressWarnings("checkstyle:WhitespaceAround") + public List> retrieveCommitDetails(List commits, Path backupDir) throws IOException, GitAPIException { + List> commitDetails; + try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { + commitDetails = new ArrayList<>(); + + // Browse the list of commits given as a parameter + for (RevCommit commit : commits) { + // A list to stock details about the commit + List commitInfo = new ArrayList<>(); + commitInfo.add(commit.getName()); // ID of commit + + // Get the size of files changes by the commit + try (TreeWalk treeWalk = new TreeWalk(repository)) { + treeWalk.addTree(commit.getTree()); + treeWalk.setRecursive(true); + long totalSize = 0; + + while (treeWalk.next()) { + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + totalSize += loader.getSize(); // size in bytes + } + + // Convert the size to Kb or Mb + String sizeFormatted = (totalSize > 1024 * 1024) + ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) + : String.format("%.2f Ko", totalSize / 1024.0); - executor.scheduleAtFixedRate( - // We need to determine the backup path on each action, because we use the timestamp in the filename - () -> determineBackupPathForNewBackup(backupDir).ifPresent(path -> this.performBackup(path)), - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - TimeUnit.SECONDS); - } -//La méthode fillQueue(backupDir) est définie dans le code et son rôle est de lister et d'ajouter -// les fichiers de sauvegarde existants dans une file d'attente, - private void fillQueue(Path backupDir) { - if (!Files.exists(backupDir)) { - return; - } - bibDatabaseContext.getDatabasePath().ifPresent(databasePath -> { - // code similar to {@link org.jabref.logic.util.io.BackupFileUtil.getPathOfLatestExisingBackupFile} - final String prefix = BackupFileUtil.getUniqueFilePrefix(databasePath) + "--" + databasePath.getFileName(); - try { - List allSavFiles = Files.list(backupDir) - // just list the .sav belonging to the given targetFile - .filter(p -> p.getFileName().toString().startsWith(prefix))//tous les files .sav commencerait par ce prefix - .sorted().toList(); - backupFilesQueue.addAll(allSavFiles); - } catch (IOException e) { - LOGGER.error("Could not determine most recent file", e); + commitInfo.add(sizeFormatted); // Add Formatted size + } + + // adding date detail + Date date = commit.getAuthorIdent().getWhen(); + commitInfo.add(date.toString()); + // Add list of details to the main list + commitDetails.add(commitInfo); } - }); + } + + return commitDetails; } - /** - * Unregisters the BackupManager from the eventBus of {@link BibDatabaseContext}. - * This method should only be used when closing a database/JabRef in a normal way. - * - * @param backupDir The backup directory - * @param createBackup If the backup manager should still perform a backup - */ - private void shutdown(Path backupDir, boolean createBackup) { + private void shutdownJGit(Path backupDir, boolean createBackup, Path originalPath) { changeFilter.unregisterListener(this); changeFilter.shutdown(); executor.shutdown(); if (createBackup) { - // Ensure that backup is a recent one - determineBackupPathForNewBackup(backupDir).ifPresent(this::performBackup); + try { + performBackup(backupDir, originalPath); + } catch (IOException | GitAPIException e) { + LOGGER.error("Error during shutdown backup", e); + } } } } + + + + + + diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java deleted file mode 100644 index 062a95e3aa7..00000000000 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerJGit.java +++ /dev/null @@ -1,302 +0,0 @@ -package org.jabref.gui.autosaveandbackup; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import org.jabref.gui.LibraryTab; -import org.jabref.logic.preferences.CliPreferences; -import org.jabref.logic.util.CoarseChangeFilter; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntryTypesManager; - -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.diff.DiffEntry; -import org.eclipse.jgit.diff.DiffFormatter; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import org.eclipse.jgit.treewalk.TreeWalk; -import org.eclipse.jgit.treewalk.filter.PathFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class BackupManagerJGit { - - private static final Logger LOGGER = LoggerFactory.getLogger(BackupManagerJGit.class); - - private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; - - private static Set runningInstances = new HashSet(); - - private final BibDatabaseContext bibDatabaseContext; - private final CliPreferences preferences; - private final ScheduledThreadPoolExecutor executor; - private final CoarseChangeFilter changeFilter; - private final BibEntryTypesManager entryTypesManager; - private final LibraryTab libraryTab; - private final Git git; - - private boolean needsBackup = false; - - BackupManagerJGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { - this.bibDatabaseContext = bibDatabaseContext; - this.entryTypesManager = entryTypesManager; - this.preferences = preferences; - this.executor = new ScheduledThreadPoolExecutor(2); - this.libraryTab = libraryTab; - - changeFilter = new CoarseChangeFilter(bibDatabaseContext); - changeFilter.registerListener(this); - - // Initialize Git repository - FileRepositoryBuilder builder = new FileRepositoryBuilder(); - git = new Git(builder.setGitDir(new File(preferences.getFilePreferences().getBackupDirectory().toFile(), ".git")) - .readEnvironment() - .findGitDir() - .build()); - if (git.getRepository().getObjectDatabase().exists()) { - LOGGER.info("Git repository already exists"); - } else { - git.init().call(); - LOGGER.info("Initialized new Git repository"); - } - } - - public BackupManagerJGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { - BackupManagerJGit backupManagerJGit = new BackupManagerJGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - backupManagerJGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); - runningInstances.add(backupManagerJGit); - return backupManagerJGit; - } - - public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup,Path originalPath) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup , originalPath)); - runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); - } - - @SuppressWarnings({"checkstyle:WhitespaceAfter", "checkstyle:LeftCurly"}) - private void startBackupTask(Path backupDir, Path originalPath) - { - executor.scheduleAtFixedRate( - () -> { - try { - performBackup(backupDir, originalPath); - } catch ( - IOException | - GitAPIException e) { - LOGGER.error("Error during backup", e); - } - }, - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - TimeUnit.SECONDS); - } - - private void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { - /* - needsBackup must be initialized - */ - needsBackup = BackupManagerJGit.backupGitDiffers(backupDir, originalPath); - if (!needsBackup) { - return; - } - - // Add and commit changes - git.add().addFilepattern(".").call(); - RevCommit commit = git.commit().setMessage("Backup at " + System.currentTimeMillis()).call(); - LOGGER.info("Committed backup: {}", commit.getId()); - - // Reset the backup flag - this.needsBackup = false; - } - - @SuppressWarnings("checkstyle:TodoComment") - public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { - try { - Git git = Git.open(backupDir.toFile()); - - git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); - // Add commits to staging Area - git.add().addFilepattern(".").call(); - - // Commit with a message - git.commit().setMessage("Restored content from commit: " + objectId.getName()).call(); - - LOGGER.info("Restored backup from Git repository and committed the changes"); - } catch ( - IOException | - GitAPIException e) { - LOGGER.error("Error while restoring the backup", e); - } - } - - public static void restoreBackupj(Path originalPath, Path backupDir, ObjectId objectId) { - try { - - Git git = Git.open(backupDir.toFile()); - git.checkout().setName(objectId.getName()).call(); - /* - faut une methode pour evite le branch nouveau - need a method to avoid the new branch - */ - LOGGER.info("Restored backup from Git repository"); - } catch ( - IOException | - GitAPIException e) { - LOGGER.error("Error while restoring the backup", e); - } - } - /* - compare what is in originalPath and last commit - */ - - public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws IOException, GitAPIException { - - File repoDir = backupDir.toFile(); - Repository repository = new FileRepositoryBuilder() - .setGitDir(new File(repoDir, ".git")) - .build(); - try (Git git = new Git(repository)) { - ObjectId headCommitId = repository.resolve("HEAD"); // to get the latest commit id - if (headCommitId == null) { - // No commits in the repository, so there's no previous backup - return false; - } - git.add().addFilepattern(originalPath.getFileName().toString()).call(); - String relativePath = backupDir.relativize(originalPath).toString(); - List diffs = git.diff() - .setPathFilter(PathFilter.create(relativePath)) // Utiliser PathFilter ici - .call(); - return !diffs.isEmpty(); - } - } - - public List showDiffers(Path originalPath, Path backupDir, String CommitId) throws IOException, GitAPIException { - - File repoDir = backupDir.toFile(); - Repository repository = new FileRepositoryBuilder() - .setGitDir(new File(repoDir, ".git")) - .build(); - /* - need a class to show the last ten backups indicating: date/ size/ number of entries - */ - - ObjectId oldCommit = repository.resolve(CommitId); - ObjectId newCommit = repository.resolve("HEAD"); - - FileOutputStream fos = new FileOutputStream(FileDescriptor.out); - DiffFormatter diffFr = new DiffFormatter(fos); - diffFr.setRepository(repository); - return diffFr.scan(oldCommit, newCommit); - } - - // n is a counter incrementing by 1 when the user asks to see older versions (packs of 10) -// and decrements by 1 when the user asks to see the pack of the 10 earlier versions -// the scroll down: n->n+1 ; the scroll up: n->n-1 - public List retreiveCommits(Path backupDir, int n) throws IOException, GitAPIException { - List retrievedCommits = new ArrayList<>(); - // Open Git depository - try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { - // Use RevWalk to go through all commits - try (RevWalk revWalk = new RevWalk(repository)) { - // Start from HEAD - RevCommit startCommit = revWalk.parseCommit(repository.resolve("HEAD")); - revWalk.markStart(startCommit); - - int count = 0; - int startIndex = n * 10; - int endIndex = startIndex + 9; - - for (RevCommit commit : revWalk) { - // Ignore commits before starting index - if (count < startIndex) { - count++; - continue; - } - if (count >= endIndex) { - break; - } - // Add commits to the main list - retrievedCommits.add(commit); - count++; - } - } - } - - return retrievedCommits; - } - - public List> retrieveCommitDetails(List commits, Path backupDir) throws IOException, GitAPIException { - List> commitDetails; - try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { - commitDetails = new ArrayList<>(); - - // Browse the list of commits given as a parameter - for (RevCommit commit : commits) { - // A list to stock details about the commit - List commitInfo = new ArrayList<>(); - commitInfo.add(commit.getName()); // ID of commit - - // Get the size of files changes by the commit - try (TreeWalk treeWalk = new TreeWalk(repository)) { - treeWalk.addTree(commit.getTree()); - treeWalk.setRecursive(true); - long totalSize = 0; - - while (treeWalk.next()) { - ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); - totalSize += loader.getSize(); // size in bytes - } - - // Convert the size to Kb or Mb - String sizeFormatted = (totalSize > 1024 * 1024) - ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) - : String.format("%.2f Ko", totalSize / 1024.0); - - commitInfo.add(sizeFormatted); // Add Formatted size - } - - // adding date detail - Date date= commit.getAuthorIdent().getWhen(); - commitInfo.add(date.toString()); - // Add list of details to the main list - commitDetails.add(commitInfo); - } - } - - return commitDetails; - } - private void shutdownJGit(Path backupDir, boolean createBackup, Path originalPath) { - changeFilter.unregisterListener(this); - changeFilter.shutdown(); - executor.shutdown(); - - if (createBackup) { - try { - performBackup(backupDir,originalPath); - } catch (IOException | GitAPIException e) { - LOGGER.error("Error during shutdown backup", e); - } - } - } -} - - - - - - diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java index a45d7cbb9c1..653e6f505ca 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java @@ -181,7 +181,7 @@ void shouldCreateABackup(@TempDir Path customDir) throws Exception { fullBackupPath.ifPresent(manager::performBackup); manager.listen(new GroupUpdatedEvent(new MetaData())); - BackupManager.shutdown(database, backupDir, true); + BackupManager.shutdown(database, backupDir, true, bibPath); List files = Files.list(backupDir).sorted().toList(); // we only know the first backup path because the second one is created on shutdown From d6df47a50160d9d9304424a963a280eafc955a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Tue, 26 Nov 2024 22:39:38 +0100 Subject: [PATCH 22/84] Corrected errors related to BackupManagerGit class --- .../gui/autosaveandbackup/BackupManager.java | 504 +++++++++++------- .../autosaveandbackup/BackupManagerGit.java | 370 +++++++++++++ .../autosaveandbackup/BackupManagerOld.java | 398 -------------- .../jabref/gui/dialogs/BackupUIManager.java | 5 +- .../gui/exporter/SaveDatabaseAction.java | 6 +- .../logic/exporter/SaveConfiguration.java | 4 +- .../org/jabref/logic/util/BackupFileType.java | 2 +- .../jabref/logic/util/io/BackupFileUtil.java | 4 +- .../autosaveandbackup/BackupManagerTest.java | 2 +- 9 files changed, 691 insertions(+), 604 deletions(-) create mode 100644 src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java delete mode 100644 src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerOld.java diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java index 32b0729e9d1..8e468418908 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java @@ -1,46 +1,66 @@ package org.jabref.gui.autosaveandbackup; -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileOutputStream; import java.io.IOException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Date; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; import java.util.HashSet; import java.util.List; +import java.util.Optional; +import java.util.Queue; import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import javafx.scene.control.TableColumn; + import org.jabref.gui.LibraryTab; +import org.jabref.gui.maintable.BibEntryTableViewModel; +import org.jabref.gui.maintable.columns.MainTableColumn; +import org.jabref.logic.bibtex.InvalidFieldValueException; +import org.jabref.logic.exporter.AtomicFileWriter; +import org.jabref.logic.exporter.BibWriter; +import org.jabref.logic.exporter.BibtexDatabaseWriter; +import org.jabref.logic.exporter.SelfContainedSaveConfiguration; import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.util.BackupFileType; import org.jabref.logic.util.CoarseChangeFilter; +import org.jabref.logic.util.io.BackupFileUtil; +import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.event.BibDatabaseContextChangedEvent; +import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.metadata.SaveOrder; +import org.jabref.model.metadata.SelfContainedSaveOrder; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.diff.DiffEntry; -import org.eclipse.jgit.diff.DiffFormatter; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.storage.file.FileRepositoryBuilder; -import org.eclipse.jgit.treewalk.TreeWalk; -import org.eclipse.jgit.treewalk.filter.PathFilter; +import com.google.common.eventbus.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Backups the given bib database file from {@link BibDatabaseContext} on every {@link BibDatabaseContextChangedEvent}. + * An intelligent {@link ExecutorService} with a {@link BlockingQueue} prevents a high load while making backups and + * rejects all redundant backup tasks. This class does not manage the .bak file which is created when opening a + * database. + */ + public class BackupManager { private static final Logger LOGGER = LoggerFactory.getLogger(BackupManager.class); + private static final int MAXIMUM_BACKUP_FILE_COUNT = 10; + private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; - private static Set runningInstances = new HashSet(); + private static Set runningInstances = new HashSet<>(); private final BibDatabaseContext bibDatabaseContext; private final CliPreferences preferences; @@ -48,11 +68,13 @@ public class BackupManager { private final CoarseChangeFilter changeFilter; private final BibEntryTypesManager entryTypesManager; private final LibraryTab libraryTab; - private final Git git; + // Contains a list of all backup paths + // During writing, the less recent backup file is deleted + private final Queue backupFilesQueue = new LinkedBlockingQueue<>(); private boolean needsBackup = false; - BackupManager(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { + BackupManager(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { this.bibDatabaseContext = bibDatabaseContext; this.entryTypesManager = entryTypesManager; this.preferences = preferences; @@ -61,224 +83,316 @@ public class BackupManager { changeFilter = new CoarseChangeFilter(bibDatabaseContext); changeFilter.registerListener(this); + } - // Initialize Git repository - FileRepositoryBuilder builder = new FileRepositoryBuilder(); - git = new Git(builder.setGitDir(new File(preferences.getFilePreferences().getBackupDirectory().toFile(), ".git")) - .readEnvironment() - .findGitDir() - .build()); - if (git.getRepository().getObjectDatabase().exists()) { - LOGGER.info("Git repository already exists"); - } else { - git.init().call(); - LOGGER.info("Initialized new Git repository"); - } + /** + * Determines the most recent backup file name + */ + static Path getBackupPathForNewBackup(Path originalPath, Path backupDir) { + return BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(originalPath, BackupFileType.BACKUP, backupDir); } - public BackupManager start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { + /** + * Determines the most recent existing backup file name + */ + static Optional getLatestBackupPath(Path originalPath, Path backupDir) { + return BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); + } + + /** + * Starts the BackupManager which is associated with the given {@link BibDatabaseContext}. As long as no database + * file is present in {@link BibDatabaseContext}, the {@link BackupManager} will do nothing. + * + * This method is not thread-safe. The caller has to ensure that this method is not called in parallel. + * + * @param bibDatabaseContext Associated {@link BibDatabaseContext} + */ + public static BackupManager start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { BackupManager BackupManager = new BackupManager(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - BackupManager.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); + BackupManager.startBackupTask(preferences.getFilePreferences().getBackupDirectory()); runningInstances.add(BackupManager); return BackupManager; } - @SuppressWarnings({"checkstyle:NoWhitespaceBefore", "checkstyle:WhitespaceAfter"}) - public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup, Path originalPath) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup, originalPath)); + /** + * Marks the backup as discarded at the library which is associated with the given {@link BibDatabaseContext}. + * + * @param bibDatabaseContext Associated {@link BibDatabaseContext} + */ + public static void discardBackup(BibDatabaseContext bibDatabaseContext, Path backupDir) { + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(BackupManager -> BackupManager.discardBackup(backupDir)); + } + + /** + * Shuts down the BackupManager which is associated with the given {@link BibDatabaseContext}. + * + * @param bibDatabaseContext Associated {@link BibDatabaseContext} + * @param createBackup True, if a backup should be created + * @param backupDir The path to the backup directory + */ + public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(BackupManager -> BackupManager.shutdown(backupDir, createBackup)); runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); } - @SuppressWarnings({"checkstyle:WhitespaceAfter", "checkstyle:LeftCurly"}) - private void startBackupTask(Path backupDir, Path originalPath) { - executor.scheduleAtFixedRate( - () -> { - try { - performBackup(backupDir, originalPath); - } catch ( - IOException | - GitAPIException e) { - LOGGER.error("Error during backup", e); - } - }, - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - TimeUnit.SECONDS); + /** + * Checks whether a backup file exists for the given database file. If it exists, it is checked whether it is + * newer and different from the original. + * + * In case a discarded file is present, the method also returns false, See also {@link #discardBackup(Path)}. + * + * @param originalPath Path to the file a backup should be checked for. Example: jabref.bib. + * + * @return true if backup file exists AND differs from originalPath. false is the + * "default" return value in the good case. In case a discarded file exists, false is returned, too. + * In the case of an exception true is returned to ensure that the user checks the output. + */ + public static boolean backupFileDiffers(Path originalPath, Path backupDir) { + Path discardedFile = determineDiscardedFile(originalPath, backupDir); + if (Files.exists(discardedFile)) { + try { + Files.delete(discardedFile); + } catch (IOException e) { + LOGGER.error("Could not remove discarded file {}", discardedFile, e); + return true; + } + return false; + } + return getLatestBackupPath(originalPath, backupDir).map(latestBackupPath -> { + FileTime latestBackupFileLastModifiedTime; + try { + latestBackupFileLastModifiedTime = Files.getLastModifiedTime(latestBackupPath); + } catch (IOException e) { + LOGGER.debug("Could not get timestamp of backup file {}", latestBackupPath, e); + // If we cannot get the timestamp, we do show any warning + return false; + } + FileTime currentFileLastModifiedTime; + try { + currentFileLastModifiedTime = Files.getLastModifiedTime(originalPath); + } catch (IOException e) { + LOGGER.debug("Could not get timestamp of current file file {}", originalPath, e); + // If we cannot get the timestamp, we do show any warning + return false; + } + if (latestBackupFileLastModifiedTime.compareTo(currentFileLastModifiedTime) <= 0) { + // Backup is older than current file + // We treat the backup as non-different (even if it could differ) + return false; + } + try { + boolean result = Files.mismatch(originalPath, latestBackupPath) != -1L; + if (result) { + LOGGER.info("Backup file {} differs from current file {}", latestBackupPath, originalPath); + } + return result; + } catch (IOException e) { + LOGGER.debug("Could not compare original file and backup file.", e); + // User has to investigate in this case + return true; + } + }).orElse(false); } - private void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { - /* - needsBackup must be initialized - */ - needsBackup = BackupManager.backupGitDiffers(backupDir, originalPath); - if (!needsBackup) { + /** + * Restores the backup file by copying and overwriting the original one. + * + * @param originalPath Path to the file which should be equalized to the backup file. + */ + public static void restoreBackup(Path originalPath, Path backupDir) { + Optional backupPath = getLatestBackupPath(originalPath, backupDir); + if (backupPath.isEmpty()) { + LOGGER.error("There is no backup file"); return; } - - // Add and commit changes - git.add().addFilepattern(".").call(); - RevCommit commit = git.commit().setMessage("Backup at " + System.currentTimeMillis()).call(); - LOGGER.info("Committed backup: {}", commit.getId()); - - // Reset the backup flag - this.needsBackup = false; + try { + Files.copy(backupPath.get(), originalPath, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + LOGGER.error("Error while restoring the backup file.", e); + } } - @SuppressWarnings("checkstyle:TodoComment") - public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { - try { - Git git = Git.open(backupDir.toFile()); + Optional determineBackupPathForNewBackup(Path backupDir) { + return bibDatabaseContext.getDatabasePath().map(path -> BackupManager.getBackupPathForNewBackup(path, backupDir)); + } - git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); - // Add commits to staging Area - git.add().addFilepattern(".").call(); + /** + * This method is called as soon as the scheduler says: "Do the backup" + * + * SIDE EFFECT: Deletes oldest backup file + * + * @param backupPath the full path to the file where the library should be backed up to + */ + void performBackup(Path backupPath) { + if (!needsBackup) { + return; + } - // Commit with a message - git.commit().setMessage("Restored content from commit: " + objectId.getName()).call(); + // We opted for "while" to delete backups in case there are more than 10 + while (backupFilesQueue.size() >= MAXIMUM_BACKUP_FILE_COUNT) { + Path oldestBackupFile = backupFilesQueue.poll(); + try { + Files.delete(oldestBackupFile); + } catch (IOException e) { + LOGGER.error("Could not delete backup file {}", oldestBackupFile, e); + } + } - LOGGER.info("Restored backup from Git repository and committed the changes"); - } catch ( - IOException | - GitAPIException e) { - LOGGER.error("Error while restoring the backup", e); + // l'ordre dans lequel les entrées BibTeX doivent être écrites dans le fichier de sauvegarde. + // Si l'utilisateur a trié la table d'affichage des entrées dans JabRef, cet ordre est récupéré. + // Sinon, un ordre par défaut est utilisé. + // code similar to org.jabref.gui.exporter.SaveDatabaseAction.saveDatabase + SelfContainedSaveOrder saveOrder = bibDatabaseContext + .getMetaData().getSaveOrder() + .map(so -> { + if (so.getOrderType() == SaveOrder.OrderType.TABLE) { + // We need to "flatten out" SaveOrder.OrderType.TABLE as BibWriter does not have access to preferences + List> sortOrder = libraryTab.getMainTable().getSortOrder(); + return new SelfContainedSaveOrder( + SaveOrder.OrderType.SPECIFIED, + sortOrder.stream() + .filter(col -> col instanceof MainTableColumn) + .map(column -> ((MainTableColumn) column).getModel()) + .flatMap(model -> model.getSortCriteria().stream()) + .toList()); + } else { + return SelfContainedSaveOrder.of(so); + } + }) + .orElse(SaveOrder.getDefaultSaveOrder()); + + // Elle configure la sauvegarde, en indiquant qu'aucune sauvegarde supplémentaire (backup) ne doit être créée, + // que l'ordre de sauvegarde doit être celui défini, et que les entrées doivent être formatées selon les préférences + // utilisateur. + SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() + .withMakeBackup(false) + .withSaveOrder(saveOrder) + .withReformatOnSave(preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); + + // "Clone" the database context + // We "know" that "only" the BibEntries might be changed during writing (see [org.jabref.logic.exporter.BibDatabaseWriter.savePartOfDatabase]) + // Chaque entrée BibTeX (comme un article, livre, etc.) est clonée en utilisant la méthode clone(). + // Cela garantit que les modifications faites pendant la sauvegarde n'affecteront pas l'entrée originale. + List list = bibDatabaseContext.getDatabase().getEntries().stream() + .map(BibEntry::clone) + .map(BibEntry.class::cast) + .toList(); + BibDatabase bibDatabaseClone = new BibDatabase(list); + BibDatabaseContext bibDatabaseContextClone = new BibDatabaseContext(bibDatabaseClone, bibDatabaseContext.getMetaData()); + // Elle définit l'encodage à utiliser pour écrire le fichier. Cela garantit que les caractères spéciaux sont bien sauvegardés. + Charset encoding = bibDatabaseContext.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8); + // We want to have successful backups only + // Thus, we do not use a plain "FileWriter", but the "AtomicFileWriter" + // Example: What happens if one hard powers off the machine (or kills the jabref process) during writing of the backup? + // This MUST NOT create a broken backup file that then jabref wants to "restore" from? + try (Writer writer = new AtomicFileWriter(backupPath, encoding, false)) { + BibWriter bibWriter = new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator()); + new BibtexDatabaseWriter( + bibWriter, + saveConfiguration, + preferences.getFieldPreferences(), + preferences.getCitationKeyPatternPreferences(), + entryTypesManager) + // we save the clone to prevent the original database (and thus the UI) from being changed + .saveDatabase(bibDatabaseContextClone); + backupFilesQueue.add(backupPath); + + // We wrote the file successfully + // Thus, we currently do not need any new backup + this.needsBackup = false; + } catch (IOException e) { + logIfCritical(backupPath, e); } } - public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws IOException, GitAPIException { + private static Path determineDiscardedFile(Path file, Path backupDir) { + return backupDir.resolve(BackupFileUtil.getUniqueFilePrefix(file) + "--" + file.getFileName() + "--discarded"); + } - File repoDir = backupDir.toFile(); - Repository repository = new FileRepositoryBuilder() - .setGitDir(new File(repoDir, ".git")) - .build(); - try (Git git = new Git(repository)) { - ObjectId headCommitId = repository.resolve("HEAD"); // to get the latest commit id - if (headCommitId == null) { - // No commits in the repository, so there's no previous backup - return false; - } - git.add().addFilepattern(originalPath.getFileName().toString()).call(); - String relativePath = backupDir.relativize(originalPath).toString(); - List diffs = git.diff() - .setPathFilter(PathFilter.create(relativePath)) // Utiliser PathFilter ici - .call(); - return !diffs.isEmpty(); + /** + * Marks the backups as discarded. + * + * We do not delete any files, because the user might want to recover old backup files. + * Therefore, we mark discarded backups by a --discarded file. + */ + public void discardBackup(Path backupDir) { + Path path = determineDiscardedFile(bibDatabaseContext.getDatabasePath().get(), backupDir); + try { + Files.createFile(path); + } catch (IOException e) { + LOGGER.info("Could not create backup file {}", path, e); } } - public List showDiffers(Path originalPath, Path backupDir, String CommitId) throws IOException, GitAPIException { - - File repoDir = backupDir.toFile(); - Repository repository = new FileRepositoryBuilder() - .setGitDir(new File(repoDir, ".git")) - .build(); - /* - need a class to show the last ten backups indicating: date/ size/ number of entries - */ - - ObjectId oldCommit = repository.resolve(CommitId); - ObjectId newCommit = repository.resolve("HEAD"); + private void logIfCritical(Path backupPath, IOException e) { + Throwable innermostCause = e; + while (innermostCause.getCause() != null) { + innermostCause = innermostCause.getCause(); + } + boolean isErrorInField = innermostCause instanceof InvalidFieldValueException; - FileOutputStream fos = new FileOutputStream(FileDescriptor.out); - DiffFormatter diffFr = new DiffFormatter(fos); - diffFr.setRepository(repository); - return diffFr.scan(oldCommit, newCommit); + // do not print errors in field values into the log during autosave + if (!isErrorInField) { + LOGGER.error("Error while saving to file {}", backupPath, e); + } } - // n is a counter incrementing by 1 when the user asks to see older versions (packs of 10) -// and decrements by 1 when the user asks to see the pack of the 10 earlier versions -// the scroll down: n->n+1 ; the scroll up: n->n-1 - public List retreiveCommits(Path backupDir, int n) throws IOException, GitAPIException { - List retrievedCommits = new ArrayList<>(); - // Open Git depository - try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { - // Use RevWalk to go through all commits - try (RevWalk revWalk = new RevWalk(repository)) { - // Start from HEAD - RevCommit startCommit = revWalk.parseCommit(repository.resolve("HEAD")); - revWalk.markStart(startCommit); - - int count = 0; - int startIndex = n * 10; - int endIndex = startIndex + 9; - - for (RevCommit commit : revWalk) { - // Ignore commits before starting index - if (count < startIndex) { - count++; - continue; - } - if (count >= endIndex) { - break; - } - // Add commits to the main list - retrievedCommits.add(commit); - count++; - } - } + @Subscribe + public synchronized void listen(@SuppressWarnings("unused") BibDatabaseContextChangedEvent event) { + if (!event.isFilteredOut()) { + this.needsBackup = true; } - - return retrievedCommits; } - @SuppressWarnings("checkstyle:WhitespaceAround") - public List> retrieveCommitDetails(List commits, Path backupDir) throws IOException, GitAPIException { - List> commitDetails; - try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { - commitDetails = new ArrayList<>(); - - // Browse the list of commits given as a parameter - for (RevCommit commit : commits) { - // A list to stock details about the commit - List commitInfo = new ArrayList<>(); - commitInfo.add(commit.getName()); // ID of commit - - // Get the size of files changes by the commit - try (TreeWalk treeWalk = new TreeWalk(repository)) { - treeWalk.addTree(commit.getTree()); - treeWalk.setRecursive(true); - long totalSize = 0; - - while (treeWalk.next()) { - ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); - totalSize += loader.getSize(); // size in bytes - } - - // Convert the size to Kb or Mb - String sizeFormatted = (totalSize > 1024 * 1024) - ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) - : String.format("%.2f Ko", totalSize / 1024.0); + private void startBackupTask(Path backupDir) { + fillQueue(backupDir); + // remplie backupFilesQueue les files .sav de le meme bibl - commitInfo.add(sizeFormatted); // Add Formatted size - } + executor.scheduleAtFixedRate( + // We need to determine the backup path on each action, because we use the timestamp in the filename + () -> determineBackupPathForNewBackup(backupDir).ifPresent(path -> this.performBackup(path)), + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, + TimeUnit.SECONDS); + } + // La méthode fillQueue(backupDir) est définie dans le code et son rôle est de lister et d'ajouter +// les fichiers de sauvegarde existants dans une file d'attente, - // adding date detail - Date date = commit.getAuthorIdent().getWhen(); - commitInfo.add(date.toString()); - // Add list of details to the main list - commitDetails.add(commitInfo); - } + private void fillQueue(Path backupDir) { + if (!Files.exists(backupDir)) { + return; } - - return commitDetails; + bibDatabaseContext.getDatabasePath().ifPresent(databasePath -> { + // code similar to {@link org.jabref.logic.util.io.BackupFileUtil.getPathOfLatestExisingBackupFile} + final String prefix = BackupFileUtil.getUniqueFilePrefix(databasePath) + "--" + databasePath.getFileName(); + try { + List allSavFiles = Files.list(backupDir) + // just list the .sav belonging to the given targetFile + .filter(p -> p.getFileName().toString().startsWith(prefix)) + // tous les files .sav commencerait par ce prefix + .sorted().toList(); + backupFilesQueue.addAll(allSavFiles); + } catch (IOException e) { + LOGGER.error("Could not determine most recent file", e); + } + }); } - private void shutdownJGit(Path backupDir, boolean createBackup, Path originalPath) { + /** + * Unregisters the BackupManager from the eventBus of {@link BibDatabaseContext}. + * This method should only be used when closing a database/JabRef in a normal way. + * + * @param backupDir The backup directory + * @param createBackup If the backup manager should still perform a backup + */ + private void shutdown(Path backupDir, boolean createBackup) { changeFilter.unregisterListener(this); changeFilter.shutdown(); executor.shutdown(); if (createBackup) { - try { - performBackup(backupDir, originalPath); - } catch (IOException | GitAPIException e) { - LOGGER.error("Error during shutdown backup", e); - } + // Ensure that backup is a recent one + determineBackupPathForNewBackup(backupDir).ifPresent(this::performBackup); } } } - - - - - - diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java new file mode 100644 index 00000000000..dae2ed05250 --- /dev/null +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -0,0 +1,370 @@ +package org.jabref.gui.autosaveandbackup; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.jabref.gui.LibraryTab; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.util.CoarseChangeFilter; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntryTypesManager; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BackupManagerGit { + + private static final Logger LOGGER = LoggerFactory.getLogger(BackupManagerGit.class); + + private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; + + private static Set runningInstances = new HashSet(); + + private final BibDatabaseContext bibDatabaseContext; + private final CliPreferences preferences; + private final ScheduledThreadPoolExecutor executor; + private final CoarseChangeFilter changeFilter; + private final BibEntryTypesManager entryTypesManager; + private final LibraryTab libraryTab; + private final Git git; + + private boolean needsBackup = false; + + BackupManagerGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { + this.bibDatabaseContext = bibDatabaseContext; + this.entryTypesManager = entryTypesManager; + this.preferences = preferences; + this.executor = new ScheduledThreadPoolExecutor(2); + this.libraryTab = libraryTab; + + changeFilter = new CoarseChangeFilter(bibDatabaseContext); + changeFilter.registerListener(this); + + // Initialize Git repository + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + git = new Git(builder.setGitDir(new File(preferences.getFilePreferences().getBackupDirectory().toFile(), ".git")) + .readEnvironment() + .findGitDir() + .build()); + if (git.getRepository().getObjectDatabase().exists()) { + LOGGER.info("Git repository already exists"); + } else { + git.init().call(); + LOGGER.info("Initialized new Git repository"); + } + } + + /** + * Starts a new BackupManagerGit instance and begins the backup task. + * + * @param libraryTab the library tab + * @param bibDatabaseContext the BibDatabaseContext to be backed up + * @param entryTypesManager the BibEntryTypesManager + * @param preferences the CLI preferences + * @param originalPath the original path of the file to be backed up + * @return the started BackupManagerGit instance + * @throws IOException if an I/O error occurs + * @throws GitAPIException if a Git API error occurs + */ + + public BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { + BackupManagerGit BackupManager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + BackupManager.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); + runningInstances.add(BackupManager); + return BackupManager; + } + + /** + * Shuts down the BackupManagerGit instances associated with the given BibDatabaseContext. + * + * @param bibDatabaseContext the BibDatabaseContext + * @param backupDir the backup directory + * @param createBackup whether to create a backup before shutting down + * @param originalPath the original path of the file to be backed up + */ + + @SuppressWarnings({"checkstyle:NoWhitespaceBefore", "checkstyle:WhitespaceAfter"}) + public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup, Path originalPath) { + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup, originalPath)); + runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); + } + + /** + * Starts the backup task that periodically checks for changes and commits them to the Git repository. + * + * @param backupDir the backup directory + * @param originalPath the original path of the file to be backed up + */ + + @SuppressWarnings({"checkstyle:WhitespaceAfter", "checkstyle:LeftCurly"}) + private void startBackupTask(Path backupDir, Path originalPath) { + executor.scheduleAtFixedRate( + () -> { + try { + performBackup(backupDir, originalPath); + } catch ( + IOException | + GitAPIException e) { + LOGGER.error("Error during backup", e); + } + }, + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, + DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, + TimeUnit.SECONDS); + } + + /** + * Performs the backup by checking for changes and committing them to the Git repository. + * + * @param backupDir the backup directory + * @param originalPath the original path of the file to be backed up + * @throws IOException if an I/O error occurs + * @throws GitAPIException if a Git API error occurs + */ + + private void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { + /* + needsBackup must be initialized + */ + needsBackup = BackupManagerGit.backupGitDiffers(backupDir, originalPath); + if (!needsBackup) { + return; + } + + // Add and commit changes + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("Backup at " + System.currentTimeMillis()).call(); + LOGGER.info("Committed backup: {}", commit.getId()); + + // Reset the backup flag + this.needsBackup = false; + } + + /** + * Restores the backup from the specified commit. + * + * @param originalPath the original path of the file to be restored + * @param backupDir the backup directory + * @param objectId the commit ID to restore from + */ + + @SuppressWarnings("checkstyle:TodoComment") + public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { + try { + Git git = Git.open(backupDir.toFile()); + + git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); + // Add commits to staging Area + git.add().addFilepattern(".").call(); + + // Commit with a message + git.commit().setMessage("Restored content from commit: " + objectId.getName()).call(); + + LOGGER.info("Restored backup from Git repository and committed the changes"); + } catch ( + IOException | + GitAPIException e) { + LOGGER.error("Error while restoring the backup", e); + } + } + + /** + * Checks if there are differences between the original file and the backup. + * + * @param originalPath the original path of the file + * @param backupDir the backup directory + * @return true if there are differences, false otherwise + * @throws IOException if an I/O error occurs + * @throws GitAPIException if a Git API error occurs + */ + + public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws IOException, GitAPIException { + + File repoDir = backupDir.toFile(); + Repository repository = new FileRepositoryBuilder() + .setGitDir(new File(repoDir, ".git")) + .build(); + try (Git git = new Git(repository)) { + ObjectId headCommitId = repository.resolve("HEAD"); // to get the latest commit id + if (headCommitId == null) { + // No commits in the repository, so there's no previous backup + return false; + } + git.add().addFilepattern(originalPath.getFileName().toString()).call(); + String relativePath = backupDir.relativize(originalPath).toString(); + List diffs = git.diff() + .setPathFilter(PathFilter.create(relativePath)) // Utiliser PathFilter ici + .call(); + return !diffs.isEmpty(); + } + } + + /** + * Shows the differences between the specified commit and the latest commit. + * + * @param originalPath the original path of the file + * @param backupDir the backup directory + * @param commitId the commit ID to compare with the latest commit + * @return a list of DiffEntry objects representing the differences + * @throws IOException if an I/O error occurs + * @throws GitAPIException if a Git API error occurs + */ + + public List showDiffers(Path originalPath, Path backupDir, String commitId) throws IOException, GitAPIException { + + File repoDir = backupDir.toFile(); + Repository repository = new FileRepositoryBuilder() + .setGitDir(new File(repoDir, ".git")) + .build(); + /* + need a class to show the last ten backups indicating: date/ size/ number of entries + */ + + ObjectId oldCommit = repository.resolve(commitId); + ObjectId newCommit = repository.resolve("HEAD"); + + FileOutputStream fos = new FileOutputStream(FileDescriptor.out); + DiffFormatter diffFr = new DiffFormatter(fos); + diffFr.setRepository(repository); + return diffFr.scan(oldCommit, newCommit); + } + + // n is a counter incrementing by 1 when the user asks to see older versions (packs of 10) +// and decrements by 1 when the user asks to see the pack of the 10 earlier versions +// the scroll down: n->n+1 ; the scroll up: n->n-1 + public List retreiveCommits(Path backupDir, int n) throws IOException, GitAPIException { + List retrievedCommits = new ArrayList<>(); + // Open Git depository + try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { + // Use RevWalk to go through all commits + try (RevWalk revWalk = new RevWalk(repository)) { + // Start from HEAD + RevCommit startCommit = revWalk.parseCommit(repository.resolve("HEAD")); + revWalk.markStart(startCommit); + + int count = 0; + int startIndex = n * 10; + int endIndex = startIndex + 9; + + for (RevCommit commit : revWalk) { + // Ignore commits before starting index + if (count < startIndex) { + count++; + continue; + } + if (count >= endIndex) { + break; + } + // Add commits to the main list + retrievedCommits.add(commit); + count++; + } + } + } + + return retrievedCommits; + } + + + /** + * Retrieves detailed information about the specified commits. + * + * @param commits the list of commits to retrieve details for + * @param backupDir the backup directory + * @return a list of lists, each containing details about a commit + * @throws IOException if an I/O error occurs + * @throws GitAPIException if a Git API error occurs + */ + + @SuppressWarnings("checkstyle:WhitespaceAround") + public List> retrieveCommitDetails(List commits, Path backupDir) throws IOException, GitAPIException { + List> commitDetails; + try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { + commitDetails = new ArrayList<>(); + + // Browse the list of commits given as a parameter + for (RevCommit commit : commits) { + // A list to stock details about the commit + List commitInfo = new ArrayList<>(); + commitInfo.add(commit.getName()); // ID of commit + + // Get the size of files changes by the commit + try (TreeWalk treeWalk = new TreeWalk(repository)) { + treeWalk.addTree(commit.getTree()); + treeWalk.setRecursive(true); + long totalSize = 0; + + while (treeWalk.next()) { + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + totalSize += loader.getSize(); // size in bytes + } + + // Convert the size to Kb or Mb + String sizeFormatted = (totalSize > 1024 * 1024) + ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) + : String.format("%.2f Ko", totalSize / 1024.0); + + commitInfo.add(sizeFormatted); // Add Formatted size + } + + // adding date detail + Date date = commit.getAuthorIdent().getWhen(); + commitInfo.add(date.toString()); + // Add list of details to the main list + commitDetails.add(commitInfo); + } + } + + return commitDetails; + } + + /** + * Shuts down the JGit components and optionally creates a backup. + * + * @param backupDir the backup directory + * @param createBackup whether to create a backup before shutting down + * @param originalPath the original path of the file to be backed up + */ + + private void shutdownJGit(Path backupDir, boolean createBackup, Path originalPath) { + changeFilter.unregisterListener(this); + changeFilter.shutdown(); + executor.shutdown(); + + if (createBackup) { + try { + performBackup(backupDir, originalPath); + } catch (IOException | GitAPIException e) { + LOGGER.error("Error during shutdown backup", e); + } + } + } +} + + + + + + diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerOld.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerOld.java deleted file mode 100644 index 32ef596f8e7..00000000000 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerOld.java +++ /dev/null @@ -1,398 +0,0 @@ -package org.jabref.gui.autosaveandbackup; - -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileTime; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import javafx.scene.control.TableColumn; - -import org.jabref.gui.LibraryTab; -import org.jabref.gui.maintable.BibEntryTableViewModel; -import org.jabref.gui.maintable.columns.MainTableColumn; -import org.jabref.logic.bibtex.InvalidFieldValueException; -import org.jabref.logic.exporter.AtomicFileWriter; -import org.jabref.logic.exporter.BibWriter; -import org.jabref.logic.exporter.BibtexDatabaseWriter; -import org.jabref.logic.exporter.SelfContainedSaveConfiguration; -import org.jabref.logic.preferences.CliPreferences; -import org.jabref.logic.util.BackupFileType; -import org.jabref.logic.util.CoarseChangeFilter; -import org.jabref.logic.util.io.BackupFileUtil; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.database.event.BibDatabaseContextChangedEvent; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.metadata.SaveOrder; -import org.jabref.model.metadata.SelfContainedSaveOrder; - -import com.google.common.eventbus.Subscribe; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Backups the given bib database file from {@link BibDatabaseContext} on every {@link BibDatabaseContextChangedEvent}. - * An intelligent {@link ExecutorService} with a {@link BlockingQueue} prevents a high load while making backups and - * rejects all redundant backup tasks. This class does not manage the .bak file which is created when opening a - * database. - */ -@SuppressWarnings("checkstyle:WhitespaceAround") -public class BackupManagerOld { - - private static final Logger LOGGER = LoggerFactory.getLogger(BackupManagerOld.class); - - private static final int MAXIMUM_BACKUP_FILE_COUNT = 10; - - private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; - - private static Set runningInstances = new HashSet<>(); - - private final BibDatabaseContext bibDatabaseContext; - private final CliPreferences preferences; - private final ScheduledThreadPoolExecutor executor; - private final CoarseChangeFilter changeFilter; - private final BibEntryTypesManager entryTypesManager; - private final LibraryTab libraryTab; - - // Contains a list of all backup paths - // During writing, the less recent backup file is deleted - private final Queue backupFilesQueue = new LinkedBlockingQueue<>(); - private boolean needsBackup = false; - - BackupManagerOld(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { - this.bibDatabaseContext = bibDatabaseContext; - this.entryTypesManager = entryTypesManager; - this.preferences = preferences; - this.executor = new ScheduledThreadPoolExecutor(2); - this.libraryTab = libraryTab; - - changeFilter = new CoarseChangeFilter(bibDatabaseContext); - changeFilter.registerListener(this); - } - - /** - * Determines the most recent backup file name - */ - static Path getBackupPathForNewBackup(Path originalPath, Path backupDir) { - return BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(originalPath, BackupFileType.BACKUP, backupDir); - } - - /** - * Determines the most recent existing backup file name - */ - static Optional getLatestBackupPath(Path originalPath, Path backupDir) { - return BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); - } - - /** - * Starts the BackupManagerOld which is associated with the given {@link BibDatabaseContext}. As long as no database - * file is present in {@link BibDatabaseContext}, the {@link BackupManagerOld} will do nothing. - * - * This method is not thread-safe. The caller has to ensure that this method is not called in parallel. - * - * @param bibDatabaseContext Associated {@link BibDatabaseContext} - */ - public static BackupManagerOld start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { - BackupManagerOld BackupManagerOld = new BackupManagerOld(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - BackupManagerOld.startBackupTask(preferences.getFilePreferences().getBackupDirectory()); - runningInstances.add(BackupManagerOld); - return BackupManagerOld; - } - - /** - * Marks the backup as discarded at the library which is associated with the given {@link BibDatabaseContext}. - * - * @param bibDatabaseContext Associated {@link BibDatabaseContext} - */ - public static void discardBackup(BibDatabaseContext bibDatabaseContext, Path backupDir) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(BackupManagerOld -> BackupManagerOld.discardBackup(backupDir)); - } - - /** - * Shuts down the BackupManagerOld which is associated with the given {@link BibDatabaseContext}. - * - * @param bibDatabaseContext Associated {@link BibDatabaseContext} - * @param createBackup True, if a backup should be created - * @param backupDir The path to the backup directory - */ - public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(BackupManagerOld -> BackupManagerOld.shutdown(backupDir, createBackup)); - runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); - } - - /** - * Checks whether a backup file exists for the given database file. If it exists, it is checked whether it is - * newer and different from the original. - * - * In case a discarded file is present, the method also returns false, See also {@link #discardBackup(Path)}. - * - * @param originalPath Path to the file a backup should be checked for. Example: jabref.bib. - * - * @return true if backup file exists AND differs from originalPath. false is the - * "default" return value in the good case. In case a discarded file exists, false is returned, too. - * In the case of an exception true is returned to ensure that the user checks the output. - */ - public static boolean backupFileDiffers(Path originalPath, Path backupDir) { - Path discardedFile = determineDiscardedFile(originalPath, backupDir); - if (Files.exists(discardedFile)) { - try { - Files.delete(discardedFile); - } catch (IOException e) { - LOGGER.error("Could not remove discarded file {}", discardedFile, e); - return true; - } - return false; - } - return getLatestBackupPath(originalPath, backupDir).map(latestBackupPath -> { - FileTime latestBackupFileLastModifiedTime; - try { - latestBackupFileLastModifiedTime = Files.getLastModifiedTime(latestBackupPath); - } catch (IOException e) { - LOGGER.debug("Could not get timestamp of backup file {}", latestBackupPath, e); - // If we cannot get the timestamp, we do show any warning - return false; - } - FileTime currentFileLastModifiedTime; - try { - currentFileLastModifiedTime = Files.getLastModifiedTime(originalPath); - } catch (IOException e) { - LOGGER.debug("Could not get timestamp of current file file {}", originalPath, e); - // If we cannot get the timestamp, we do show any warning - return false; - } - if (latestBackupFileLastModifiedTime.compareTo(currentFileLastModifiedTime) <= 0) { - // Backup is older than current file - // We treat the backup as non-different (even if it could differ) - return false; - } - try { - boolean result = Files.mismatch(originalPath, latestBackupPath) != -1L; - if (result) { - LOGGER.info("Backup file {} differs from current file {}", latestBackupPath, originalPath); - } - return result; - } catch (IOException e) { - LOGGER.debug("Could not compare original file and backup file.", e); - // User has to investigate in this case - return true; - } - }).orElse(false); - } - - /** - * Restores the backup file by copying and overwriting the original one. - * - * @param originalPath Path to the file which should be equalized to the backup file. - */ - public static void restoreBackup(Path originalPath, Path backupDir) { - Optional backupPath = getLatestBackupPath(originalPath, backupDir); - if (backupPath.isEmpty()) { - LOGGER.error("There is no backup file"); - return; - } - try { - Files.copy(backupPath.get(), originalPath, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - LOGGER.error("Error while restoring the backup file.", e); - } - } - - Optional determineBackupPathForNewBackup(Path backupDir) { - return bibDatabaseContext.getDatabasePath().map(path -> BackupManagerOld.getBackupPathForNewBackup(path, backupDir)); - } - - /** - * This method is called as soon as the scheduler says: "Do the backup" - * - * SIDE EFFECT: Deletes oldest backup file - * - * @param backupPath the full path to the file where the library should be backed up to - */ - void performBackup(Path backupPath) { - if (!needsBackup) { - return; - } - - // We opted for "while" to delete backups in case there are more than 10 - while (backupFilesQueue.size() >= MAXIMUM_BACKUP_FILE_COUNT) { - Path oldestBackupFile = backupFilesQueue.poll(); - try { - Files.delete(oldestBackupFile); - } catch (IOException e) { - LOGGER.error("Could not delete backup file {}", oldestBackupFile, e); - } - } - - // l'ordre dans lequel les entrées BibTeX doivent être écrites dans le fichier de sauvegarde. - // Si l'utilisateur a trié la table d'affichage des entrées dans JabRef, cet ordre est récupéré. - // Sinon, un ordre par défaut est utilisé. - // code similar to org.jabref.gui.exporter.SaveDatabaseAction.saveDatabase - SelfContainedSaveOrder saveOrder = bibDatabaseContext - .getMetaData().getSaveOrder() - .map(so -> { - if (so.getOrderType() == SaveOrder.OrderType.TABLE) { - // We need to "flatten out" SaveOrder.OrderType.TABLE as BibWriter does not have access to preferences - List> sortOrder = libraryTab.getMainTable().getSortOrder(); - return new SelfContainedSaveOrder( - SaveOrder.OrderType.SPECIFIED, - sortOrder.stream() - .filter(col -> col instanceof MainTableColumn) - .map(column -> ((MainTableColumn) column).getModel()) - .flatMap(model -> model.getSortCriteria().stream()) - .toList()); - } else { - return SelfContainedSaveOrder.of(so); - } - }) - .orElse(SaveOrder.getDefaultSaveOrder()); - - // Elle configure la sauvegarde, en indiquant qu'aucune sauvegarde supplémentaire (backup) ne doit être créée, - // que l'ordre de sauvegarde doit être celui défini, et que les entrées doivent être formatées selon les préférences - // utilisateur. - SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() - .withMakeBackup(false) - .withSaveOrder(saveOrder) - .withReformatOnSave(preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); - - // "Clone" the database context - // We "know" that "only" the BibEntries might be changed during writing (see [org.jabref.logic.exporter.BibDatabaseWriter.savePartOfDatabase]) - // Chaque entrée BibTeX (comme un article, livre, etc.) est clonée en utilisant la méthode clone(). - // Cela garantit que les modifications faites pendant la sauvegarde n'affecteront pas l'entrée originale. - List list = bibDatabaseContext.getDatabase().getEntries().stream() - .map(BibEntry::clone) - .map(BibEntry.class::cast) - .toList(); - BibDatabase bibDatabaseClone = new BibDatabase(list); - BibDatabaseContext bibDatabaseContextClone = new BibDatabaseContext(bibDatabaseClone, bibDatabaseContext.getMetaData()); - // Elle définit l'encodage à utiliser pour écrire le fichier. Cela garantit que les caractères spéciaux sont bien sauvegardés. - Charset encoding = bibDatabaseContext.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8); - // We want to have successful backups only - // Thus, we do not use a plain "FileWriter", but the "AtomicFileWriter" - // Example: What happens if one hard powers off the machine (or kills the jabref process) during writing of the backup? - // This MUST NOT create a broken backup file that then jabref wants to "restore" from? - try (Writer writer = new AtomicFileWriter(backupPath, encoding, false)) { - BibWriter bibWriter = new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator()); - new BibtexDatabaseWriter( - bibWriter, - saveConfiguration, - preferences.getFieldPreferences(), - preferences.getCitationKeyPatternPreferences(), - entryTypesManager) - // we save the clone to prevent the original database (and thus the UI) from being changed - .saveDatabase(bibDatabaseContextClone); - backupFilesQueue.add(backupPath); - - // We wrote the file successfully - // Thus, we currently do not need any new backup - this.needsBackup = false; - } catch (IOException e) { - logIfCritical(backupPath, e); - } - } - - private static Path determineDiscardedFile(Path file, Path backupDir) { - return backupDir.resolve(BackupFileUtil.getUniqueFilePrefix(file) + "--" + file.getFileName() + "--discarded"); - } - - /** - * Marks the backups as discarded. - * - * We do not delete any files, because the user might want to recover old backup files. - * Therefore, we mark discarded backups by a --discarded file. - */ - public void discardBackup(Path backupDir) { - Path path = determineDiscardedFile(bibDatabaseContext.getDatabasePath().get(), backupDir); - try { - Files.createFile(path); - } catch (IOException e) { - LOGGER.info("Could not create backup file {}", path, e); - } - } - - private void logIfCritical(Path backupPath, IOException e) { - Throwable innermostCause = e; - while (innermostCause.getCause() != null) { - innermostCause = innermostCause.getCause(); - } - boolean isErrorInField = innermostCause instanceof InvalidFieldValueException; - - // do not print errors in field values into the log during autosave - if (!isErrorInField) { - LOGGER.error("Error while saving to file {}", backupPath, e); - } - } - - @Subscribe - public synchronized void listen(@SuppressWarnings("unused") BibDatabaseContextChangedEvent event) { - if (!event.isFilteredOut()) { - this.needsBackup = true; - } - } - - private void startBackupTask(Path backupDir) { - fillQueue(backupDir); - // remplie backupFilesQueue les files .sav de le meme bibl - - executor.scheduleAtFixedRate( - // We need to determine the backup path on each action, because we use the timestamp in the filename - () -> determineBackupPathForNewBackup(backupDir).ifPresent(path -> this.performBackup(path)), - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - TimeUnit.SECONDS); - } - // La méthode fillQueue(backupDir) est définie dans le code et son rôle est de lister et d'ajouter -// les fichiers de sauvegarde existants dans une file d'attente, - - private void fillQueue(Path backupDir) { - if (!Files.exists(backupDir)) { - return; - } - bibDatabaseContext.getDatabasePath().ifPresent(databasePath -> { - // code similar to {@link org.jabref.logic.util.io.BackupFileUtil.getPathOfLatestExisingBackupFile} - final String prefix = BackupFileUtil.getUniqueFilePrefix(databasePath) + "--" + databasePath.getFileName(); - try { - List allSavFiles = Files.list(backupDir) - // just list the .sav belonging to the given targetFile - .filter(p -> p.getFileName().toString().startsWith(prefix)) - // tous les files .sav commencerait par ce prefix - .sorted().toList(); - backupFilesQueue.addAll(allSavFiles); - } catch (IOException e) { - LOGGER.error("Could not determine most recent file", e); - } - }); - } - - /** - * Unregisters the BackupManagerOld from the eventBus of {@link BibDatabaseContext}. - * This method should only be used when closing a database/JabRef in a normal way. - * - * @param backupDir The backup directory - * @param createBackup If the backup manager should still perform a backup - */ - private void shutdown(Path backupDir, boolean createBackup) { - changeFilter.unregisterListener(this); - changeFilter.shutdown(); - executor.shutdown(); - - if (createBackup) { - // Ensure that backup is a recent one - determineBackupPathForNewBackup(backupDir).ifPresent(this::performBackup); - } - } -} diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 9f5be274700..8e1f0227d45 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -13,6 +13,7 @@ import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; import org.jabref.gui.autosaveandbackup.BackupManager; +import org.jabref.gui.autosaveandbackup.BackupManagerGit; import org.jabref.gui.backup.BackupChoiceDialog; import org.jabref.gui.backup.BackupChoiceDialogRecord; import org.jabref.gui.backup.BackupResolverDialog; @@ -38,7 +39,7 @@ import org.slf4j.LoggerFactory; /** - * Stores all user dialogs related to {@link BackupManager}. + * Stores all user dialogs related to {@link BackupManagerGit}. */ public class BackupUIManager { private static final Logger LOGGER = LoggerFactory.getLogger(BackupUIManager.class); @@ -84,7 +85,7 @@ public static Optional showRestoreBackupDialog(DialogService dialo /* return actionOpt.flatMap(action -> { if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { - BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); + BackupManagerGit.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); return Optional.empty(); } else if (action == BackupResolverDialog.COMPARE_OLDER_BACKUP) { var test = showBackupChoiceDialog(dialogService, originalPath, preferences); diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 10487ead6ad..7965a2d626d 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -139,7 +139,7 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { Optional databasePath = context.getDatabasePath(); if (databasePath.isPresent()) { - // Close AutosaveManager, BackupManager, and IndexManager for original library + // Close AutosaveManager, BackupManagerGit, and IndexManager for original library AutosaveManager.shutdown(context); BackupManager.shutdown(context, this.preferences.getFilePreferences().getBackupDirectory(), preferences.getFilePreferences().shouldCreateBackup()); libraryTab.closeIndexManger(); @@ -160,7 +160,7 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { context.setDatabasePath(file); libraryTab.updateTabTitle(false); - // Reset (here: uninstall and install again) AutosaveManager, BackupManager and IndexManager for the new file name + // Reset (here: uninstall and install again) AutosaveManager, BackupManagerGit and IndexManager for the new file name libraryTab.resetChangeMonitor(); libraryTab.installAutosaveManagerAndBackupManager(); libraryTab.createIndexManager(); @@ -251,7 +251,7 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { } private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, BibDatabaseWriter.SaveType saveType, SelfContainedSaveOrder saveOrder) throws SaveException { - // if this code is adapted, please also adapt org.jabref.logic.autosaveandbackup.BackupManager.performBackup + // if this code is adapted, please also adapt org.jabref.logic.autosaveandbackup.BackupManagerGit.performBackup SelfContainedSaveConfiguration saveConfiguration = new SelfContainedSaveConfiguration(saveOrder, false, saveType, preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); BibDatabaseContext bibDatabaseContext = libraryTab.getBibDatabaseContext(); diff --git a/src/main/java/org/jabref/logic/exporter/SaveConfiguration.java b/src/main/java/org/jabref/logic/exporter/SaveConfiguration.java index 358659113e5..f92fdf8e8ef 100644 --- a/src/main/java/org/jabref/logic/exporter/SaveConfiguration.java +++ b/src/main/java/org/jabref/logic/exporter/SaveConfiguration.java @@ -1,6 +1,6 @@ package org.jabref.logic.exporter; -import org.jabref.gui.autosaveandbackup.BackupManager; +import org.jabref.gui.autosaveandbackup.BackupManagerGit; import org.jabref.model.metadata.SaveOrder; public class SaveConfiguration { @@ -44,7 +44,7 @@ public boolean shouldMakeBackup() { } /** - * Required by {@link BackupManager}. Should not be used in other settings + * Required by {@link BackupManagerGit}. Should not be used in other settings * * @param newMakeBackup whether a backup (.bak file) should be made */ diff --git a/src/main/java/org/jabref/logic/util/BackupFileType.java b/src/main/java/org/jabref/logic/util/BackupFileType.java index b24cc3faeb8..3fbf331daf4 100644 --- a/src/main/java/org/jabref/logic/util/BackupFileType.java +++ b/src/main/java/org/jabref/logic/util/BackupFileType.java @@ -5,7 +5,7 @@ public enum BackupFileType implements FileType { - // Used at BackupManager + // Used at BackupManagerGit BACKUP("Backup", "bak"), // Used when writing the .bib file. See {@link org.jabref.logic.exporter.AtomicFileWriter} diff --git a/src/main/java/org/jabref/logic/util/io/BackupFileUtil.java b/src/main/java/org/jabref/logic/util/io/BackupFileUtil.java index 8df2eb600d7..9b2e1948089 100644 --- a/src/main/java/org/jabref/logic/util/io/BackupFileUtil.java +++ b/src/main/java/org/jabref/logic/util/io/BackupFileUtil.java @@ -9,7 +9,7 @@ import java.util.HexFormat; import java.util.Optional; -import org.jabref.gui.autosaveandbackup.BackupManager; +import org.jabref.gui.autosaveandbackup.BackupManagerGit; import org.jabref.logic.util.BackupFileType; import org.slf4j.Logger; @@ -34,7 +34,7 @@ private BackupFileUtil() { * In case that fails, the return path of the .bak file is set to be next to the .bib file *

*

- * Note that this backup is different from the .sav file generated by {@link BackupManager} + * Note that this backup is different from the .sav file generated by {@link BackupManagerGit} * (and configured in the preferences as "make backups") *

*/ diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java index 653e6f505ca..a45d7cbb9c1 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java @@ -181,7 +181,7 @@ void shouldCreateABackup(@TempDir Path customDir) throws Exception { fullBackupPath.ifPresent(manager::performBackup); manager.listen(new GroupUpdatedEvent(new MetaData())); - BackupManager.shutdown(database, backupDir, true, bibPath); + BackupManager.shutdown(database, backupDir, true); List files = Files.list(backupDir).sorted().toList(); // we only know the first backup path because the second one is created on shutdown From 9a0707791e84a707824cd193092e84c4ef4989d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Tue, 26 Nov 2024 23:14:30 +0100 Subject: [PATCH 23/84] Start tests --- .../autosaveandbackup/BackupManagerGit.java | 4 +- .../BackupManagerGitTest.java | 58 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index dae2ed05250..1c980e61e8a 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -71,7 +71,7 @@ public class BackupManagerGit { if (git.getRepository().getObjectDatabase().exists()) { LOGGER.info("Git repository already exists"); } else { - git.init().call(); + Git.init().call(); LOGGER.info("Initialized new Git repository"); } } @@ -144,7 +144,7 @@ private void startBackupTask(Path backupDir, Path originalPath) { * @throws GitAPIException if a Git API error occurs */ - private void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { + protected void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { /* needsBackup must be initialized */ diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java new file mode 100644 index 00000000000..13f6dedaecc --- /dev/null +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -0,0 +1,58 @@ +package org.jabref.gui.autosaveandbackup; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.jabref.gui.LibraryTab; +import org.jabref.logic.FilePreferences; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntryTypesManager; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class BackupManagerGitTest { + + private Path backupDir; + private BackupManagerGit backupManager; + + @BeforeEach + void setup(@TempDir Path tempDir) throws IOException, GitAPIException, IOException { + backupDir = tempDir.resolve("backup"); + Files.createDirectories(backupDir); + + // Initialize BackupManagerGit with mock dependencies + var libraryTab = mock(LibraryTab.class); + var bibDatabaseContext = new BibDatabaseContext(); + var entryTypesManager = mock(BibEntryTypesManager.class); + var preferences = mock(CliPreferences.class); + var filePreferences = mock(FilePreferences.class); + + when(preferences.getFilePreferences()).thenReturn(filePreferences); + when(filePreferences.getBackupDirectory()).thenReturn(backupDir); + + backupManager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + } + + @Test + void testInitialization() { + assertNotNull(backupManager); + } +} + + + + From 9c86a64e727037b3b8b16766f756327849683f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 27 Nov 2024 12:26:58 +0100 Subject: [PATCH 24/84] OpenDatabaseAction adapted --- .../autosaveandbackup/BackupManagerGit.java | 1 - .../actions/OpenDatabaseActionGit.java | 329 ++++++++++++++++++ 2 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/jabref/gui/importer/actions/OpenDatabaseActionGit.java diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 1c980e61e8a..ffd7db9acf3 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -170,7 +170,6 @@ protected void performBackup(Path backupDir, Path originalPath) throws IOExcepti * @param objectId the commit ID to restore from */ - @SuppressWarnings("checkstyle:TodoComment") public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { try { Git git = Git.open(backupDir.toFile()); diff --git a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseActionGit.java b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseActionGit.java new file mode 100644 index 00000000000..b762cb7f81e --- /dev/null +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseActionGit.java @@ -0,0 +1,329 @@ +package org.jabref.gui.importer.actions; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import javax.swing.undo.UndoManager; + +import org.jabref.gui.ClipBoardManager; +import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; +import org.jabref.gui.LibraryTabContainer; +import org.jabref.gui.StateManager; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.autosaveandbackup.BackupManagerGit; +import org.jabref.gui.dialogs.BackupUIManager; +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.gui.shared.SharedDatabaseUIManager; +import org.jabref.gui.undo.CountingUndoManager; +import org.jabref.gui.util.FileDialogConfiguration; +import org.jabref.gui.util.UiTaskExecutor; +import org.jabref.logic.ai.AiService; +import org.jabref.logic.importer.OpenDatabase; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.shared.DatabaseNotSupportedException; +import org.jabref.logic.shared.exception.InvalidDBMSConnectionPropertiesException; +import org.jabref.logic.shared.exception.NotASharedDatabaseException; +import org.jabref.logic.util.BackgroundTask; +import org.jabref.logic.util.Directories; +import org.jabref.logic.util.StandardFileType; +import org.jabref.logic.util.TaskExecutor; +import org.jabref.logic.util.io.FileHistory; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.util.FileUpdateMonitor; + +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// The action concerned with opening an existing database. +public class OpenDatabaseActionGit extends SimpleCommand { + + private static final Logger LOGGER = LoggerFactory.getLogger(OpenDatabaseAction.class); + + // List of actions that may need to be called after opening the file. Such as + // upgrade actions etc. that may depend on the JabRef version that wrote the file: + private static final List POST_OPEN_ACTIONS = List.of( + // Migrations: + // Warning for migrating the Review into the Comment field + new MergeReviewIntoCommentAction(), + // Check for new custom entry types loaded from the BIB file: + new CheckForNewEntryTypesAction(), + // Migrate search groups fielded terms to use the new operators (RegEx, case sensitive) + new SearchGroupsMigrationAction()); + + private final LibraryTabContainer tabContainer; + private final GuiPreferences preferences; + private final AiService aiService; + private final StateManager stateManager; + private final FileUpdateMonitor fileUpdateMonitor; + private final DialogService dialogService; + private final BibEntryTypesManager entryTypesManager; + private final CountingUndoManager undoManager; + private final ClipBoardManager clipboardManager; + private final TaskExecutor taskExecutor; + + public OpenDatabaseActionGit(LibraryTabContainer tabContainer, + GuiPreferences preferences, + AiService aiService, + DialogService dialogService, + StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, + CountingUndoManager undoManager, + ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor) { + this.tabContainer = tabContainer; + this.preferences = preferences; + this.aiService = aiService; + this.dialogService = dialogService; + this.stateManager = stateManager; + this.fileUpdateMonitor = fileUpdateMonitor; + this.entryTypesManager = entryTypesManager; + this.undoManager = undoManager; + this.clipboardManager = clipBoardManager; + this.taskExecutor = taskExecutor; + } + + public static void performPostOpenActions(ParserResult result, DialogService dialogService, CliPreferences preferences) { + for (GUIPostOpenAction action : OpenDatabaseActionGit.POST_OPEN_ACTIONS) { + if (action.isActionNecessary(result, dialogService, preferences)) { + action.performAction(result, dialogService, preferences); + } + } + } + + @Override + public void execute() { + List filesToOpen = getFilesToOpen(); + openFiles(new ArrayList<>(filesToOpen)); + } + + @VisibleForTesting + List getFilesToOpen() { + List filesToOpen; + + try { + FileDialogConfiguration initialDirectoryConfig = getFileDialogConfiguration(getInitialDirectory()); + filesToOpen = dialogService.showFileOpenDialogAndGetMultipleFiles(initialDirectoryConfig); + } catch (IllegalArgumentException e) { + // See https://github.com/JabRef/jabref/issues/10548 for details + // Rebuild a new config with the home directory + FileDialogConfiguration homeDirectoryConfig = getFileDialogConfiguration(Directories.getUserDirectory()); + filesToOpen = dialogService.showFileOpenDialogAndGetMultipleFiles(homeDirectoryConfig); + } + + return filesToOpen; + } + + /** + * Builds a new FileDialogConfiguration using the given path as the initial directory for use in + * dialogService.showFileOpenDialogAndGetMultipleFiles(). + * + * @param initialDirectory Path to use as the initial directory + * @return new FileDialogConfig with given initial directory + */ + public FileDialogConfiguration getFileDialogConfiguration(Path initialDirectory) { + return new FileDialogConfiguration.Builder() + .addExtensionFilter(StandardFileType.BIBTEX_DB) + .withDefaultExtension(StandardFileType.BIBTEX_DB) + .withInitialDirectory(initialDirectory) + .build(); + } + + /** + * @return Path of current panel database directory or the working directory + */ + @VisibleForTesting + Path getInitialDirectory() { + if (tabContainer.getLibraryTabs().isEmpty()) { + return preferences.getFilePreferences().getWorkingDirectory(); + } else { + Optional databasePath = tabContainer.getCurrentLibraryTab().getBibDatabaseContext().getDatabasePath(); + return databasePath.map(Path::getParent).orElse(preferences.getFilePreferences().getWorkingDirectory()); + } + } + + /** + * Opens the given file. If null or 404, nothing happens. + * In case the file is already opened, that panel is raised. + * + * @param file the file, may be null or not existing + */ + public void openFile(Path file) { + openFiles(new ArrayList<>(List.of(file))); + } + + /** + * Opens the given files. If one of it is null or 404, nothing happens. + * In case the file is already opened, that panel is raised. + * + * @param filesToOpen the filesToOpen, may be null or not existing + */ + public void openFiles(List filesToOpen) { + LibraryTab toRaise = null; + int initialCount = filesToOpen.size(); + int removed = 0; + + // Check if any of the files are already open: + for (Iterator iterator = filesToOpen.iterator(); iterator.hasNext(); ) { + Path file = iterator.next(); + for (LibraryTab libraryTab : tabContainer.getLibraryTabs()) { + if ((libraryTab.getBibDatabaseContext().getDatabasePath().isPresent()) + && libraryTab.getBibDatabaseContext().getDatabasePath().get().equals(file)) { + iterator.remove(); + removed++; + // See if we removed the final one. If so, we must perhaps + // raise the LibraryTab in question: + if (removed == initialCount) { + toRaise = libraryTab; + } + // no more LibraryTabs to check, we found a matching one + break; + } + } + } + + // Run the actual open in a thread to prevent the program + // locking until the file is loaded. + if (!filesToOpen.isEmpty()) { + FileHistory fileHistory = preferences.getLastFilesOpenedPreferences().getFileHistory(); + filesToOpen.forEach(theFile -> { + // This method will execute the concrete file opening and loading in a background thread + openTheFile(theFile); + fileHistory.newFile(theFile); + }); + } else if (toRaise != null && tabContainer.getCurrentLibraryTab() == null) { + // If no files are remaining to open, this could mean that a file was + // already open. If so, we may have to raise the correct tab: + // If there is already a library focused, do not show this library + tabContainer.showLibraryTab(toRaise); + } + } + + /** + * This is the real file opening. Should be called via {@link #openFile(Path)} + * + * Similar method: {@link org.jabref.gui.frame.JabRefFrame#addTab(org.jabref.model.database.BibDatabaseContext, boolean)}. + * + * @param file the file, may be NOT null, but may not be existing + */ + private void openTheFile(Path file) { + Objects.requireNonNull(file); + if (!Files.exists(file)) { + return; + } + + BackgroundTask backgroundTask = BackgroundTask.wrap(() -> loadDatabase(file)); + // The backgroundTask is executed within the method createLibraryTab + LibraryTab newTab = LibraryTab.createLibraryTab( + backgroundTask, + file, + dialogService, + aiService, + preferences, + stateManager, + tabContainer, + fileUpdateMonitor, + entryTypesManager, + undoManager, + clipboardManager, + taskExecutor); + tabContainer.addTab(newTab, true); + } + + private ParserResult loadDatabase(Path file) throws Exception { + Path fileToLoad = file.toAbsolutePath(); + + dialogService.notify(Localization.lang("Opening") + ": '" + file + "'"); + + preferences.getFilePreferences().setWorkingDirectory(fileToLoad.getParent()); + Path backupDir = preferences.getFilePreferences().getBackupDirectory(); + + ParserResult parserResult = null; + if (BackupManagerGit.backupGitDiffers(fileToLoad, backupDir)) { + // 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, preferences, fileUpdateMonitor, undoManager, stateManager) + .orElse(null); + } + + try { + if (parserResult == null) { + // No backup was restored, do the "normal" loading + parserResult = OpenDatabase.loadDatabase(fileToLoad, + preferences.getImportFormatPreferences(), + fileUpdateMonitor); + } + + if (parserResult.hasWarnings()) { + String content = Localization.lang("Please check your library file for wrong syntax.") + + "\n\n" + parserResult.getErrorMessage(); + UiTaskExecutor.runInJavaFXThread(() -> + dialogService.showWarningDialogAndWait(Localization.lang("Open library error"), content)); + } + } catch (IOException e) { + parserResult = ParserResult.fromError(e); + LOGGER.error("Error opening file '{}'", fileToLoad, e); + } + + if (parserResult.getDatabase().isShared()) { + openSharedDatabase( + parserResult, + tabContainer, + dialogService, + preferences, + aiService, + stateManager, + entryTypesManager, + fileUpdateMonitor, + undoManager, + clipboardManager, + taskExecutor); + } + return parserResult; + } + + public static void openSharedDatabase(ParserResult parserResult, + LibraryTabContainer tabContainer, + DialogService dialogService, + GuiPreferences preferences, + AiService aiService, + StateManager stateManager, + BibEntryTypesManager entryTypesManager, + FileUpdateMonitor fileUpdateMonitor, + UndoManager undoManager, + ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor) + throws SQLException, DatabaseNotSupportedException, InvalidDBMSConnectionPropertiesException, NotASharedDatabaseException { + try { + new SharedDatabaseUIManager( + tabContainer, + dialogService, + preferences, + aiService, + stateManager, + entryTypesManager, + fileUpdateMonitor, + undoManager, + clipBoardManager, + taskExecutor) + .openSharedDatabaseFromParserResult(parserResult); + } catch (SQLException | DatabaseNotSupportedException | InvalidDBMSConnectionPropertiesException | + NotASharedDatabaseException e) { + parserResult.getDatabaseContext().clearDatabasePath(); // do not open the original file + parserResult.getDatabase().clearSharedDatabaseID(); + + throw e; + } + } +} From fa42e866e455b45216d01d96d65029cfea4385f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 27 Nov 2024 14:22:13 +0100 Subject: [PATCH 25/84] Start adapting SaveDatabaseAction.java and LibraryTab.java --- src/main/java/org/jabref/gui/LibraryTab.java | 3 ++- .../org/jabref/gui/autosaveandbackup/BackupManagerGit.java | 2 +- src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index f7579709894..2a5dd6c5d30 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -43,6 +43,7 @@ import org.jabref.gui.autocompleter.SuggestionProviders; import org.jabref.gui.autosaveandbackup.AutosaveManager; import org.jabref.gui.autosaveandbackup.BackupManager; +import org.jabref.gui.autosaveandbackup.BackupManagerGit; import org.jabref.gui.collab.DatabaseChangeMonitor; import org.jabref.gui.dialogs.AutosaveUiManager; import org.jabref.gui.entryeditor.EntryEditor; @@ -373,7 +374,7 @@ public void installAutosaveManagerAndBackupManager() { autosaveManager.registerListener(new AutosaveUiManager(this, dialogService, preferences, entryTypesManager)); } if (isDatabaseReadyForBackup(bibDatabaseContext) && preferences.getFilePreferences().shouldCreateBackup()) { - BackupManager.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences); + BackupManagerGit.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences); } } diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index ffd7db9acf3..bea8a3980c5 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -91,7 +91,7 @@ public class BackupManagerGit { public BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { BackupManagerGit BackupManager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - BackupManager.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); + this.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); runningInstances.add(BackupManager); return BackupManager; } diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 7965a2d626d..0d0376b86de 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -22,6 +22,7 @@ import org.jabref.gui.LibraryTab; import org.jabref.gui.autosaveandbackup.AutosaveManager; import org.jabref.gui.autosaveandbackup.BackupManager; +import org.jabref.gui.autosaveandbackup.BackupManagerGit; import org.jabref.gui.maintable.BibEntryTableViewModel; import org.jabref.gui.maintable.columns.MainTableColumn; import org.jabref.gui.preferences.GuiPreferences; @@ -141,7 +142,7 @@ boolean saveAs(Path file, SaveDatabaseMode mode) { if (databasePath.isPresent()) { // Close AutosaveManager, BackupManagerGit, and IndexManager for original library AutosaveManager.shutdown(context); - BackupManager.shutdown(context, this.preferences.getFilePreferences().getBackupDirectory(), preferences.getFilePreferences().shouldCreateBackup()); + BackupManagerGit.shutdown(context, this.preferences.getFilePreferences().getBackupDirectory(), preferences.getFilePreferences().shouldCreateBackup(), databasePath.get()); libraryTab.closeIndexManger(); } From be482cd1cfecc721ebeb00411434ef31a3e28695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 27 Nov 2024 15:12:09 +0100 Subject: [PATCH 26/84] Classes adapted, except for UI --- src/main/java/org/jabref/gui/LibraryTab.java | 19 +- .../autosaveandbackup/BackupManagerGit.java | 10 +- .../gui/exporter/SaveDatabaseAction.java | 26 +- .../importer/actions/OpenDatabaseAction.java | 46 +-- .../actions/OpenDatabaseActionGit.java | 329 ------------------ .../gui/exporter/SaveDatabaseActionTest.java | 7 +- 6 files changed, 67 insertions(+), 370 deletions(-) delete mode 100644 src/main/java/org/jabref/gui/importer/actions/OpenDatabaseActionGit.java diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 2a5dd6c5d30..86227248bb0 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -106,6 +106,7 @@ import com.tobiasdiez.easybind.Subscription; import org.controlsfx.control.NotificationPane; import org.controlsfx.control.action.Action; +import org.eclipse.jgit.api.errors.GitAPIException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -309,7 +310,7 @@ private void onDatabaseLoadingStarted() { getMainTable().placeholderProperty().setValue(loadingLayout); } - private void onDatabaseLoadingSucceed(ParserResult result) { + private void onDatabaseLoadingSucceed(ParserResult result) throws GitAPIException, IOException { OpenDatabaseAction.performPostOpenActions(result, dialogService, preferences); if (result.getChangedOnMigration()) { this.markBaseChanged(); @@ -344,7 +345,7 @@ private void onDatabaseLoadingFailed(Exception ex) { dialogService.showErrorDialogAndWait(title, content, ex); } - private void setDatabaseContext(BibDatabaseContext bibDatabaseContext) { + private void setDatabaseContext(BibDatabaseContext bibDatabaseContext) throws GitAPIException, IOException { TabPane tabPane = this.getTabPane(); if (tabPane == null) { LOGGER.debug("User interrupted loading. Not showing any library."); @@ -368,13 +369,13 @@ private void setDatabaseContext(BibDatabaseContext bibDatabaseContext) { installAutosaveManagerAndBackupManager(); } - public void installAutosaveManagerAndBackupManager() { + public void installAutosaveManagerAndBackupManager() throws GitAPIException, IOException { if (isDatabaseReadyForAutoSave(bibDatabaseContext)) { AutosaveManager autosaveManager = AutosaveManager.start(bibDatabaseContext); autosaveManager.registerListener(new AutosaveUiManager(this, dialogService, preferences, entryTypesManager)); } if (isDatabaseReadyForBackup(bibDatabaseContext) && preferences.getFilePreferences().shouldCreateBackup()) { - BackupManagerGit.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences); + BackupManagerGit.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences, bibDatabaseContext.getDatabasePath().get()); } } @@ -1083,7 +1084,15 @@ public static LibraryTab createLibraryTab(BackgroundTask dataLoadi newTab.setDataLoadingTask(dataLoadingTask); dataLoadingTask.onRunning(newTab::onDatabaseLoadingStarted) - .onSuccess(newTab::onDatabaseLoadingSucceed) + .onSuccess(result -> { + try { + newTab.onDatabaseLoadingSucceed(result); + } catch (Exception e) { + // We need to handle the exception. + // Handle the exception, e.g., log it or show an error dialog + LOGGER.error("An error occurred while loading the database", e); + } + }) .onFailure(newTab::onDatabaseLoadingFailed) .executeWith(taskExecutor); diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index bea8a3980c5..cb623bf48d1 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -89,11 +89,11 @@ public class BackupManagerGit { * @throws GitAPIException if a Git API error occurs */ - public BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { - BackupManagerGit BackupManager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - this.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); - runningInstances.add(BackupManager); - return BackupManager; + public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { + BackupManagerGit backupManagerGit = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + backupManagerGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); + runningInstances.add(backupManagerGit); + return backupManagerGit; } /** diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 0d0376b86de..afefbd69318 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -21,7 +21,6 @@ import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.autosaveandbackup.AutosaveManager; -import org.jabref.gui.autosaveandbackup.BackupManager; import org.jabref.gui.autosaveandbackup.BackupManagerGit; import org.jabref.gui.maintable.BibEntryTableViewModel; import org.jabref.gui.maintable.columns.MainTableColumn; @@ -45,6 +44,7 @@ import org.jabref.model.metadata.SaveOrder; import org.jabref.model.metadata.SelfContainedSaveOrder; +import org.eclipse.jgit.api.errors.GitAPIException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,11 +88,18 @@ public boolean save(SaveDatabaseMode mode) { /** * Asks the user for the path and saves afterward */ + public void saveAs() { - askForSavePath().ifPresent(this::saveAs); + askForSavePath().ifPresent(path -> { + try { + saveAs(path); + } catch (Exception e) { + throw new RuntimeException("Failed to save the database", e); + } + }); } - public boolean saveAs(Path file) { + public boolean saveAs(Path file) throws GitAPIException, IOException { return this.saveAs(file, SaveDatabaseMode.NORMAL); } @@ -135,7 +142,7 @@ public void saveSelectedAsPlain() { * successful save. * @return true on successful save */ - boolean saveAs(Path file, SaveDatabaseMode mode) { + boolean saveAs(Path file, SaveDatabaseMode mode) throws GitAPIException, IOException { BibDatabaseContext context = libraryTab.getBibDatabaseContext(); Optional databasePath = context.getDatabasePath(); @@ -205,7 +212,16 @@ private boolean save(BibDatabaseContext bibDatabaseContext, SaveDatabaseMode mod Optional databasePath = bibDatabaseContext.getDatabasePath(); if (databasePath.isEmpty()) { Optional savePath = askForSavePath(); - return savePath.filter(path -> saveAs(path, mode)).isPresent(); + return savePath.filter(path -> { + try { + return saveAs(path, mode); + } catch ( + GitAPIException | + IOException e) { + LOGGER.error("A problem occurred when trying to save the file %s".formatted(path), e); + throw new RuntimeException(e); + } + }).isPresent(); } return save(databasePath.get(), mode); 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 748c78db227..2c3436e2476 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -18,7 +18,7 @@ import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; -import org.jabref.gui.autosaveandbackup.BackupManager; +import org.jabref.gui.autosaveandbackup.BackupManagerGit; import org.jabref.gui.dialogs.BackupUIManager; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.shared.SharedDatabaseUIManager; @@ -73,15 +73,15 @@ public class OpenDatabaseAction extends SimpleCommand { private final TaskExecutor taskExecutor; public OpenDatabaseAction(LibraryTabContainer tabContainer, - GuiPreferences preferences, - AiService aiService, - DialogService dialogService, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - CountingUndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) { + GuiPreferences preferences, + AiService aiService, + DialogService dialogService, + StateManager stateManager, + FileUpdateMonitor fileUpdateMonitor, + BibEntryTypesManager entryTypesManager, + CountingUndoManager undoManager, + ClipBoardManager clipBoardManager, + TaskExecutor taskExecutor) { this.tabContainer = tabContainer; this.preferences = preferences; this.aiService = aiService; @@ -250,7 +250,7 @@ private ParserResult loadDatabase(Path file) throws Exception { Path backupDir = preferences.getFilePreferences().getBackupDirectory(); ParserResult parserResult = null; - if (BackupManager.backupFileDiffers(fileToLoad, backupDir)) { + if (BackupManagerGit.backupGitDiffers(fileToLoad, backupDir)) { // 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, preferences, fileUpdateMonitor, undoManager, stateManager) @@ -277,18 +277,18 @@ private ParserResult loadDatabase(Path file) throws Exception { } if (parserResult.getDatabase().isShared()) { - openSharedDatabase( - parserResult, - tabContainer, - dialogService, - preferences, - aiService, - stateManager, - entryTypesManager, - fileUpdateMonitor, - undoManager, - clipboardManager, - taskExecutor); + openSharedDatabase( + parserResult, + tabContainer, + dialogService, + preferences, + aiService, + stateManager, + entryTypesManager, + fileUpdateMonitor, + undoManager, + clipboardManager, + taskExecutor); } return parserResult; } diff --git a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseActionGit.java b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseActionGit.java deleted file mode 100644 index b762cb7f81e..00000000000 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseActionGit.java +++ /dev/null @@ -1,329 +0,0 @@ -package org.jabref.gui.importer.actions; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -import javax.swing.undo.UndoManager; - -import org.jabref.gui.ClipBoardManager; -import org.jabref.gui.DialogService; -import org.jabref.gui.LibraryTab; -import org.jabref.gui.LibraryTabContainer; -import org.jabref.gui.StateManager; -import org.jabref.gui.actions.SimpleCommand; -import org.jabref.gui.autosaveandbackup.BackupManagerGit; -import org.jabref.gui.dialogs.BackupUIManager; -import org.jabref.gui.preferences.GuiPreferences; -import org.jabref.gui.shared.SharedDatabaseUIManager; -import org.jabref.gui.undo.CountingUndoManager; -import org.jabref.gui.util.FileDialogConfiguration; -import org.jabref.gui.util.UiTaskExecutor; -import org.jabref.logic.ai.AiService; -import org.jabref.logic.importer.OpenDatabase; -import org.jabref.logic.importer.ParserResult; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.preferences.CliPreferences; -import org.jabref.logic.shared.DatabaseNotSupportedException; -import org.jabref.logic.shared.exception.InvalidDBMSConnectionPropertiesException; -import org.jabref.logic.shared.exception.NotASharedDatabaseException; -import org.jabref.logic.util.BackgroundTask; -import org.jabref.logic.util.Directories; -import org.jabref.logic.util.StandardFileType; -import org.jabref.logic.util.TaskExecutor; -import org.jabref.logic.util.io.FileHistory; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.util.FileUpdateMonitor; - -import com.google.common.annotations.VisibleForTesting; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -// The action concerned with opening an existing database. -public class OpenDatabaseActionGit extends SimpleCommand { - - private static final Logger LOGGER = LoggerFactory.getLogger(OpenDatabaseAction.class); - - // List of actions that may need to be called after opening the file. Such as - // upgrade actions etc. that may depend on the JabRef version that wrote the file: - private static final List POST_OPEN_ACTIONS = List.of( - // Migrations: - // Warning for migrating the Review into the Comment field - new MergeReviewIntoCommentAction(), - // Check for new custom entry types loaded from the BIB file: - new CheckForNewEntryTypesAction(), - // Migrate search groups fielded terms to use the new operators (RegEx, case sensitive) - new SearchGroupsMigrationAction()); - - private final LibraryTabContainer tabContainer; - private final GuiPreferences preferences; - private final AiService aiService; - private final StateManager stateManager; - private final FileUpdateMonitor fileUpdateMonitor; - private final DialogService dialogService; - private final BibEntryTypesManager entryTypesManager; - private final CountingUndoManager undoManager; - private final ClipBoardManager clipboardManager; - private final TaskExecutor taskExecutor; - - public OpenDatabaseActionGit(LibraryTabContainer tabContainer, - GuiPreferences preferences, - AiService aiService, - DialogService dialogService, - StateManager stateManager, - FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager, - CountingUndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) { - this.tabContainer = tabContainer; - this.preferences = preferences; - this.aiService = aiService; - this.dialogService = dialogService; - this.stateManager = stateManager; - this.fileUpdateMonitor = fileUpdateMonitor; - this.entryTypesManager = entryTypesManager; - this.undoManager = undoManager; - this.clipboardManager = clipBoardManager; - this.taskExecutor = taskExecutor; - } - - public static void performPostOpenActions(ParserResult result, DialogService dialogService, CliPreferences preferences) { - for (GUIPostOpenAction action : OpenDatabaseActionGit.POST_OPEN_ACTIONS) { - if (action.isActionNecessary(result, dialogService, preferences)) { - action.performAction(result, dialogService, preferences); - } - } - } - - @Override - public void execute() { - List filesToOpen = getFilesToOpen(); - openFiles(new ArrayList<>(filesToOpen)); - } - - @VisibleForTesting - List getFilesToOpen() { - List filesToOpen; - - try { - FileDialogConfiguration initialDirectoryConfig = getFileDialogConfiguration(getInitialDirectory()); - filesToOpen = dialogService.showFileOpenDialogAndGetMultipleFiles(initialDirectoryConfig); - } catch (IllegalArgumentException e) { - // See https://github.com/JabRef/jabref/issues/10548 for details - // Rebuild a new config with the home directory - FileDialogConfiguration homeDirectoryConfig = getFileDialogConfiguration(Directories.getUserDirectory()); - filesToOpen = dialogService.showFileOpenDialogAndGetMultipleFiles(homeDirectoryConfig); - } - - return filesToOpen; - } - - /** - * Builds a new FileDialogConfiguration using the given path as the initial directory for use in - * dialogService.showFileOpenDialogAndGetMultipleFiles(). - * - * @param initialDirectory Path to use as the initial directory - * @return new FileDialogConfig with given initial directory - */ - public FileDialogConfiguration getFileDialogConfiguration(Path initialDirectory) { - return new FileDialogConfiguration.Builder() - .addExtensionFilter(StandardFileType.BIBTEX_DB) - .withDefaultExtension(StandardFileType.BIBTEX_DB) - .withInitialDirectory(initialDirectory) - .build(); - } - - /** - * @return Path of current panel database directory or the working directory - */ - @VisibleForTesting - Path getInitialDirectory() { - if (tabContainer.getLibraryTabs().isEmpty()) { - return preferences.getFilePreferences().getWorkingDirectory(); - } else { - Optional databasePath = tabContainer.getCurrentLibraryTab().getBibDatabaseContext().getDatabasePath(); - return databasePath.map(Path::getParent).orElse(preferences.getFilePreferences().getWorkingDirectory()); - } - } - - /** - * Opens the given file. If null or 404, nothing happens. - * In case the file is already opened, that panel is raised. - * - * @param file the file, may be null or not existing - */ - public void openFile(Path file) { - openFiles(new ArrayList<>(List.of(file))); - } - - /** - * Opens the given files. If one of it is null or 404, nothing happens. - * In case the file is already opened, that panel is raised. - * - * @param filesToOpen the filesToOpen, may be null or not existing - */ - public void openFiles(List filesToOpen) { - LibraryTab toRaise = null; - int initialCount = filesToOpen.size(); - int removed = 0; - - // Check if any of the files are already open: - for (Iterator iterator = filesToOpen.iterator(); iterator.hasNext(); ) { - Path file = iterator.next(); - for (LibraryTab libraryTab : tabContainer.getLibraryTabs()) { - if ((libraryTab.getBibDatabaseContext().getDatabasePath().isPresent()) - && libraryTab.getBibDatabaseContext().getDatabasePath().get().equals(file)) { - iterator.remove(); - removed++; - // See if we removed the final one. If so, we must perhaps - // raise the LibraryTab in question: - if (removed == initialCount) { - toRaise = libraryTab; - } - // no more LibraryTabs to check, we found a matching one - break; - } - } - } - - // Run the actual open in a thread to prevent the program - // locking until the file is loaded. - if (!filesToOpen.isEmpty()) { - FileHistory fileHistory = preferences.getLastFilesOpenedPreferences().getFileHistory(); - filesToOpen.forEach(theFile -> { - // This method will execute the concrete file opening and loading in a background thread - openTheFile(theFile); - fileHistory.newFile(theFile); - }); - } else if (toRaise != null && tabContainer.getCurrentLibraryTab() == null) { - // If no files are remaining to open, this could mean that a file was - // already open. If so, we may have to raise the correct tab: - // If there is already a library focused, do not show this library - tabContainer.showLibraryTab(toRaise); - } - } - - /** - * This is the real file opening. Should be called via {@link #openFile(Path)} - * - * Similar method: {@link org.jabref.gui.frame.JabRefFrame#addTab(org.jabref.model.database.BibDatabaseContext, boolean)}. - * - * @param file the file, may be NOT null, but may not be existing - */ - private void openTheFile(Path file) { - Objects.requireNonNull(file); - if (!Files.exists(file)) { - return; - } - - BackgroundTask backgroundTask = BackgroundTask.wrap(() -> loadDatabase(file)); - // The backgroundTask is executed within the method createLibraryTab - LibraryTab newTab = LibraryTab.createLibraryTab( - backgroundTask, - file, - dialogService, - aiService, - preferences, - stateManager, - tabContainer, - fileUpdateMonitor, - entryTypesManager, - undoManager, - clipboardManager, - taskExecutor); - tabContainer.addTab(newTab, true); - } - - private ParserResult loadDatabase(Path file) throws Exception { - Path fileToLoad = file.toAbsolutePath(); - - dialogService.notify(Localization.lang("Opening") + ": '" + file + "'"); - - preferences.getFilePreferences().setWorkingDirectory(fileToLoad.getParent()); - Path backupDir = preferences.getFilePreferences().getBackupDirectory(); - - ParserResult parserResult = null; - if (BackupManagerGit.backupGitDiffers(fileToLoad, backupDir)) { - // 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, preferences, fileUpdateMonitor, undoManager, stateManager) - .orElse(null); - } - - try { - if (parserResult == null) { - // No backup was restored, do the "normal" loading - parserResult = OpenDatabase.loadDatabase(fileToLoad, - preferences.getImportFormatPreferences(), - fileUpdateMonitor); - } - - if (parserResult.hasWarnings()) { - String content = Localization.lang("Please check your library file for wrong syntax.") - + "\n\n" + parserResult.getErrorMessage(); - UiTaskExecutor.runInJavaFXThread(() -> - dialogService.showWarningDialogAndWait(Localization.lang("Open library error"), content)); - } - } catch (IOException e) { - parserResult = ParserResult.fromError(e); - LOGGER.error("Error opening file '{}'", fileToLoad, e); - } - - if (parserResult.getDatabase().isShared()) { - openSharedDatabase( - parserResult, - tabContainer, - dialogService, - preferences, - aiService, - stateManager, - entryTypesManager, - fileUpdateMonitor, - undoManager, - clipboardManager, - taskExecutor); - } - return parserResult; - } - - public static void openSharedDatabase(ParserResult parserResult, - LibraryTabContainer tabContainer, - DialogService dialogService, - GuiPreferences preferences, - AiService aiService, - StateManager stateManager, - BibEntryTypesManager entryTypesManager, - FileUpdateMonitor fileUpdateMonitor, - UndoManager undoManager, - ClipBoardManager clipBoardManager, - TaskExecutor taskExecutor) - throws SQLException, DatabaseNotSupportedException, InvalidDBMSConnectionPropertiesException, NotASharedDatabaseException { - try { - new SharedDatabaseUIManager( - tabContainer, - dialogService, - preferences, - aiService, - stateManager, - entryTypesManager, - fileUpdateMonitor, - undoManager, - clipBoardManager, - taskExecutor) - .openSharedDatabaseFromParserResult(parserResult); - } catch (SQLException | DatabaseNotSupportedException | InvalidDBMSConnectionPropertiesException | - NotASharedDatabaseException e) { - parserResult.getDatabaseContext().clearDatabasePath(); // do not open the original file - parserResult.getDatabase().clearSharedDatabaseID(); - - throw e; - } - } -} diff --git a/src/test/java/org/jabref/gui/exporter/SaveDatabaseActionTest.java b/src/test/java/org/jabref/gui/exporter/SaveDatabaseActionTest.java index bbc91aa2501..8f5f80c3f22 100644 --- a/src/test/java/org/jabref/gui/exporter/SaveDatabaseActionTest.java +++ b/src/test/java/org/jabref/gui/exporter/SaveDatabaseActionTest.java @@ -33,6 +33,7 @@ import org.jabref.model.metadata.MetaData; import org.jabref.model.metadata.SaveOrder; +import org.eclipse.jgit.api.errors.GitAPIException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -67,7 +68,7 @@ void setUp() { } @Test - void saveAsShouldSetWorkingDirectory() { + void saveAsShouldSetWorkingDirectory() throws GitAPIException, IOException { when(dialogService.showFileSaveDialog(any(FileDialogConfiguration.class))).thenReturn(Optional.of(file)); doReturn(true).when(saveDatabaseAction).saveAs(any()); @@ -77,7 +78,7 @@ void saveAsShouldSetWorkingDirectory() { } @Test - void saveAsShouldNotSetWorkingDirectoryIfNotSelected() { + void saveAsShouldNotSetWorkingDirectoryIfNotSelected() throws GitAPIException, IOException { when(dialogService.showFileSaveDialog(any(FileDialogConfiguration.class))).thenReturn(Optional.empty()); doReturn(false).when(saveDatabaseAction).saveAs(any()); @@ -87,7 +88,7 @@ void saveAsShouldNotSetWorkingDirectoryIfNotSelected() { } @Test - void saveShouldShowSaveAsIfDatabaseNotSelected() { + void saveShouldShowSaveAsIfDatabaseNotSelected() throws GitAPIException, IOException { when(dbContext.getDatabasePath()).thenReturn(Optional.empty()); when(dbContext.getLocation()).thenReturn(DatabaseLocation.LOCAL); when(dialogService.showFileSaveDialog(any())).thenReturn(Optional.of(file)); From 2797c01887261301b3c23f402d6e11d15c1d1c04 Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Wed, 27 Nov 2024 16:00:30 +0100 Subject: [PATCH 27/84] UI update --- .../jabref/gui/backup/BackupChoiceDialog.java | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java b/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java index 8049e5888a6..435ac24c63f 100644 --- a/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java +++ b/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java @@ -8,7 +8,6 @@ import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.scene.control.Label; -import javafx.scene.control.Pagination; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.layout.VBox; @@ -21,7 +20,7 @@ public class BackupChoiceDialog extends BaseDialog { public static final ButtonType IGNORE_BACKUP = new ButtonType(Localization.lang("Ignore backup"), ButtonBar.ButtonData.CANCEL_CLOSE); public static final ButtonType REVIEW_BACKUP = new ButtonType(Localization.lang("Review backup"), ButtonBar.ButtonData.LEFT); - private static final int ROWS_PER_PAGE = 10; // Define number of rows per page + private final ObservableList tableData = FXCollections.observableArrayList(); @FXML private final TableView backupTableView; @@ -32,38 +31,20 @@ public BackupChoiceDialog(Path originalPath, Path backupDir) { getDialogPane().setMinHeight(150); getDialogPane().setMinWidth(450); getDialogPane().getButtonTypes().setAll(RESTORE_BACKUP, IGNORE_BACKUP, REVIEW_BACKUP); + String content = Localization.lang("It looks like JabRef did not shut down cleanly last time the file was used.") + "\n\n" + Localization.lang("Do you want to recover the library from a backup file?"); - backupTableView = new TableView(); + backupTableView = new TableView<>(); setupBackupTableView(); + pushSampleData(); + backupTableView.setItems(tableData); - // Sample data - ObservableList data = FXCollections.observableArrayList(); - for (int i = 0; i < 100; i++) { // Adjust 20 to however many entries you want - data.add(new BackupEntry("2023-11-01", i + " MB", i)); - } - setContentText(content); - - // Pagination control - int pageCount = (int) Math.ceil(data.size() / (double) ROWS_PER_PAGE); - Pagination pagination = new Pagination(pageCount, 0); - pagination.setPageFactory(pageIndex -> { - int start = pageIndex * ROWS_PER_PAGE; - int end = Math.min(start + ROWS_PER_PAGE, data.size()); - backupTableView.setItems(FXCollections.observableArrayList(data.subList(start, end))); - backupTableView.getSelectionModel().selectFirst(); - return new VBox(backupTableView); - }); - - // VBox content to hold the pagination and the label - VBox contentBox = new VBox(10); - contentBox.getChildren().addAll(new Label(content), pagination); + VBox contentBox = new VBox(); + contentBox.getChildren().addAll(new Label(content), backupTableView); contentBox.setPrefWidth(380); - // Set the dialog content getDialogPane().setContent(contentBox); - setResultConverter(dialogButton -> { if (dialogButton == RESTORE_BACKUP || dialogButton == REVIEW_BACKUP) { return new BackupChoiceDialogRecord(backupTableView.getSelectionModel().getSelectedItem(), dialogButton); @@ -84,5 +65,10 @@ private void setupBackupTableView() { backupTableView.getColumns().addAll(dateColumn, sizeColumn, entriesColumn); } -} + private void pushSampleData() { + for (int i = 0; i < 50; i++) { + tableData.add(new BackupEntry("2023-11-" + (i + 1), i + " MB", i)); + } + } +} From d070e67ac380ddb9ad81da7483707835b6be32e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 27 Nov 2024 16:20:17 +0100 Subject: [PATCH 28/84] Starting tests --- src/main/java/org/jabref/gui/LibraryTab.java | 2 +- .../BackupManagerGitTest.java | 107 +++++++++--- .../autosaveandbackup/BackupManagerTest.java | 160 ------------------ 3 files changed, 89 insertions(+), 180 deletions(-) diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 86227248bb0..1cd6dfaaeef 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -375,7 +375,7 @@ public void installAutosaveManagerAndBackupManager() throws GitAPIException, IOE autosaveManager.registerListener(new AutosaveUiManager(this, dialogService, preferences, entryTypesManager)); } if (isDatabaseReadyForBackup(bibDatabaseContext) && preferences.getFilePreferences().shouldCreateBackup()) { - BackupManagerGit.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences, bibDatabaseContext.getDatabasePath().get()); + BackupManagerGit.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences, bibDatabaseContext.getDatabasePath().get().getParent()); } } diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index 13f6dedaecc..7d2e4812625 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -3,53 +3,122 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; import org.jabref.gui.LibraryTab; import org.jabref.logic.FilePreferences; import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.util.BackupFileType; +import org.jabref.logic.util.io.BackupFileUtil; +import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.revwalk.RevCommit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; class BackupManagerGitTest { - private Path backupDir; - private BackupManagerGit backupManager; + Path backupDir; @BeforeEach - void setup(@TempDir Path tempDir) throws IOException, GitAPIException, IOException { + void setup(@TempDir Path tempDir) throws IOException, GitAPIException { backupDir = tempDir.resolve("backup"); - Files.createDirectories(backupDir); + } + + @Test + void testGitRepositoryExistsAfterInitialization() throws IOException, GitAPIException { + // Initialize BackupManagerGit + BackupManagerGit backupManager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + + // Check if the .git directory exists + Path gitDir = backupDir.resolve(".git"); + assertTrue(Files.exists(gitDir), "Git repository should exist after initialization"); + } + + @Test + void testBackupFileIsEqualForNonExistingBackup() throws Exception { + Path originalFile = Path.of(BackupManagerGitTest.class.getResource("no-autosave.bib").toURI()); + assertFalse(BackupManagerGit.backupGitDiffers(originalFile, backupDir)); + } + + @Test + void testBackupFileIsEqual() throws Exception { + // Prepare test: Create backup file on "right" path + Path source = Path.of(BackupManagerGitTest.class.getResource("no-changes.bib.bak").toURI()); + Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(Path.of(BackupManagerGitTest.class.getResource("no-changes.bib").toURI()), BackupFileType.BACKUP, backupDir); + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + + Path originalFile = Path.of(BackupManagerGitTest.class.getResource("no-changes.bib").toURI()); + assertFalse(BackupManagerGit.backupGitDiffers(originalFile, backupDir)); + } + + @Test + void testBackupFileDiffers() throws Exception { + // Prepare test: Create backup file on "right" path + Path source = Path.of(BackupManagerGitTest.class.getResource("changes.bib.bak").toURI()); + Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(Path.of(BackupManagerGitTest.class.getResource("changes.bib").toURI()), BackupFileType.BACKUP, backupDir); + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + + Path originalFile = Path.of(BackupManagerGitTest.class.getResource("changes.bib").toURI()); + assertTrue(BackupManagerGit.backupGitDiffers(originalFile, backupDir)); + } - // Initialize BackupManagerGit with mock dependencies - var libraryTab = mock(LibraryTab.class); - var bibDatabaseContext = new BibDatabaseContext(); - var entryTypesManager = mock(BibEntryTypesManager.class); - var preferences = mock(CliPreferences.class); - var filePreferences = mock(FilePreferences.class); + @Test + void testCorrectBackupFileDeterminedForMultipleBakFiles() throws Exception { + Path noChangesBib = Path.of(BackupManagerGitTest.class.getResource("no-changes.bib").toURI()); + Path noChangesBibBak = Path.of(BackupManagerGitTest.class.getResource("no-changes.bib.bak").toURI()); - when(preferences.getFilePreferences()).thenReturn(filePreferences); - when(filePreferences.getBackupDirectory()).thenReturn(backupDir); + // Prepare test: Create backup files on "right" path + // most recent file does not have any changes + Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(noChangesBib, BackupFileType.BACKUP, backupDir); + Files.copy(noChangesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - backupManager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + // create "older" .bak files containing changes + for (int i = 0; i < 10; i++) { + Path changesBibBak = Path.of(BackupManagerGitTest.class.getResource("changes.bib").toURI()); + Path directory = backupDir; + String timeSuffix = "2020-02-03--00.00.0" + Integer.toString(i); + String fileName = BackupFileUtil.getUniqueFilePrefix(noChangesBib) + "--no-changes.bib--" + timeSuffix + ".bak"; + target = directory.resolve(fileName); + Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); + } + + Path originalFile = noChangesBib; + assertFalse(BackupManagerGit.backupGitDiffers(originalFile, backupDir)); } @Test - void testInitialization() { - assertNotNull(backupManager); + void testBakFileWithNewerTimeStampLeadsToDiff() throws Exception { + Path changesBib = Path.of(BackupManagerGitTest.class.getResource("changes.bib").toURI()); + Path changesBibBak = Path.of(BackupManagerGitTest.class.getResource("changes.bib.bak").toURI()); + + Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(changesBib, BackupFileType.BACKUP, backupDir); + Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); + + assertTrue(BackupManagerGit.backupGitDiffers(changesBib, backupDir)); + } + + @Test + void testBakFileWithOlderTimeStampDoesNotLeadToDiff() throws Exception { + Path changesBib = Path.of(BackupManagerGitTest.class.getResource("changes.bib").toURI()); + Path changesBibBak = Path.of(BackupManagerGitTest.class.getResource("changes.bib.bak").toURI()); + + Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(changesBib, BackupFileType.BACKUP, backupDir); + Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); + + // Make .bak file very old + Files.setLastModifiedTime(target, FileTime.fromMillis(0)); + + assertFalse(BackupManagerGit.backupGitDiffers(changesBib, backupDir)); } } diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java index a45d7cbb9c1..4fe048d4a86 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java @@ -1,37 +1,14 @@ package org.jabref.gui.autosaveandbackup; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileTime; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import org.jabref.gui.LibraryTab; -import org.jabref.logic.FilePreferences; -import org.jabref.logic.preferences.CliPreferences; -import org.jabref.logic.util.BackupFileType; import org.jabref.logic.util.Directories; -import org.jabref.logic.util.io.BackupFileUtil; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.groups.event.GroupUpdatedEvent; -import org.jabref.model.metadata.MetaData; -import org.jabref.model.metadata.event.MetaDataChangedEvent; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.mockito.Answers; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; class BackupManagerTest { @@ -51,141 +28,4 @@ void backupFileNameIsCorrectlyGeneratedInAppDataDirectory() { // Pattern is "27182d3c--test.bib--", but the hashing is implemented differently on Linux than on Windows assertNotEquals("", bakPath); } - - @Test - void backupFileIsEqualForNonExistingBackup() throws Exception { - Path originalFile = Path.of(BackupManagerTest.class.getResource("no-autosave.bib").toURI()); - assertFalse(BackupManager.backupFileDiffers(originalFile, backupDir)); - } - - @Test - void backupFileIsEqual() throws Exception { - // Prepare test: Create backup file on "right" path - Path source = Path.of(BackupManagerTest.class.getResource("no-changes.bib.bak").toURI()); - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(Path.of(BackupManagerTest.class.getResource("no-changes.bib").toURI()), BackupFileType.BACKUP, backupDir); - Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); - - Path originalFile = Path.of(BackupManagerTest.class.getResource("no-changes.bib").toURI()); - assertFalse(BackupManager.backupFileDiffers(originalFile, backupDir)); - } - - @Test - void backupFileDiffers() throws Exception { - // Prepare test: Create backup file on "right" path - Path source = Path.of(BackupManagerTest.class.getResource("changes.bib.bak").toURI()); - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()), BackupFileType.BACKUP, backupDir); - Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); - - Path originalFile = Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()); - assertTrue(BackupManager.backupFileDiffers(originalFile, backupDir)); - } - - @Test - void correctBackupFileDeterminedForMultipleBakFiles() throws Exception { - Path noChangesBib = Path.of(BackupManagerTest.class.getResource("no-changes.bib").toURI()); - Path noChangesBibBak = Path.of(BackupManagerTest.class.getResource("no-changes.bib.bak").toURI()); - - // Prepare test: Create backup files on "right" path - // most recent file does not have any changes - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(noChangesBib, BackupFileType.BACKUP, backupDir); - Files.copy(noChangesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - - // create "older" .bak files containing changes - for (int i = 0; i < 10; i++) { - Path changesBibBak = Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()); - Path directory = backupDir; - String timeSuffix = "2020-02-03--00.00.0" + Integer.toString(i); - String fileName = BackupFileUtil.getUniqueFilePrefix(noChangesBib) + "--no-changes.bib--" + timeSuffix + ".bak"; - target = directory.resolve(fileName); - Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - } - - Path originalFile = noChangesBib; - assertFalse(BackupManager.backupFileDiffers(originalFile, backupDir)); - } - - @Test - void bakFileWithNewerTimeStampLeadsToDiff() throws Exception { - Path changesBib = Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()); - Path changesBibBak = Path.of(BackupManagerTest.class.getResource("changes.bib.bak").toURI()); - - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(changesBib, BackupFileType.BACKUP, backupDir); - Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - - assertTrue(BackupManager.backupFileDiffers(changesBib, backupDir)); - } - - @Test - void bakFileWithOlderTimeStampDoesNotLeadToDiff() throws Exception { - Path changesBib = Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()); - Path changesBibBak = Path.of(BackupManagerTest.class.getResource("changes.bib.bak").toURI()); - - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(changesBib, BackupFileType.BACKUP, backupDir); - Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - - // Make .bak file very old - Files.setLastModifiedTime(target, FileTime.fromMillis(0)); - - assertFalse(BackupManager.backupFileDiffers(changesBib, backupDir)); - } - - @Test - void shouldNotCreateABackup(@TempDir Path customDir) throws Exception { - Path backupDir = customDir.resolve("subBackupDir"); - Files.createDirectories(backupDir); - - var database = new BibDatabaseContext(new BibDatabase()); - database.setDatabasePath(customDir.resolve("Bibfile.bib")); - - var preferences = mock(CliPreferences.class, Answers.RETURNS_DEEP_STUBS); - var filePreferences = mock(FilePreferences.class); - when(preferences.getFilePreferences()).thenReturn(filePreferences); - when(filePreferences.getBackupDirectory()).thenReturn(backupDir); - when(filePreferences.shouldCreateBackup()).thenReturn(false); - - BackupManager manager = BackupManager.start( - mock(LibraryTab.class), - database, - mock(BibEntryTypesManager.class, Answers.RETURNS_DEEP_STUBS), - preferences); - manager.listen(new MetaDataChangedEvent(new MetaData())); - - BackupManager.shutdown(database, filePreferences.getBackupDirectory(), filePreferences.shouldCreateBackup()); - - List files = Files.list(backupDir).toList(); - assertEquals(Collections.emptyList(), files); - } - - @Test - void shouldCreateABackup(@TempDir Path customDir) throws Exception { - Path backupDir = customDir.resolve("subBackupDir"); - Files.createDirectories(backupDir); - - var database = new BibDatabaseContext(new BibDatabase()); - database.setDatabasePath(customDir.resolve("Bibfile.bib")); - - var preferences = mock(CliPreferences.class, Answers.RETURNS_DEEP_STUBS); - var filePreferences = mock(FilePreferences.class); - when(preferences.getFilePreferences()).thenReturn(filePreferences); - when(filePreferences.getBackupDirectory()).thenReturn(backupDir); - when(filePreferences.shouldCreateBackup()).thenReturn(true); - - BackupManager manager = BackupManager.start( - mock(LibraryTab.class), - database, - mock(BibEntryTypesManager.class, Answers.RETURNS_DEEP_STUBS), - preferences); - manager.listen(new MetaDataChangedEvent(new MetaData())); - - Optional fullBackupPath = manager.determineBackupPathForNewBackup(backupDir); - fullBackupPath.ifPresent(manager::performBackup); - manager.listen(new GroupUpdatedEvent(new MetaData())); - - BackupManager.shutdown(database, backupDir, true); - - List files = Files.list(backupDir).sorted().toList(); - // we only know the first backup path because the second one is created on shutdown - // due to timing issues we cannot test that reliable - assertEquals(fullBackupPath.get(), files.getFirst()); - } } From 528fe039c87d9f32051183ce87eb6211c03730c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 27 Nov 2024 16:22:04 +0100 Subject: [PATCH 29/84] Starting tests --- .../BackupManagerGitTest.java | 105 +----------- .../autosaveandbackup/BackupManagerTest.java | 160 ++++++++++++++++++ 2 files changed, 161 insertions(+), 104 deletions(-) diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index 7d2e4812625..a98b4c96f75 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -1,30 +1,12 @@ package org.jabref.gui.autosaveandbackup; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileTime; - -import org.jabref.gui.LibraryTab; -import org.jabref.logic.FilePreferences; -import org.jabref.logic.preferences.CliPreferences; -import org.jabref.logic.util.BackupFileType; -import org.jabref.logic.util.io.BackupFileUtil; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntryTypesManager; import org.eclipse.jgit.api.errors.GitAPIException; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - class BackupManagerGitTest { Path backupDir; @@ -33,94 +15,9 @@ class BackupManagerGitTest { void setup(@TempDir Path tempDir) throws IOException, GitAPIException { backupDir = tempDir.resolve("backup"); } +} - @Test - void testGitRepositoryExistsAfterInitialization() throws IOException, GitAPIException { - // Initialize BackupManagerGit - BackupManagerGit backupManager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - - // Check if the .git directory exists - Path gitDir = backupDir.resolve(".git"); - assertTrue(Files.exists(gitDir), "Git repository should exist after initialization"); - } - - @Test - void testBackupFileIsEqualForNonExistingBackup() throws Exception { - Path originalFile = Path.of(BackupManagerGitTest.class.getResource("no-autosave.bib").toURI()); - assertFalse(BackupManagerGit.backupGitDiffers(originalFile, backupDir)); - } - - @Test - void testBackupFileIsEqual() throws Exception { - // Prepare test: Create backup file on "right" path - Path source = Path.of(BackupManagerGitTest.class.getResource("no-changes.bib.bak").toURI()); - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(Path.of(BackupManagerGitTest.class.getResource("no-changes.bib").toURI()), BackupFileType.BACKUP, backupDir); - Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); - - Path originalFile = Path.of(BackupManagerGitTest.class.getResource("no-changes.bib").toURI()); - assertFalse(BackupManagerGit.backupGitDiffers(originalFile, backupDir)); - } - - @Test - void testBackupFileDiffers() throws Exception { - // Prepare test: Create backup file on "right" path - Path source = Path.of(BackupManagerGitTest.class.getResource("changes.bib.bak").toURI()); - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(Path.of(BackupManagerGitTest.class.getResource("changes.bib").toURI()), BackupFileType.BACKUP, backupDir); - Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); - - Path originalFile = Path.of(BackupManagerGitTest.class.getResource("changes.bib").toURI()); - assertTrue(BackupManagerGit.backupGitDiffers(originalFile, backupDir)); - } - - @Test - void testCorrectBackupFileDeterminedForMultipleBakFiles() throws Exception { - Path noChangesBib = Path.of(BackupManagerGitTest.class.getResource("no-changes.bib").toURI()); - Path noChangesBibBak = Path.of(BackupManagerGitTest.class.getResource("no-changes.bib.bak").toURI()); - - // Prepare test: Create backup files on "right" path - // most recent file does not have any changes - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(noChangesBib, BackupFileType.BACKUP, backupDir); - Files.copy(noChangesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - - // create "older" .bak files containing changes - for (int i = 0; i < 10; i++) { - Path changesBibBak = Path.of(BackupManagerGitTest.class.getResource("changes.bib").toURI()); - Path directory = backupDir; - String timeSuffix = "2020-02-03--00.00.0" + Integer.toString(i); - String fileName = BackupFileUtil.getUniqueFilePrefix(noChangesBib) + "--no-changes.bib--" + timeSuffix + ".bak"; - target = directory.resolve(fileName); - Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - } - - Path originalFile = noChangesBib; - assertFalse(BackupManagerGit.backupGitDiffers(originalFile, backupDir)); - } - - @Test - void testBakFileWithNewerTimeStampLeadsToDiff() throws Exception { - Path changesBib = Path.of(BackupManagerGitTest.class.getResource("changes.bib").toURI()); - Path changesBibBak = Path.of(BackupManagerGitTest.class.getResource("changes.bib.bak").toURI()); - - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(changesBib, BackupFileType.BACKUP, backupDir); - Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - - assertTrue(BackupManagerGit.backupGitDiffers(changesBib, backupDir)); - } - - @Test - void testBakFileWithOlderTimeStampDoesNotLeadToDiff() throws Exception { - Path changesBib = Path.of(BackupManagerGitTest.class.getResource("changes.bib").toURI()); - Path changesBibBak = Path.of(BackupManagerGitTest.class.getResource("changes.bib.bak").toURI()); - - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(changesBib, BackupFileType.BACKUP, backupDir); - Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - - // Make .bak file very old - Files.setLastModifiedTime(target, FileTime.fromMillis(0)); - assertFalse(BackupManagerGit.backupGitDiffers(changesBib, backupDir)); - } -} diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java index 4fe048d4a86..a45d7cbb9c1 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java @@ -1,14 +1,37 @@ package org.jabref.gui.autosaveandbackup; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import org.jabref.gui.LibraryTab; +import org.jabref.logic.FilePreferences; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.util.BackupFileType; import org.jabref.logic.util.Directories; +import org.jabref.logic.util.io.BackupFileUtil; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.groups.event.GroupUpdatedEvent; +import org.jabref.model.metadata.MetaData; +import org.jabref.model.metadata.event.MetaDataChangedEvent; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class BackupManagerTest { @@ -28,4 +51,141 @@ void backupFileNameIsCorrectlyGeneratedInAppDataDirectory() { // Pattern is "27182d3c--test.bib--", but the hashing is implemented differently on Linux than on Windows assertNotEquals("", bakPath); } + + @Test + void backupFileIsEqualForNonExistingBackup() throws Exception { + Path originalFile = Path.of(BackupManagerTest.class.getResource("no-autosave.bib").toURI()); + assertFalse(BackupManager.backupFileDiffers(originalFile, backupDir)); + } + + @Test + void backupFileIsEqual() throws Exception { + // Prepare test: Create backup file on "right" path + Path source = Path.of(BackupManagerTest.class.getResource("no-changes.bib.bak").toURI()); + Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(Path.of(BackupManagerTest.class.getResource("no-changes.bib").toURI()), BackupFileType.BACKUP, backupDir); + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + + Path originalFile = Path.of(BackupManagerTest.class.getResource("no-changes.bib").toURI()); + assertFalse(BackupManager.backupFileDiffers(originalFile, backupDir)); + } + + @Test + void backupFileDiffers() throws Exception { + // Prepare test: Create backup file on "right" path + Path source = Path.of(BackupManagerTest.class.getResource("changes.bib.bak").toURI()); + Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()), BackupFileType.BACKUP, backupDir); + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + + Path originalFile = Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()); + assertTrue(BackupManager.backupFileDiffers(originalFile, backupDir)); + } + + @Test + void correctBackupFileDeterminedForMultipleBakFiles() throws Exception { + Path noChangesBib = Path.of(BackupManagerTest.class.getResource("no-changes.bib").toURI()); + Path noChangesBibBak = Path.of(BackupManagerTest.class.getResource("no-changes.bib.bak").toURI()); + + // Prepare test: Create backup files on "right" path + // most recent file does not have any changes + Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(noChangesBib, BackupFileType.BACKUP, backupDir); + Files.copy(noChangesBibBak, target, StandardCopyOption.REPLACE_EXISTING); + + // create "older" .bak files containing changes + for (int i = 0; i < 10; i++) { + Path changesBibBak = Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()); + Path directory = backupDir; + String timeSuffix = "2020-02-03--00.00.0" + Integer.toString(i); + String fileName = BackupFileUtil.getUniqueFilePrefix(noChangesBib) + "--no-changes.bib--" + timeSuffix + ".bak"; + target = directory.resolve(fileName); + Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); + } + + Path originalFile = noChangesBib; + assertFalse(BackupManager.backupFileDiffers(originalFile, backupDir)); + } + + @Test + void bakFileWithNewerTimeStampLeadsToDiff() throws Exception { + Path changesBib = Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()); + Path changesBibBak = Path.of(BackupManagerTest.class.getResource("changes.bib.bak").toURI()); + + Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(changesBib, BackupFileType.BACKUP, backupDir); + Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); + + assertTrue(BackupManager.backupFileDiffers(changesBib, backupDir)); + } + + @Test + void bakFileWithOlderTimeStampDoesNotLeadToDiff() throws Exception { + Path changesBib = Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()); + Path changesBibBak = Path.of(BackupManagerTest.class.getResource("changes.bib.bak").toURI()); + + Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(changesBib, BackupFileType.BACKUP, backupDir); + Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); + + // Make .bak file very old + Files.setLastModifiedTime(target, FileTime.fromMillis(0)); + + assertFalse(BackupManager.backupFileDiffers(changesBib, backupDir)); + } + + @Test + void shouldNotCreateABackup(@TempDir Path customDir) throws Exception { + Path backupDir = customDir.resolve("subBackupDir"); + Files.createDirectories(backupDir); + + var database = new BibDatabaseContext(new BibDatabase()); + database.setDatabasePath(customDir.resolve("Bibfile.bib")); + + var preferences = mock(CliPreferences.class, Answers.RETURNS_DEEP_STUBS); + var filePreferences = mock(FilePreferences.class); + when(preferences.getFilePreferences()).thenReturn(filePreferences); + when(filePreferences.getBackupDirectory()).thenReturn(backupDir); + when(filePreferences.shouldCreateBackup()).thenReturn(false); + + BackupManager manager = BackupManager.start( + mock(LibraryTab.class), + database, + mock(BibEntryTypesManager.class, Answers.RETURNS_DEEP_STUBS), + preferences); + manager.listen(new MetaDataChangedEvent(new MetaData())); + + BackupManager.shutdown(database, filePreferences.getBackupDirectory(), filePreferences.shouldCreateBackup()); + + List files = Files.list(backupDir).toList(); + assertEquals(Collections.emptyList(), files); + } + + @Test + void shouldCreateABackup(@TempDir Path customDir) throws Exception { + Path backupDir = customDir.resolve("subBackupDir"); + Files.createDirectories(backupDir); + + var database = new BibDatabaseContext(new BibDatabase()); + database.setDatabasePath(customDir.resolve("Bibfile.bib")); + + var preferences = mock(CliPreferences.class, Answers.RETURNS_DEEP_STUBS); + var filePreferences = mock(FilePreferences.class); + when(preferences.getFilePreferences()).thenReturn(filePreferences); + when(filePreferences.getBackupDirectory()).thenReturn(backupDir); + when(filePreferences.shouldCreateBackup()).thenReturn(true); + + BackupManager manager = BackupManager.start( + mock(LibraryTab.class), + database, + mock(BibEntryTypesManager.class, Answers.RETURNS_DEEP_STUBS), + preferences); + manager.listen(new MetaDataChangedEvent(new MetaData())); + + Optional fullBackupPath = manager.determineBackupPathForNewBackup(backupDir); + fullBackupPath.ifPresent(manager::performBackup); + manager.listen(new GroupUpdatedEvent(new MetaData())); + + BackupManager.shutdown(database, backupDir, true); + + List files = Files.list(backupDir).sorted().toList(); + // we only know the first backup path because the second one is created on shutdown + // due to timing issues we cannot test that reliable + assertEquals(fullBackupPath.get(), files.getFirst()); + } } From 11f65c78174c4d596112ef3e66cbf2f6affb21cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 28 Nov 2024 00:01:37 +0100 Subject: [PATCH 30/84] first tests that are working --- .../autosaveandbackup/BackupManagerGit.java | 70 +++++--- .../BackupManagerGitTest.java | 149 +++++++++++++++++- 2 files changed, 194 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index cb623bf48d1..aa76fb52ac6 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -4,6 +4,8 @@ import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Date; @@ -23,6 +25,7 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; @@ -30,7 +33,6 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.treewalk.TreeWalk; -import org.eclipse.jgit.treewalk.filter.PathFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,20 +64,33 @@ public class BackupManagerGit { changeFilter = new CoarseChangeFilter(bibDatabaseContext); changeFilter.registerListener(this); + // Ensure the backup directory exists + File backupDir = preferences.getFilePreferences().getBackupDirectory().toFile(); + if (!backupDir.exists()) { + boolean dirCreated = backupDir.mkdirs(); + if (dirCreated) { + LOGGER.info("Created backup directory: " + backupDir); + } else { + LOGGER.error("Failed to create backup directory: " + backupDir); + } + } + // Initialize Git repository FileRepositoryBuilder builder = new FileRepositoryBuilder(); - git = new Git(builder.setGitDir(new File(preferences.getFilePreferences().getBackupDirectory().toFile(), ".git")) + git = new Git(builder.setGitDir(new File(backupDir, ".git")) .readEnvironment() .findGitDir() .build()); + if (git.getRepository().getObjectDatabase().exists()) { LOGGER.info("Git repository already exists"); } else { - Git.init().call(); + Git.init().setDirectory(backupDir).call(); // Explicitly set the directory LOGGER.info("Initialized new Git repository"); } } + /** * Starts a new BackupManagerGit instance and begins the backup task. * @@ -107,7 +122,7 @@ public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext b @SuppressWarnings({"checkstyle:NoWhitespaceBefore", "checkstyle:WhitespaceAfter"}) public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup, Path originalPath) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownJGit(backupDir, createBackup, originalPath)); + runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownGit(backupDir, createBackup, originalPath)); runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); } @@ -145,9 +160,8 @@ private void startBackupTask(Path backupDir, Path originalPath) { */ protected void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { - /* - needsBackup must be initialized - */ + LOGGER.info("Starting backup process for file: {}", originalPath); + needsBackup = BackupManagerGit.backupGitDiffers(backupDir, originalPath); if (!needsBackup) { return; @@ -198,25 +212,42 @@ public static void restoreBackup(Path originalPath, Path backupDir, ObjectId obj * @throws IOException if an I/O error occurs * @throws GitAPIException if a Git API error occurs */ - public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws IOException, GitAPIException { - File repoDir = backupDir.toFile(); Repository repository = new FileRepositoryBuilder() .setGitDir(new File(repoDir, ".git")) .build(); + try (Git git = new Git(repository)) { - ObjectId headCommitId = repository.resolve("HEAD"); // to get the latest commit id + // Resolve HEAD commit + ObjectId headCommitId = repository.resolve("HEAD"); if (headCommitId == null) { - // No commits in the repository, so there's no previous backup - return false; + // No commits in the repository; assume the file differs + return true; } - git.add().addFilepattern(originalPath.getFileName().toString()).call(); - String relativePath = backupDir.relativize(originalPath).toString(); - List diffs = git.diff() - .setPathFilter(PathFilter.create(relativePath)) // Utiliser PathFilter ici - .call(); - return !diffs.isEmpty(); + + // Attempt to retrieve the file content from the last commit + ObjectLoader loader; + try { + loader = repository.open(repository.resolve("HEAD:" + originalPath.getFileName().toString())); + } catch ( + MissingObjectException e) { + // File not found in the last commit; assume it differs + return true; + } + + // Read the content from the last commit + String committedContent = new String(loader.getBytes(), StandardCharsets.UTF_8); + + // Read the current content of the file + if (!Files.exists(originalPath)) { + // If the file doesn't exist in the working directory, it differs + return true; + } + String currentContent = Files.readString(originalPath, StandardCharsets.UTF_8); + + // Compare the current content to the committed content + return !currentContent.equals(committedContent); } } @@ -230,7 +261,6 @@ public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws * @throws IOException if an I/O error occurs * @throws GitAPIException if a Git API error occurs */ - public List showDiffers(Path originalPath, Path backupDir, String commitId) throws IOException, GitAPIException { File repoDir = backupDir.toFile(); @@ -347,7 +377,7 @@ public List> retrieveCommitDetails(List commits, Path ba * @param originalPath the original path of the file to be backed up */ - private void shutdownJGit(Path backupDir, boolean createBackup, Path originalPath) { + private void shutdownGit(Path backupDir, boolean createBackup, Path originalPath) { changeFilter.unregisterListener(this); changeFilter.shutdown(); executor.shutdown(); diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index a98b4c96f75..6f826bd6214 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -1,19 +1,157 @@ package org.jabref.gui.autosaveandbackup; -import java.io.IOException; -import java.nio.file.Path; - -import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.Git; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +import org.jabref.gui.LibraryTab; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntryTypesManager; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; class BackupManagerGitTest { + @TempDir + Path tempDir; Path backupDir; + Git git; + CliPreferences preferences; + BibEntryTypesManager entryTypesManager; + LibraryTab libraryTab; + BibDatabaseContext bibDatabaseContext; @BeforeEach - void setup(@TempDir Path tempDir) throws IOException, GitAPIException { + void setup() throws Exception { backupDir = tempDir.resolve("backup"); + Files.createDirectories(backupDir); + + // Initialize a mock Git repository in the parent directory of the backup directory + git = Git.init().setDirectory(backupDir.getParent().toFile()).call(); + + // Mock dependencies + preferences = mock(CliPreferences.class, Answers.RETURNS_DEEP_STUBS); + entryTypesManager = mock(BibEntryTypesManager.class, Answers.RETURNS_DEEP_STUBS); + libraryTab = mock(LibraryTab.class); + + when(preferences.getFilePreferences().getBackupDirectory()).thenReturn(backupDir); + } + + @Test + void testBackupManagerInitializesGitRepository() throws Exception { + // Ensure the backup directory exists + Path backupDir = tempDir.resolve("backup"); + Files.createDirectories(backupDir); + + // Create a BibDatabaseContext + Path databaseFile = tempDir.resolve("test.bib"); + Files.writeString(databaseFile, "Initial content"); + var bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); + bibDatabaseContext.setDatabasePath(databaseFile); + + // Initialize BackupManagerGit + BackupManagerGit manager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + + // Ensure the Git repository is initialized in the backup directory + assertTrue(Files.exists(backupDir.resolve(".git")), "Git repository not initialized in backup directory"); + } + + @Test + void testBackupGitDiffers_NoDifferences() throws Exception { + // Create a file in the original directory + Path originalFile = tempDir.resolve("test.bib"); + Files.writeString(originalFile, "Initial content"); + + // Create the backup directory if it doesn't exist + Files.createDirectories(backupDir); + + // Copy the original file to the backup directory + Path fileInBackupDir = backupDir.resolve("test.bib"); + Files.copy(originalFile, fileInBackupDir, StandardCopyOption.REPLACE_EXISTING); + + // Initialize the Git repository if not already done + if (!Files.exists(backupDir.resolve(".git"))) { + Git.init().setDirectory(backupDir.toFile()).call(); + } + + // Add and commit the file to the Git repository + Git git = Git.open(backupDir.toFile()); + git.add().addFilepattern("test.bib").call(); + git.commit().setMessage("Initial commit").call(); + + // Check that no differences are detected between the backup file and Git repository + boolean differs = BackupManagerGit.backupGitDiffers(fileInBackupDir, backupDir); + assertFalse(differs, "Differences were detected when there should be none."); + + // Clean up resources + BackupManagerGit.shutdown(bibDatabaseContext, backupDir, false, originalFile); + } + + @Test + void testBackupGitDiffers_WithDifferences() throws Exception { + // Create a file in the backup directory + Path originalFile = tempDir.resolve("test.bib"); + Files.writeString(originalFile, "Initial content"); + + // Copy the file to the backup directory + Path fileInBackupDir = backupDir.resolve("test.bib"); + Files.copy(originalFile, fileInBackupDir, StandardCopyOption.REPLACE_EXISTING); + + // Add and commit the file in the Git repository + git.add().addFilepattern(".").call(); + git.commit().setMessage("Initial commit").call(); + + // Modify the file in the backup directory + Files.writeString(fileInBackupDir, "Modified content"); + + // Check that differences are detected + boolean differs = BackupManagerGit.backupGitDiffers(fileInBackupDir, backupDir); + assertTrue(differs); + + BackupManagerGit.shutdown(bibDatabaseContext, backupDir, differs, originalFile); + } + + @Test + void testNoNewRepositoryCreated() throws Exception { + // Create a fake file to simulate the database file + Path databaseFile = tempDir.resolve("test.bib"); + Files.writeString(databaseFile, "Initial content"); + + // Set up BibDatabaseContext with the file path + var bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); + bibDatabaseContext.setDatabasePath(databaseFile); + + // Ensure the initial repository is created + BackupManagerGit initialManager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + assertTrue(Files.exists(backupDir.resolve(".git"))); // Ensure the repo exists + + // Use backupGitDiffers to check if the backup differs + boolean createBackup; + if (bibDatabaseContext.getDatabasePath().isPresent()) { + createBackup = BackupManagerGit.backupGitDiffers(bibDatabaseContext.getDatabasePath().get(), backupDir); + } else { + fail("Database path is not present"); + return; // Avoid further execution if the path is missing + } + + // Shutdown the initial manager + BackupManagerGit.shutdown(bibDatabaseContext, backupDir, createBackup, bibDatabaseContext.getDatabasePath().get()); + + // Create another instance pointing to the same backup directory + BackupManagerGit newManager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + assertTrue(Files.exists(backupDir.resolve(".git"))); // Ensure no new repo is created + + // Shutdown the new manager + BackupManagerGit.shutdown(bibDatabaseContext, backupDir, createBackup, bibDatabaseContext.getDatabasePath().get()); } } @@ -22,3 +160,4 @@ void setup(@TempDir Path tempDir) throws IOException, GitAPIException { + From 8ebb62aebbf953c30972b6e3a01b86358284a55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 28 Nov 2024 01:19:18 +0100 Subject: [PATCH 31/84] All the tests for BackupManagerGit + Launcher working --- .../autosaveandbackup/BackupManagerGit.java | 46 ++-- .../BackupManagerGitTest.java | 260 +++++++++++++++++- 2 files changed, 278 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index aa76fb52ac6..a7f46276a25 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -8,6 +8,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -90,7 +91,6 @@ public class BackupManagerGit { } } - /** * Starts a new BackupManagerGit instance and begins the backup task. * @@ -133,8 +133,7 @@ public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDi * @param originalPath the original path of the file to be backed up */ - @SuppressWarnings({"checkstyle:WhitespaceAfter", "checkstyle:LeftCurly"}) - private void startBackupTask(Path backupDir, Path originalPath) { + void startBackupTask(Path backupDir, Path originalPath) { executor.scheduleAtFixedRate( () -> { try { @@ -212,6 +211,7 @@ public static void restoreBackup(Path originalPath, Path backupDir, ObjectId obj * @throws IOException if an I/O error occurs * @throws GitAPIException if a Git API error occurs */ + public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws IOException, GitAPIException { File repoDir = backupDir.toFile(); Repository repository = new FileRepositoryBuilder() @@ -261,6 +261,7 @@ public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws * @throws IOException if an I/O error occurs * @throws GitAPIException if a Git API error occurs */ + public List showDiffers(Path originalPath, Path backupDir, String commitId) throws IOException, GitAPIException { File repoDir = backupDir.toFile(); @@ -280,43 +281,41 @@ need a class to show the last ten backups indicating: date/ size/ number of entr return diffFr.scan(oldCommit, newCommit); } - // n is a counter incrementing by 1 when the user asks to see older versions (packs of 10) -// and decrements by 1 when the user asks to see the pack of the 10 earlier versions -// the scroll down: n->n+1 ; the scroll up: n->n-1 - public List retreiveCommits(Path backupDir, int n) throws IOException, GitAPIException { + /** + * Retrieves the last n commits from the Git repository. + * + * @param backupDir the backup directory + * @param n the number of commits to retrieve + * @return a list of RevCommit objects representing the commits + * @throws IOException if an I/O error occurs + * @throws GitAPIException if a Git API error occurs + */ + + public List retrieveCommits(Path backupDir, int n) throws IOException, GitAPIException { List retrievedCommits = new ArrayList<>(); - // Open Git depository + // Open Git repository try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { - // Use RevWalk to go through all commits + // Use RevWalk to traverse commits try (RevWalk revWalk = new RevWalk(repository)) { - // Start from HEAD RevCommit startCommit = revWalk.parseCommit(repository.resolve("HEAD")); revWalk.markStart(startCommit); int count = 0; - int startIndex = n * 10; - int endIndex = startIndex + 9; - for (RevCommit commit : revWalk) { - // Ignore commits before starting index - if (count < startIndex) { - count++; - continue; - } - if (count >= endIndex) { - break; - } - // Add commits to the main list retrievedCommits.add(commit); count++; + if (count == n) { + break; // Stop after collecting the required number of commits + } } } } + // Reverse the list to have commits in the correct order + Collections.reverse(retrievedCommits); return retrievedCommits; } - /** * Retrieves detailed information about the specified commits. * @@ -327,7 +326,6 @@ public List retreiveCommits(Path backupDir, int n) throws IOException * @throws GitAPIException if a Git API error occurs */ - @SuppressWarnings("checkstyle:WhitespaceAround") public List> retrieveCommitDetails(List commits, Path backupDir) throws IOException, GitAPIException { List> commitDetails; try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index 6f826bd6214..34d384c3ea1 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -1,14 +1,22 @@ package org.jabref.gui.autosaveandbackup; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.mockito.Answers; +import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.StreamSupport; import org.jabref.gui.LibraryTab; import org.jabref.logic.preferences.CliPreferences; @@ -16,8 +24,13 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class BackupManagerGitTest { @@ -35,8 +48,8 @@ void setup() throws Exception { backupDir = tempDir.resolve("backup"); Files.createDirectories(backupDir); - // Initialize a mock Git repository in the parent directory of the backup directory - git = Git.init().setDirectory(backupDir.getParent().toFile()).call(); + // Initialize the Git repository inside backupDir + git = Git.init().setDirectory(backupDir.toFile()).call(); // Mock dependencies preferences = mock(CliPreferences.class, Answers.RETURNS_DEEP_STUBS); @@ -153,6 +166,245 @@ void testNoNewRepositoryCreated() throws Exception { // Shutdown the new manager BackupManagerGit.shutdown(bibDatabaseContext, backupDir, createBackup, bibDatabaseContext.getDatabasePath().get()); } + + @Test + void testStartMethod() throws Exception { + // Arrange: Set up necessary dependencies and mock objects + Path databaseFile = tempDir.resolve("test.bib"); + Files.writeString(databaseFile, "Initial content"); + + BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); + bibDatabaseContext.setDatabasePath(databaseFile); + + Path backupDirectory = tempDir.resolve("backup"); + Files.createDirectories(backupDirectory); + + // Mock preferences to return the backup directory + when(preferences.getFilePreferences().getBackupDirectory()).thenReturn(backupDirectory); + + // Act: Call the start method + BackupManagerGit backupManager = BackupManagerGit.start( + libraryTab, + bibDatabaseContext, + entryTypesManager, + preferences, + databaseFile + ); + + // Assert: Verify the outcomes + // Ensure a Git repository is initialized in the backup directory + assertTrue(Files.exists(backupDirectory.resolve(".git")), "Git repository not initialized"); + + // Use reflection to access the private `runningInstances` + Field runningInstancesField = BackupManagerGit.class.getDeclaredField("runningInstances"); + runningInstancesField.setAccessible(true); + @SuppressWarnings("unchecked") + Set runningInstances = (Set) runningInstancesField.get(null); + + // Ensure the backup manager is added to the running instances + assertTrue(runningInstances.contains(backupManager), "Backup manager not added to running instances"); + + // Clean up by shutting down the backup manager + BackupManagerGit.shutdown(bibDatabaseContext, backupDirectory, false, databaseFile); + } + + @Test + void testStartBackupTaskWithReflection() throws Exception { + // Arrange: Similar setup as above + Path databaseFile = tempDir.resolve("test.bib"); + Files.writeString(databaseFile, "Initial content"); + + BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); + bibDatabaseContext.setDatabasePath(databaseFile); + + Path backupDirectory = tempDir.resolve("backup"); + Files.createDirectories(backupDirectory); + + when(preferences.getFilePreferences().getBackupDirectory()).thenReturn(backupDirectory); + + BackupManagerGit backupManager = BackupManagerGit.start( + libraryTab, + bibDatabaseContext, + entryTypesManager, + preferences, + databaseFile + ); + + // Act: Start the backup task + // private void startBackupTask(Path backupDir, Path originalPath) + backupManager.startBackupTask(backupDirectory, databaseFile); + + // Simulate passage of time + Thread.sleep(100); + + // Use reflection to access the private `runningInstances` + Field runningInstancesField = BackupManagerGit.class.getDeclaredField("runningInstances"); + runningInstancesField.setAccessible(true); + @SuppressWarnings("unchecked") + Set runningInstances = (Set) runningInstancesField.get(null); + + // Assert: Verify the backup task is active + assertTrue(runningInstances.contains(backupManager), "Backup manager not added to running instances"); + + // Clean up + BackupManagerGit.shutdown(bibDatabaseContext, backupDirectory, false, databaseFile); + } + + @Test + void testRestoreBackup() throws Exception { + // Create multiple commits + ObjectId targetCommitId = null; + for (int i = 1; i <= 3; i++) { + Path file = backupDir.resolve("file" + i + ".txt"); + Files.writeString(file, "Content of file " + i); + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("Commit " + i).call(); + if (i == 2) { + // Save the ID of the second commit for testing + targetCommitId = commit.getId(); + } + } + + // Act: Call restoreBackup + BackupManagerGit.restoreBackup(tempDir.resolve("restored.txt"), backupDir, targetCommitId); + + // Assert: Verify the repository has a new commit after restoration + try (RevWalk revWalk = new RevWalk(git.getRepository())) { + RevCommit headCommit = revWalk.parseCommit(git.getRepository().resolve("HEAD")); + assertTrue( + headCommit.getShortMessage().contains("Restored content from commit: " + targetCommitId.getName()), + "A new commit should indicate the restoration" + ); + } + + // Assert: Ensure the file from the restored commit exists + assertTrue( + Files.exists(backupDir.resolve("file2.txt")), + "File from the restored commit should be present" + ); + + // Assert: Ensure files from later commits still exist + assertTrue( + Files.exists(backupDir.resolve("file3.txt")), + "File from later commits should still exist after restoration" + ); + + // Assert: Ensure earlier files still exist + assertTrue( + Files.exists(backupDir.resolve("file1.txt")), + "File from earlier commits should still exist after restoration" + ); + } + + @Test + void testRetrieveCommits() throws Exception { + // Create multiple commits in the Git repository + List commitIds = new ArrayList<>(); + for (int i = 1; i <= 10; i++) { + Path file = backupDir.resolve("file" + i + ".txt"); + Files.writeString(file, "Content of file " + i); + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("Commit " + i).call(); + commitIds.add(commit.getId()); + } + + // Act: Call retrieveCommits to get the last 5 commits + // Arrange: Similar setup as above + Path databaseFile = tempDir.resolve("test.bib"); + Files.writeString(databaseFile, "Initial content"); + + BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); + bibDatabaseContext.setDatabasePath(databaseFile); + + Path backupDirectory = tempDir.resolve("backup"); + Files.createDirectories(backupDirectory); + + when(preferences.getFilePreferences().getBackupDirectory()).thenReturn(backupDirectory); + + BackupManagerGit backupManager = BackupManagerGit.start( + libraryTab, + bibDatabaseContext, + entryTypesManager, + preferences, + databaseFile + ); + + List retrievedCommits = backupManager.retrieveCommits(backupDir, 5); + + // Assert: Verify the number of commits retrieved + assertEquals(5, retrievedCommits.size(), "Should retrieve the last 5 commits"); + + // Assert: Verify the content of the retrieved commits + for (int i = 0; i < 5; i++) { + RevCommit retrievedCommit = retrievedCommits.get(i); + int finalI = i; + RevCommit expectedCommit = StreamSupport.stream(git.log().call().spliterator(), false) + .filter(commit -> commit.getId().equals(commitIds.get(commitIds.size() - 5 + finalI))) + .findFirst() + .orElse(null); + + assertNotNull(expectedCommit, "Expected commit should exist in the repository"); + assertEquals(expectedCommit.getFullMessage(), retrievedCommit.getFullMessage(), + "Commit messages should match"); + assertEquals(expectedCommit.getId(), retrievedCommit.getId(), + "Commit IDs should match"); + } + } + + @Test + void testRetrieveCommitDetails() throws Exception { + // Create multiple commits in the Git repository + List commits = new ArrayList<>(); + for (int i = 1; i <= 5; i++) { + Path file = backupDir.resolve("file" + i + ".txt"); + Files.writeString(file, "Content of file " + i); + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("Commit " + i).call(); + commits.add(commit); + } + + // Act: Call retrieveCommitDetails to get the details of the commits + // Arrange: Similar setup as above + Path databaseFile = tempDir.resolve("test.bib"); + Files.writeString(databaseFile, "Initial content"); + + BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); + bibDatabaseContext.setDatabasePath(databaseFile); + + Path backupDirectory = tempDir.resolve("backup"); + Files.createDirectories(backupDirectory); + + when(preferences.getFilePreferences().getBackupDirectory()).thenReturn(backupDirectory); + + BackupManagerGit backupManager = BackupManagerGit.start( + libraryTab, + bibDatabaseContext, + entryTypesManager, + preferences, + databaseFile + ); + List> commitDetails = backupManager.retrieveCommitDetails(commits, backupDir); + + // Assert: Verify the number of commits + assertEquals(5, commitDetails.size(), "Should retrieve details for 5 commits"); + + // Assert: Verify the content of the retrieved commit details + for (int i = 0; i < 5; i++) { + List commitInfo = commitDetails.get(i); + RevCommit commit = commits.get(i); + + // Verify commit ID + assertEquals(commit.getName(), commitInfo.get(0), "Commit ID should match"); + + // Verify commit size (this is a bit tricky, so just check it's a valid size string) + String sizeFormatted = commitInfo.get(1); + assertTrue(sizeFormatted.contains("Ko") || sizeFormatted.contains("Mo"), "Commit size should be properly formatted"); + + // Verify commit date + String commitDate = commitInfo.get(2); + assertTrue(commitDate.contains(commit.getAuthorIdent().getWhen().toString()), "Commit date should match"); + } + } } From 562b285e8e9568a69f5cd8586f89aa2ae5b6264e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 28 Nov 2024 01:39:59 +0100 Subject: [PATCH 32/84] Add some LOGGERS to BackupManagerGit.java --- .../org/jabref/gui/autosaveandbackup/BackupManagerGit.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index a7f46276a25..a9e6eca54d5 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -105,6 +105,7 @@ public class BackupManagerGit { */ public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { + LOGGER.info("Starting backup manager for file: {}", originalPath); BackupManagerGit backupManagerGit = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); backupManagerGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); runningInstances.add(backupManagerGit); @@ -137,10 +138,10 @@ void startBackupTask(Path backupDir, Path originalPath) { executor.scheduleAtFixedRate( () -> { try { + LOGGER.info("Starting backup task for file: {}", originalPath); performBackup(backupDir, originalPath); - } catch ( - IOException | - GitAPIException e) { + LOGGER.info("Backup task completed for file: {}", originalPath); + } catch (IOException | GitAPIException e) { LOGGER.error("Error during backup", e); } }, From a2fa8b0b07f1031aa495e28b13ac205e475a7c2a Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Fri, 29 Nov 2024 21:41:15 +0100 Subject: [PATCH 33/84] Adapted UI to BackupManagerGit for ChoiceDialog --- .../autosaveandbackup/BackupManagerGit.java | 14 ++++++----- .../jabref/gui/backup/BackupChoiceDialog.java | 23 +++++++++++++++---- .../org/jabref/gui/backup/BackupEntry.java | 4 +++- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index a9e6eca54d5..dde2379a936 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -213,7 +213,7 @@ public static void restoreBackup(Path originalPath, Path backupDir, ObjectId obj * @throws GitAPIException if a Git API error occurs */ - public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws IOException, GitAPIException { + public static boolean backupGitDiffers(Path backupDir, Path originalPath) throws IOException, GitAPIException { File repoDir = backupDir.toFile(); Repository repository = new FileRepositoryBuilder() .setGitDir(new File(repoDir, ".git")) @@ -292,7 +292,7 @@ need a class to show the last ten backups indicating: date/ size/ number of entr * @throws GitAPIException if a Git API error occurs */ - public List retrieveCommits(Path backupDir, int n) throws IOException, GitAPIException { + public static List retrieveCommits(Path backupDir, int n) throws IOException, GitAPIException { List retrievedCommits = new ArrayList<>(); // Open Git repository try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { @@ -327,8 +327,8 @@ public List retrieveCommits(Path backupDir, int n) throws IOException * @throws GitAPIException if a Git API error occurs */ - public List> retrieveCommitDetails(List commits, Path backupDir) throws IOException, GitAPIException { - List> commitDetails; + public static List retrieveCommitDetails(List commits, Path backupDir) throws IOException, GitAPIException { + List commitDetails; try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { commitDetails = new ArrayList<>(); @@ -339,6 +339,7 @@ public List> retrieveCommitDetails(List commits, Path ba commitInfo.add(commit.getName()); // ID of commit // Get the size of files changes by the commit + String sizeFormatted; try (TreeWalk treeWalk = new TreeWalk(repository)) { treeWalk.addTree(commit.getTree()); treeWalk.setRecursive(true); @@ -350,7 +351,7 @@ public List> retrieveCommitDetails(List commits, Path ba } // Convert the size to Kb or Mb - String sizeFormatted = (totalSize > 1024 * 1024) + sizeFormatted = (totalSize > 1024 * 1024) ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) : String.format("%.2f Ko", totalSize / 1024.0); @@ -361,7 +362,8 @@ public List> retrieveCommitDetails(List commits, Path ba Date date = commit.getAuthorIdent().getWhen(); commitInfo.add(date.toString()); // Add list of details to the main list - commitDetails.add(commitInfo); + BackupEntry backupEntry = new BackupEntry(commit.getName(), date.toString(), sizeFormatted, 0); + commitDetails.add(backupEntry); } } diff --git a/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java b/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java index 435ac24c63f..01d42758fb4 100644 --- a/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java +++ b/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java @@ -1,6 +1,8 @@ package org.jabref.gui.backup; +import java.io.IOException; import java.nio.file.Path; +import java.util.List; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -12,9 +14,13 @@ import javafx.scene.control.TableView; import javafx.scene.layout.VBox; +import org.jabref.gui.autosaveandbackup.BackupManagerGit; import org.jabref.gui.util.BaseDialog; import org.jabref.logic.l10n.Localization; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.revwalk.RevCommit; + public class BackupChoiceDialog extends BaseDialog { public static final ButtonType RESTORE_BACKUP = new ButtonType(Localization.lang("Restore from backup"), ButtonBar.ButtonData.OK_DONE); public static final ButtonType IGNORE_BACKUP = new ButtonType(Localization.lang("Ignore backup"), ButtonBar.ButtonData.CANCEL_CLOSE); @@ -22,10 +28,13 @@ public class BackupChoiceDialog extends BaseDialog { private final ObservableList tableData = FXCollections.observableArrayList(); + private final Path backupDir; @FXML private final TableView backupTableView; public BackupChoiceDialog(Path originalPath, Path backupDir) { + this.backupDir = backupDir; + setTitle(Localization.lang("Choose backup file")); setHeaderText(null); getDialogPane().setMinHeight(150); @@ -37,7 +46,8 @@ public BackupChoiceDialog(Path originalPath, Path backupDir) { backupTableView = new TableView<>(); setupBackupTableView(); - pushSampleData(); + fetchBackupData(); + backupTableView.setItems(tableData); VBox contentBox = new VBox(); @@ -66,9 +76,14 @@ private void setupBackupTableView() { backupTableView.getColumns().addAll(dateColumn, sizeColumn, entriesColumn); } - private void pushSampleData() { - for (int i = 0; i < 50; i++) { - tableData.add(new BackupEntry("2023-11-" + (i + 1), i + " MB", i)); + private void fetchBackupData() { + try { + List commits = BackupManagerGit.retrieveCommits(backupDir, -1); + tableData.addAll(BackupManagerGit.retrieveCommitDetails(commits, backupDir)); + } catch ( + IOException | + GitAPIException e) { + throw new RuntimeException(e); } } } diff --git a/src/main/java/org/jabref/gui/backup/BackupEntry.java b/src/main/java/org/jabref/gui/backup/BackupEntry.java index 9a6c3fe817c..a500fed8e15 100644 --- a/src/main/java/org/jabref/gui/backup/BackupEntry.java +++ b/src/main/java/org/jabref/gui/backup/BackupEntry.java @@ -6,11 +6,13 @@ import javafx.beans.property.StringProperty; public class BackupEntry { + private final StringProperty name; private final StringProperty date; private final StringProperty size; private final IntegerProperty entries; - public BackupEntry(String date, String size, int entries) { + public BackupEntry(String name, String date, String size, int entries) { + this.name = new SimpleStringProperty(name); this.date = new SimpleStringProperty(date); this.size = new SimpleStringProperty(size); this.entries = new SimpleIntegerProperty(entries); From 1ad47a33b843a324a1c86815bc42f15186cd30e3 Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Fri, 29 Nov 2024 21:53:32 +0100 Subject: [PATCH 34/84] Adapted BackupUIManager to BackupManagerGit Review Backup to be added lated --- .../org/jabref/gui/backup/BackupEntry.java | 10 ++++- .../jabref/gui/dialogs/BackupUIManager.java | 40 +++++++------------ 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/main/java/org/jabref/gui/backup/BackupEntry.java b/src/main/java/org/jabref/gui/backup/BackupEntry.java index a500fed8e15..fbb39e27bc9 100644 --- a/src/main/java/org/jabref/gui/backup/BackupEntry.java +++ b/src/main/java/org/jabref/gui/backup/BackupEntry.java @@ -5,13 +5,17 @@ import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import org.eclipse.jgit.lib.ObjectId; + public class BackupEntry { + private final ObjectId id; private final StringProperty name; private final StringProperty date; private final StringProperty size; private final IntegerProperty entries; - public BackupEntry(String name, String date, String size, int entries) { + public BackupEntry(ObjectId id, String name, String date, String size, int entries) { + this.id = id; this.name = new SimpleStringProperty(name); this.date = new SimpleStringProperty(date); this.size = new SimpleStringProperty(size); @@ -41,4 +45,8 @@ public int getEntries() { public IntegerProperty entriesProperty() { return entries; } + + public ObjectId getId() { + return id; + } } diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 8e1f0227d45..c2f9687e86a 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -12,7 +12,6 @@ import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; -import org.jabref.gui.autosaveandbackup.BackupManager; import org.jabref.gui.autosaveandbackup.BackupManagerGit; import org.jabref.gui.backup.BackupChoiceDialog; import org.jabref.gui.backup.BackupChoiceDialogRecord; @@ -35,6 +34,8 @@ import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.ObjectId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,6 +54,7 @@ public static Optional showRestoreBackupDialog(DialogService dialo FileUpdateMonitor fileUpdateMonitor, UndoManager undoManager, StateManager stateManager) { + LOGGER.info("Show restore backup dialog"); var actionOpt = showBackupResolverDialog( dialogService, preferences.getExternalApplicationsPreferences(), @@ -60,7 +62,15 @@ public static Optional showRestoreBackupDialog(DialogService dialo preferences.getFilePreferences().getBackupDirectory()); return actionOpt.flatMap(action -> { if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { - BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); + try { + ObjectId commitId = BackupManagerGit.retrieveCommits(preferences.getFilePreferences().getBackupDirectory(), 1).getFirst().getId(); + BackupManagerGit.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory(), commitId); + } catch ( + IOException | + GitAPIException e + ) { + throw new RuntimeException(e); + } return Optional.empty(); } else if (action == BackupResolverDialog.REVIEW_BACKUP) { return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); @@ -71,7 +81,8 @@ public static Optional showRestoreBackupDialog(DialogService dialo } if (recordBackupChoice.get().action() == BackupChoiceDialog.RESTORE_BACKUP) { LOGGER.warn(recordBackupChoice.get().entry().getSize()); - BackupManager.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); + ObjectId commitId = recordBackupChoice.get().entry().getId(); + BackupManagerGit.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory(), commitId); return Optional.empty(); } if (recordBackupChoice.get().action() == BackupChoiceDialog.REVIEW_BACKUP) { @@ -82,30 +93,7 @@ public static Optional showRestoreBackupDialog(DialogService dialo return Optional.empty(); }); } - /* - return actionOpt.flatMap(action -> { - if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { - BackupManagerGit.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory()); - return Optional.empty(); - } else if (action == BackupResolverDialog.COMPARE_OLDER_BACKUP) { - var test = showBackupChoiceDialog(dialogService, originalPath, preferences); - if (test.isPresent()) { - LOGGER.warn(String.valueOf(test.get().getEntries())); - showBackupResolverDialog( - dialogService, - preferences.getExternalApplicationsPreferences(), - originalPath, - preferences.getFilePreferences().getBackupDirectory()); - } else { - LOGGER.warn("Empty"); - } - } else if (action == BackupResolverDialog.REVIEW_BACKUP) { - return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); - } - return Optional.empty(); - }); - */ private static Optional showBackupResolverDialog(DialogService dialogService, ExternalApplicationsPreferences externalApplicationsPreferences, Path originalPath, From a580032460b10c0c8e211ba629525f2aec5a0a9e Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Sat, 30 Nov 2024 22:00:22 +0100 Subject: [PATCH 35/84] Adapted Tests to UI adaptation --- .../BackupManagerGitTest.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index 34d384c3ea1..12e2ee2aaae 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -1,14 +1,5 @@ package org.jabref.gui.autosaveandbackup; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Answers; - import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Path; @@ -19,11 +10,21 @@ import java.util.stream.StreamSupport; import org.jabref.gui.LibraryTab; +import org.jabref.gui.backup.BackupEntry; import org.jabref.logic.preferences.CliPreferences; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -383,25 +384,25 @@ void testRetrieveCommitDetails() throws Exception { preferences, databaseFile ); - List> commitDetails = backupManager.retrieveCommitDetails(commits, backupDir); + List commitDetails = backupManager.retrieveCommitDetails(commits, backupDir); // Assert: Verify the number of commits assertEquals(5, commitDetails.size(), "Should retrieve details for 5 commits"); // Assert: Verify the content of the retrieved commit details for (int i = 0; i < 5; i++) { - List commitInfo = commitDetails.get(i); + BackupEntry commitInfo = commitDetails.get(i); RevCommit commit = commits.get(i); // Verify commit ID - assertEquals(commit.getName(), commitInfo.get(0), "Commit ID should match"); + assertEquals(commit.getName(), commitInfo.getName(), "Commit ID should match"); // Verify commit size (this is a bit tricky, so just check it's a valid size string) - String sizeFormatted = commitInfo.get(1); + String sizeFormatted = commitInfo.getSize(); assertTrue(sizeFormatted.contains("Ko") || sizeFormatted.contains("Mo"), "Commit size should be properly formatted"); // Verify commit date - String commitDate = commitInfo.get(2); + String commitDate = commitInfo.getDate(); assertTrue(commitDate.contains(commit.getAuthorIdent().getWhen().toString()), "Commit date should match"); } } From 9a1c3a1b4985549aa9d5a7a070373bc1bb7cf6d2 Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Sat, 30 Nov 2024 22:00:46 +0100 Subject: [PATCH 36/84] Added getter for name attribute of BackupEntry --- .../autosaveandbackup/BackupManagerGit.java | 151 ++++++++++++++++-- .../org/jabref/gui/backup/BackupEntry.java | 8 + 2 files changed, 145 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index dde2379a936..4d5f3ded6f5 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -4,6 +4,8 @@ import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; +import java.io.Writer; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -12,16 +14,34 @@ import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import javafx.scene.control.TableColumn; + import org.jabref.gui.LibraryTab; +import org.jabref.gui.backup.BackupEntry; +import org.jabref.gui.maintable.BibEntryTableViewModel; +import org.jabref.gui.maintable.columns.MainTableColumn; +import org.jabref.logic.bibtex.InvalidFieldValueException; +import org.jabref.logic.exporter.AtomicFileWriter; +import org.jabref.logic.exporter.BibWriter; +import org.jabref.logic.exporter.BibtexDatabaseWriter; +import org.jabref.logic.exporter.SelfContainedSaveConfiguration; import org.jabref.logic.preferences.CliPreferences; +import org.jabref.logic.util.BackupFileType; import org.jabref.logic.util.CoarseChangeFilter; +import org.jabref.logic.util.io.BackupFileUtil; +import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.metadata.SaveOrder; +import org.jabref.model.metadata.SelfContainedSaveOrder; +import org.apache.commons.io.FilenameUtils; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; @@ -162,18 +182,100 @@ void startBackupTask(Path backupDir, Path originalPath) { protected void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { LOGGER.info("Starting backup process for file: {}", originalPath); - needsBackup = BackupManagerGit.backupGitDiffers(backupDir, originalPath); + needsBackup = BackupManagerGit.backupGitDiffers(originalPath, backupDir); if (!needsBackup) { + LOGGER.info("No need for backup"); return; } - // Add and commit changes - git.add().addFilepattern(".").call(); - RevCommit commit = git.commit().setMessage("Backup at " + System.currentTimeMillis()).call(); - LOGGER.info("Committed backup: {}", commit.getId()); + // l'ordre dans lequel les entrées BibTeX doivent être écrites dans le fichier de sauvegarde. + // Si l'utilisateur a trié la table d'affichage des entrées dans JabRef, cet ordre est récupéré. + // Sinon, un ordre par défaut est utilisé. + // code similar to org.jabref.gui.exporter.SaveDatabaseAction.saveDatabase + SelfContainedSaveOrder saveOrder = bibDatabaseContext + .getMetaData().getSaveOrder() + .map(so -> { + if (so.getOrderType() == SaveOrder.OrderType.TABLE) { + // We need to "flatten out" SaveOrder.OrderType.TABLE as BibWriter does not have access to preferences + List> sortOrder = libraryTab.getMainTable().getSortOrder(); + return new SelfContainedSaveOrder( + SaveOrder.OrderType.SPECIFIED, + sortOrder.stream() + .filter(col -> col instanceof MainTableColumn) + .map(column -> ((MainTableColumn) column).getModel()) + .flatMap(model -> model.getSortCriteria().stream()) + .toList()); + } else { + return SelfContainedSaveOrder.of(so); + } + }) + .orElse(SaveOrder.getDefaultSaveOrder()); + + // Elle configure la sauvegarde, en indiquant qu'aucune sauvegarde supplémentaire (backup) ne doit être créée, + // que l'ordre de sauvegarde doit être celui défini, et que les entrées doivent être formatées selon les préférences + // utilisateur. + SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() + .withMakeBackup(false) + .withSaveOrder(saveOrder) + .withReformatOnSave(preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); + + // "Clone" the database context + // We "know" that "only" the BibEntries might be changed during writing (see [org.jabref.logic.exporter.BibDatabaseWriter.savePartOfDatabase]) + // Chaque entrée BibTeX (comme un article, livre, etc.) est clonée en utilisant la méthode clone(). + // Cela garantit que les modifications faites pendant la sauvegarde n'affecteront pas l'entrée originale. + List list = bibDatabaseContext.getDatabase().getEntries().stream() + .map(BibEntry::clone) + .map(BibEntry.class::cast) + .toList(); + BibDatabase bibDatabaseClone = new BibDatabase(list); + BibDatabaseContext bibDatabaseContextClone = new BibDatabaseContext(bibDatabaseClone, bibDatabaseContext.getMetaData()); + // Elle définit l'encodage à utiliser pour écrire le fichier. Cela garantit que les caractères spéciaux sont bien sauvegardés. + Charset encoding = bibDatabaseContext.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8); + // We want to have successful backups only + // Thus, we do not use a plain "FileWriter", but the "AtomicFileWriter" + // Example: What happens if one hard powers off the machine (or kills the jabref process) during writing of the backup? + // This MUST NOT create a broken backup file that then jabref wants to "restore" from? + try (Writer writer = new AtomicFileWriter(backupDir, encoding, false)) { + BibWriter bibWriter = new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator()); + new BibtexDatabaseWriter( + bibWriter, + saveConfiguration, + preferences.getFieldPreferences(), + preferences.getCitationKeyPatternPreferences(), + entryTypesManager) + // we save the clone to prevent the original database (and thus the UI) from being changed + .saveDatabase(bibDatabaseContextClone); + + // Add and commit changes + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("Backup at " + System.currentTimeMillis()).call(); + LOGGER.info("Committed backup: {}", commit.getId()); + + // Reset the backup flag + this.needsBackup = false; + } catch (IOException e) { + logIfCritical(backupDir, e); + } + } + + private void logIfCritical(Path backupPath, IOException e) { + Throwable innermostCause = e; + while (innermostCause.getCause() != null) { + innermostCause = innermostCause.getCause(); + } + boolean isErrorInField = innermostCause instanceof InvalidFieldValueException; + + // do not print errors in field values into the log during autosave + if (!isErrorInField) { + LOGGER.error("Error while saving to file {}", backupPath, e); + } + } - // Reset the backup flag - this.needsBackup = false; + /** + * Determines the most recent existing backup file name + */ + static Optional getLatestBackupPath(Path originalPath, Path backupDir) { + return BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); } /** @@ -183,7 +285,6 @@ protected void performBackup(Path backupDir, Path originalPath) throws IOExcepti * @param backupDir the backup directory * @param objectId the commit ID to restore from */ - public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { try { Git git = Git.open(backupDir.toFile()); @@ -196,6 +297,16 @@ public static void restoreBackup(Path originalPath, Path backupDir, ObjectId obj git.commit().setMessage("Restored content from commit: " + objectId.getName()).call(); LOGGER.info("Restored backup from Git repository and committed the changes"); + + try { + ObjectLoader loader = git.getRepository().open(objectId); + // Read the content from the last commit + String committedContent = new String(loader.getBytes(), StandardCharsets.UTF_8); + LOGGER.info(committedContent); + // Files.writeString(originalPath, committedContent, StandardCharsets.UTF_8); + } catch (IOException e) { + LOGGER.error("Error while restoring the backup file.", e); + } } catch ( IOException | GitAPIException e) { @@ -213,24 +324,33 @@ public static void restoreBackup(Path originalPath, Path backupDir, ObjectId obj * @throws GitAPIException if a Git API error occurs */ - public static boolean backupGitDiffers(Path backupDir, Path originalPath) throws IOException, GitAPIException { + public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws IOException, GitAPIException { File repoDir = backupDir.toFile(); Repository repository = new FileRepositoryBuilder() .setGitDir(new File(repoDir, ".git")) .build(); + LOGGER.info("ogPath : {}", originalPath.toString()); + LOGGER.info("backupDir : {}", backupDir.toString()); + try (Git git = new Git(repository)) { // Resolve HEAD commit ObjectId headCommitId = repository.resolve("HEAD"); if (headCommitId == null) { - // No commits in the repository; assume the file differs - return true; + // No commits in the repository; assume the file doesn't differ + return false; } // Attempt to retrieve the file content from the last commit ObjectLoader loader; try { - loader = repository.open(repository.resolve("HEAD:" + originalPath.getFileName().toString())); + LOGGER.info("File to check in repo : {}", originalPath.getFileName().toString()); + String fileToCheck = FilenameUtils.removeExtension(originalPath.getFileName().toString()) + ".bak"; + ObjectId fileId = repository.resolve("HEAD:" + fileToCheck); + if (fileId == null) { + return false; + } + loader = repository.open(fileId); } catch ( MissingObjectException e) { // File not found in the last commit; assume it differs @@ -242,11 +362,14 @@ public static boolean backupGitDiffers(Path backupDir, Path originalPath) throws // Read the current content of the file if (!Files.exists(originalPath)) { + LOGGER.warn("File doesn't exist: {}", originalPath); // If the file doesn't exist in the working directory, it differs return true; } String currentContent = Files.readString(originalPath, StandardCharsets.UTF_8); + LOGGER.info("Commited content : \n{}", committedContent); + LOGGER.info("Current content : \n{}", currentContent); // Compare the current content to the committed content return !currentContent.equals(committedContent); } @@ -362,7 +485,7 @@ public static List retrieveCommitDetails(List commits, P Date date = commit.getAuthorIdent().getWhen(); commitInfo.add(date.toString()); // Add list of details to the main list - BackupEntry backupEntry = new BackupEntry(commit.getName(), date.toString(), sizeFormatted, 0); + BackupEntry backupEntry = new BackupEntry(commit.getId(), commit.getName(), date.toString(), sizeFormatted, 0); commitDetails.add(backupEntry); } } @@ -385,7 +508,7 @@ private void shutdownGit(Path backupDir, boolean createBackup, Path originalPath if (createBackup) { try { - performBackup(backupDir, originalPath); + performBackup(originalPath, backupDir); } catch (IOException | GitAPIException e) { LOGGER.error("Error during shutdown backup", e); } diff --git a/src/main/java/org/jabref/gui/backup/BackupEntry.java b/src/main/java/org/jabref/gui/backup/BackupEntry.java index fbb39e27bc9..2ff14fbe728 100644 --- a/src/main/java/org/jabref/gui/backup/BackupEntry.java +++ b/src/main/java/org/jabref/gui/backup/BackupEntry.java @@ -49,4 +49,12 @@ public IntegerProperty entriesProperty() { public ObjectId getId() { return id; } + + public String getName() { + return name.get(); + } + + public StringProperty nameProperty() { + return name; + } } From 1013c3cc69ea2832cac64218e8b1c2e03cea810d Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Sat, 30 Nov 2024 22:02:57 +0100 Subject: [PATCH 37/84] Revert "Added getter for name attribute of BackupEntry" This reverts commit 9a1c3a1b4985549aa9d5a7a070373bc1bb7cf6d2. --- .../autosaveandbackup/BackupManagerGit.java | 151 ++---------------- .../org/jabref/gui/backup/BackupEntry.java | 8 - 2 files changed, 14 insertions(+), 145 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 4d5f3ded6f5..dde2379a936 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -4,8 +4,6 @@ import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -14,34 +12,16 @@ import java.util.Date; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import javafx.scene.control.TableColumn; - import org.jabref.gui.LibraryTab; -import org.jabref.gui.backup.BackupEntry; -import org.jabref.gui.maintable.BibEntryTableViewModel; -import org.jabref.gui.maintable.columns.MainTableColumn; -import org.jabref.logic.bibtex.InvalidFieldValueException; -import org.jabref.logic.exporter.AtomicFileWriter; -import org.jabref.logic.exporter.BibWriter; -import org.jabref.logic.exporter.BibtexDatabaseWriter; -import org.jabref.logic.exporter.SelfContainedSaveConfiguration; import org.jabref.logic.preferences.CliPreferences; -import org.jabref.logic.util.BackupFileType; import org.jabref.logic.util.CoarseChangeFilter; -import org.jabref.logic.util.io.BackupFileUtil; -import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.metadata.SaveOrder; -import org.jabref.model.metadata.SelfContainedSaveOrder; -import org.apache.commons.io.FilenameUtils; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; @@ -182,100 +162,18 @@ void startBackupTask(Path backupDir, Path originalPath) { protected void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { LOGGER.info("Starting backup process for file: {}", originalPath); - needsBackup = BackupManagerGit.backupGitDiffers(originalPath, backupDir); + needsBackup = BackupManagerGit.backupGitDiffers(backupDir, originalPath); if (!needsBackup) { - LOGGER.info("No need for backup"); return; } - // l'ordre dans lequel les entrées BibTeX doivent être écrites dans le fichier de sauvegarde. - // Si l'utilisateur a trié la table d'affichage des entrées dans JabRef, cet ordre est récupéré. - // Sinon, un ordre par défaut est utilisé. - // code similar to org.jabref.gui.exporter.SaveDatabaseAction.saveDatabase - SelfContainedSaveOrder saveOrder = bibDatabaseContext - .getMetaData().getSaveOrder() - .map(so -> { - if (so.getOrderType() == SaveOrder.OrderType.TABLE) { - // We need to "flatten out" SaveOrder.OrderType.TABLE as BibWriter does not have access to preferences - List> sortOrder = libraryTab.getMainTable().getSortOrder(); - return new SelfContainedSaveOrder( - SaveOrder.OrderType.SPECIFIED, - sortOrder.stream() - .filter(col -> col instanceof MainTableColumn) - .map(column -> ((MainTableColumn) column).getModel()) - .flatMap(model -> model.getSortCriteria().stream()) - .toList()); - } else { - return SelfContainedSaveOrder.of(so); - } - }) - .orElse(SaveOrder.getDefaultSaveOrder()); - - // Elle configure la sauvegarde, en indiquant qu'aucune sauvegarde supplémentaire (backup) ne doit être créée, - // que l'ordre de sauvegarde doit être celui défini, et que les entrées doivent être formatées selon les préférences - // utilisateur. - SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() - .withMakeBackup(false) - .withSaveOrder(saveOrder) - .withReformatOnSave(preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); - - // "Clone" the database context - // We "know" that "only" the BibEntries might be changed during writing (see [org.jabref.logic.exporter.BibDatabaseWriter.savePartOfDatabase]) - // Chaque entrée BibTeX (comme un article, livre, etc.) est clonée en utilisant la méthode clone(). - // Cela garantit que les modifications faites pendant la sauvegarde n'affecteront pas l'entrée originale. - List list = bibDatabaseContext.getDatabase().getEntries().stream() - .map(BibEntry::clone) - .map(BibEntry.class::cast) - .toList(); - BibDatabase bibDatabaseClone = new BibDatabase(list); - BibDatabaseContext bibDatabaseContextClone = new BibDatabaseContext(bibDatabaseClone, bibDatabaseContext.getMetaData()); - // Elle définit l'encodage à utiliser pour écrire le fichier. Cela garantit que les caractères spéciaux sont bien sauvegardés. - Charset encoding = bibDatabaseContext.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8); - // We want to have successful backups only - // Thus, we do not use a plain "FileWriter", but the "AtomicFileWriter" - // Example: What happens if one hard powers off the machine (or kills the jabref process) during writing of the backup? - // This MUST NOT create a broken backup file that then jabref wants to "restore" from? - try (Writer writer = new AtomicFileWriter(backupDir, encoding, false)) { - BibWriter bibWriter = new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator()); - new BibtexDatabaseWriter( - bibWriter, - saveConfiguration, - preferences.getFieldPreferences(), - preferences.getCitationKeyPatternPreferences(), - entryTypesManager) - // we save the clone to prevent the original database (and thus the UI) from being changed - .saveDatabase(bibDatabaseContextClone); - - // Add and commit changes - git.add().addFilepattern(".").call(); - RevCommit commit = git.commit().setMessage("Backup at " + System.currentTimeMillis()).call(); - LOGGER.info("Committed backup: {}", commit.getId()); - - // Reset the backup flag - this.needsBackup = false; - } catch (IOException e) { - logIfCritical(backupDir, e); - } - } - - private void logIfCritical(Path backupPath, IOException e) { - Throwable innermostCause = e; - while (innermostCause.getCause() != null) { - innermostCause = innermostCause.getCause(); - } - boolean isErrorInField = innermostCause instanceof InvalidFieldValueException; - - // do not print errors in field values into the log during autosave - if (!isErrorInField) { - LOGGER.error("Error while saving to file {}", backupPath, e); - } - } + // Add and commit changes + git.add().addFilepattern(".").call(); + RevCommit commit = git.commit().setMessage("Backup at " + System.currentTimeMillis()).call(); + LOGGER.info("Committed backup: {}", commit.getId()); - /** - * Determines the most recent existing backup file name - */ - static Optional getLatestBackupPath(Path originalPath, Path backupDir) { - return BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); + // Reset the backup flag + this.needsBackup = false; } /** @@ -285,6 +183,7 @@ static Optional getLatestBackupPath(Path originalPath, Path backupDir) { * @param backupDir the backup directory * @param objectId the commit ID to restore from */ + public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { try { Git git = Git.open(backupDir.toFile()); @@ -297,16 +196,6 @@ public static void restoreBackup(Path originalPath, Path backupDir, ObjectId obj git.commit().setMessage("Restored content from commit: " + objectId.getName()).call(); LOGGER.info("Restored backup from Git repository and committed the changes"); - - try { - ObjectLoader loader = git.getRepository().open(objectId); - // Read the content from the last commit - String committedContent = new String(loader.getBytes(), StandardCharsets.UTF_8); - LOGGER.info(committedContent); - // Files.writeString(originalPath, committedContent, StandardCharsets.UTF_8); - } catch (IOException e) { - LOGGER.error("Error while restoring the backup file.", e); - } } catch ( IOException | GitAPIException e) { @@ -324,33 +213,24 @@ public static void restoreBackup(Path originalPath, Path backupDir, ObjectId obj * @throws GitAPIException if a Git API error occurs */ - public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws IOException, GitAPIException { + public static boolean backupGitDiffers(Path backupDir, Path originalPath) throws IOException, GitAPIException { File repoDir = backupDir.toFile(); Repository repository = new FileRepositoryBuilder() .setGitDir(new File(repoDir, ".git")) .build(); - LOGGER.info("ogPath : {}", originalPath.toString()); - LOGGER.info("backupDir : {}", backupDir.toString()); - try (Git git = new Git(repository)) { // Resolve HEAD commit ObjectId headCommitId = repository.resolve("HEAD"); if (headCommitId == null) { - // No commits in the repository; assume the file doesn't differ - return false; + // No commits in the repository; assume the file differs + return true; } // Attempt to retrieve the file content from the last commit ObjectLoader loader; try { - LOGGER.info("File to check in repo : {}", originalPath.getFileName().toString()); - String fileToCheck = FilenameUtils.removeExtension(originalPath.getFileName().toString()) + ".bak"; - ObjectId fileId = repository.resolve("HEAD:" + fileToCheck); - if (fileId == null) { - return false; - } - loader = repository.open(fileId); + loader = repository.open(repository.resolve("HEAD:" + originalPath.getFileName().toString())); } catch ( MissingObjectException e) { // File not found in the last commit; assume it differs @@ -362,14 +242,11 @@ public static boolean backupGitDiffers(Path originalPath, Path backupDir) throws // Read the current content of the file if (!Files.exists(originalPath)) { - LOGGER.warn("File doesn't exist: {}", originalPath); // If the file doesn't exist in the working directory, it differs return true; } String currentContent = Files.readString(originalPath, StandardCharsets.UTF_8); - LOGGER.info("Commited content : \n{}", committedContent); - LOGGER.info("Current content : \n{}", currentContent); // Compare the current content to the committed content return !currentContent.equals(committedContent); } @@ -485,7 +362,7 @@ public static List retrieveCommitDetails(List commits, P Date date = commit.getAuthorIdent().getWhen(); commitInfo.add(date.toString()); // Add list of details to the main list - BackupEntry backupEntry = new BackupEntry(commit.getId(), commit.getName(), date.toString(), sizeFormatted, 0); + BackupEntry backupEntry = new BackupEntry(commit.getName(), date.toString(), sizeFormatted, 0); commitDetails.add(backupEntry); } } @@ -508,7 +385,7 @@ private void shutdownGit(Path backupDir, boolean createBackup, Path originalPath if (createBackup) { try { - performBackup(originalPath, backupDir); + performBackup(backupDir, originalPath); } catch (IOException | GitAPIException e) { LOGGER.error("Error during shutdown backup", e); } diff --git a/src/main/java/org/jabref/gui/backup/BackupEntry.java b/src/main/java/org/jabref/gui/backup/BackupEntry.java index 2ff14fbe728..fbb39e27bc9 100644 --- a/src/main/java/org/jabref/gui/backup/BackupEntry.java +++ b/src/main/java/org/jabref/gui/backup/BackupEntry.java @@ -49,12 +49,4 @@ public IntegerProperty entriesProperty() { public ObjectId getId() { return id; } - - public String getName() { - return name.get(); - } - - public StringProperty nameProperty() { - return name; - } } From 4daf7729507e50d6d41b81c2a0e132913f36281f Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Sat, 30 Nov 2024 22:05:40 +0100 Subject: [PATCH 38/84] Final fix --- .../jabref/gui/autosaveandbackup/BackupManagerGit.java | 3 ++- src/main/java/org/jabref/gui/backup/BackupEntry.java | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index dde2379a936..9e7462f3304 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -17,6 +17,7 @@ import java.util.concurrent.TimeUnit; import org.jabref.gui.LibraryTab; +import org.jabref.gui.backup.BackupEntry; import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.util.CoarseChangeFilter; import org.jabref.model.database.BibDatabaseContext; @@ -362,7 +363,7 @@ public static List retrieveCommitDetails(List commits, P Date date = commit.getAuthorIdent().getWhen(); commitInfo.add(date.toString()); // Add list of details to the main list - BackupEntry backupEntry = new BackupEntry(commit.getName(), date.toString(), sizeFormatted, 0); + BackupEntry backupEntry = new BackupEntry(commit.getId(), commit.getName(), date.toString(), sizeFormatted, 0); commitDetails.add(backupEntry); } } diff --git a/src/main/java/org/jabref/gui/backup/BackupEntry.java b/src/main/java/org/jabref/gui/backup/BackupEntry.java index fbb39e27bc9..2ff14fbe728 100644 --- a/src/main/java/org/jabref/gui/backup/BackupEntry.java +++ b/src/main/java/org/jabref/gui/backup/BackupEntry.java @@ -49,4 +49,12 @@ public IntegerProperty entriesProperty() { public ObjectId getId() { return id; } + + public String getName() { + return name.get(); + } + + public StringProperty nameProperty() { + return name; + } } From ecad5807eaf886e69d0984086c420db027ec7c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Sat, 30 Nov 2024 22:23:24 +0100 Subject: [PATCH 39/84] fixed the issue with detecting changes --- .../autosaveandbackup/BackupManagerGit.java | 63 +++++++++------ .../gui/exporter/SaveDatabaseAction.java | 2 +- .../BackupManagerGitTest.java | 77 ++++++------------- 3 files changed, 67 insertions(+), 75 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index dde2379a936..64457b6b6a7 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -17,6 +18,7 @@ import java.util.concurrent.TimeUnit; import org.jabref.gui.LibraryTab; +import org.jabref.gui.backup.BackupEntry; import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.util.CoarseChangeFilter; import org.jabref.model.database.BibDatabaseContext; @@ -66,7 +68,8 @@ public class BackupManagerGit { changeFilter.registerListener(this); // Ensure the backup directory exists - File backupDir = preferences.getFilePreferences().getBackupDirectory().toFile(); + Path backupDirPath = bibDatabaseContext.getDatabasePath().orElseThrow().getParent().resolve("backup"); + File backupDir = backupDirPath.toFile(); if (!backupDir.exists()) { boolean dirCreated = backupDir.mkdirs(); if (dirCreated) { @@ -76,7 +79,7 @@ public class BackupManagerGit { } } - // Initialize Git repository + // Initialize Git repository in the backup directory FileRepositoryBuilder builder = new FileRepositoryBuilder(); git = new Git(builder.setGitDir(new File(backupDir, ".git")) .readEnvironment() @@ -116,14 +119,15 @@ public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext b * Shuts down the BackupManagerGit instances associated with the given BibDatabaseContext. * * @param bibDatabaseContext the BibDatabaseContext - * @param backupDir the backup directory * @param createBackup whether to create a backup before shutting down * @param originalPath the original path of the file to be backed up */ + public static void shutdown(BibDatabaseContext bibDatabaseContext, boolean createBackup, Path originalPath) { + runningInstances.stream() + .filter(instance -> instance.bibDatabaseContext == bibDatabaseContext) + .forEach(backupManager -> backupManager.shutdownGit(bibDatabaseContext.getDatabasePath().orElseThrow().getParent().resolve("backup"), createBackup, originalPath)); - @SuppressWarnings({"checkstyle:NoWhitespaceBefore", "checkstyle:WhitespaceAfter"}) - public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup, Path originalPath) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(backupManager -> backupManager.shutdownGit(backupDir, createBackup, originalPath)); + // Remove the instances associated with the BibDatabaseContext after shutdown runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); } @@ -162,18 +166,22 @@ void startBackupTask(Path backupDir, Path originalPath) { protected void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { LOGGER.info("Starting backup process for file: {}", originalPath); - needsBackup = BackupManagerGit.backupGitDiffers(backupDir, originalPath); + // Check if the file needs a backup by comparing it to the last commit + boolean needsBackup = BackupManagerGit.backupGitDiffers(backupDir, originalPath); if (!needsBackup) { + LOGGER.info("No changes detected. Backup not required for file: {}", originalPath); return; } - // Add and commit changes - git.add().addFilepattern(".").call(); - RevCommit commit = git.commit().setMessage("Backup at " + System.currentTimeMillis()).call(); - LOGGER.info("Committed backup: {}", commit.getId()); + // Stage the file for commit + Path relativePath = backupDir.relativize(originalPath); // Ensure relative path for Git + git.add().addFilepattern(relativePath.toString().replace("\\", "/")).call(); - // Reset the backup flag - this.needsBackup = false; + // Commit the staged changes + RevCommit commit = git.commit() + .setMessage("Backup at " + Instant.now().toString()) + .call(); + LOGGER.info("Backup committed with ID: {}", commit.getId().getName()); } /** @@ -214,6 +222,7 @@ public static void restoreBackup(Path originalPath, Path backupDir, ObjectId obj */ public static boolean backupGitDiffers(Path backupDir, Path originalPath) throws IOException, GitAPIException { + // Ensure Git repository exists File repoDir = backupDir.toFile(); Repository repository = new FileRepositoryBuilder() .setGitDir(new File(repoDir, ".git")) @@ -227,12 +236,14 @@ public static boolean backupGitDiffers(Path backupDir, Path originalPath) throws return true; } + // Determine the path relative to the Git repository + Path relativePath = backupDir.relativize(originalPath); + // Attempt to retrieve the file content from the last commit ObjectLoader loader; try { - loader = repository.open(repository.resolve("HEAD:" + originalPath.getFileName().toString())); - } catch ( - MissingObjectException e) { + loader = repository.open(repository.resolve("HEAD:" + relativePath.toString().replace("\\", "/"))); + } catch (MissingObjectException e) { // File not found in the last commit; assume it differs return true; } @@ -362,7 +373,7 @@ public static List retrieveCommitDetails(List commits, P Date date = commit.getAuthorIdent().getWhen(); commitInfo.add(date.toString()); // Add list of details to the main list - BackupEntry backupEntry = new BackupEntry(commit.getName(), date.toString(), sizeFormatted, 0); + BackupEntry backupEntry = new BackupEntry(ObjectId.fromString(commit.getName()), commitInfo.get(0), commitInfo.get(2), commitInfo.get(1), 0); commitDetails.add(backupEntry); } } @@ -373,21 +384,29 @@ public static List retrieveCommitDetails(List commits, P /** * Shuts down the JGit components and optionally creates a backup. * - * @param backupDir the backup directory * @param createBackup whether to create a backup before shutting down * @param originalPath the original path of the file to be backed up */ private void shutdownGit(Path backupDir, boolean createBackup, Path originalPath) { - changeFilter.unregisterListener(this); - changeFilter.shutdown(); - executor.shutdown(); + // Unregister the listener and shut down the change filter + if (changeFilter != null) { + changeFilter.unregisterListener(this); + changeFilter.shutdown(); + } + + // Shut down the executor if it's not already shut down + if (executor != null && !executor.isShutdown()) { + executor.shutdown(); + } + // If backup is requested, ensure that we perform the Git-based backup if (createBackup) { try { + // Ensure the backup is a recent one by performing the Git commit performBackup(backupDir, originalPath); } catch (IOException | GitAPIException e) { - LOGGER.error("Error during shutdown backup", e); + LOGGER.error("Error during Git backup on shutdown", e); } } } diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index afefbd69318..e881631bda5 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -149,7 +149,7 @@ boolean saveAs(Path file, SaveDatabaseMode mode) throws GitAPIException, IOExcep if (databasePath.isPresent()) { // Close AutosaveManager, BackupManagerGit, and IndexManager for original library AutosaveManager.shutdown(context); - BackupManagerGit.shutdown(context, this.preferences.getFilePreferences().getBackupDirectory(), preferences.getFilePreferences().shouldCreateBackup(), databasePath.get()); + BackupManagerGit.shutdown(context, preferences.getFilePreferences().shouldCreateBackup(), databasePath.get()); libraryTab.closeIndexManger(); } diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index 34d384c3ea1..c015268ddba 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -10,6 +10,7 @@ import org.mockito.Answers; import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -19,6 +20,7 @@ import java.util.stream.StreamSupport; import org.jabref.gui.LibraryTab; +import org.jabref.gui.backup.BackupEntry; import org.jabref.logic.preferences.CliPreferences; import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseContext; @@ -57,6 +59,14 @@ void setup() throws Exception { libraryTab = mock(LibraryTab.class); when(preferences.getFilePreferences().getBackupDirectory()).thenReturn(backupDir); + + // Create a test file in the backup directory + Path testFile = backupDir.resolve("testfile.bib"); + Files.writeString(testFile, "This is a test file.", StandardCharsets.UTF_8); + + // Add and commit the test file to the repository + git.add().addFilepattern("testfile.bib").call(); + git.commit().setMessage("Initial commit").call(); } @Test @@ -80,57 +90,20 @@ void testBackupManagerInitializesGitRepository() throws Exception { @Test void testBackupGitDiffers_NoDifferences() throws Exception { - // Create a file in the original directory - Path originalFile = tempDir.resolve("test.bib"); - Files.writeString(originalFile, "Initial content"); - - // Create the backup directory if it doesn't exist - Files.createDirectories(backupDir); - - // Copy the original file to the backup directory - Path fileInBackupDir = backupDir.resolve("test.bib"); - Files.copy(originalFile, fileInBackupDir, StandardCopyOption.REPLACE_EXISTING); - - // Initialize the Git repository if not already done - if (!Files.exists(backupDir.resolve(".git"))) { - Git.init().setDirectory(backupDir.toFile()).call(); - } - - // Add and commit the file to the Git repository - Git git = Git.open(backupDir.toFile()); - git.add().addFilepattern("test.bib").call(); - git.commit().setMessage("Initial commit").call(); - - // Check that no differences are detected between the backup file and Git repository - boolean differs = BackupManagerGit.backupGitDiffers(fileInBackupDir, backupDir); - assertFalse(differs, "Differences were detected when there should be none."); - - // Clean up resources - BackupManagerGit.shutdown(bibDatabaseContext, backupDir, false, originalFile); + // Verify that there is no difference between the file and the last commit + Path testFile = backupDir.resolve("testfile.bib"); + boolean differs = BackupManagerGit.backupGitDiffers(backupDir, testFile); + assertFalse(differs, "Expected no difference between the file and the last commit"); } @Test void testBackupGitDiffers_WithDifferences() throws Exception { - // Create a file in the backup directory - Path originalFile = tempDir.resolve("test.bib"); - Files.writeString(originalFile, "Initial content"); - - // Copy the file to the backup directory - Path fileInBackupDir = backupDir.resolve("test.bib"); - Files.copy(originalFile, fileInBackupDir, StandardCopyOption.REPLACE_EXISTING); - - // Add and commit the file in the Git repository - git.add().addFilepattern(".").call(); - git.commit().setMessage("Initial commit").call(); - - // Modify the file in the backup directory - Files.writeString(fileInBackupDir, "Modified content"); - - // Check that differences are detected - boolean differs = BackupManagerGit.backupGitDiffers(fileInBackupDir, backupDir); - assertTrue(differs); + // Modify the test file to create differences + Path testFile = backupDir.resolve("testfile.bib"); + Files.writeString(testFile, "Modified content", StandardCharsets.UTF_8); - BackupManagerGit.shutdown(bibDatabaseContext, backupDir, differs, originalFile); + boolean differs = BackupManagerGit.backupGitDiffers(backupDir, testFile); + assertTrue(differs, "Expected differences between the file and the last commit"); } @Test @@ -157,14 +130,14 @@ void testNoNewRepositoryCreated() throws Exception { } // Shutdown the initial manager - BackupManagerGit.shutdown(bibDatabaseContext, backupDir, createBackup, bibDatabaseContext.getDatabasePath().get()); + BackupManagerGit.shutdown(bibDatabaseContext, createBackup, bibDatabaseContext.getDatabasePath().get()); // Create another instance pointing to the same backup directory BackupManagerGit newManager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); assertTrue(Files.exists(backupDir.resolve(".git"))); // Ensure no new repo is created // Shutdown the new manager - BackupManagerGit.shutdown(bibDatabaseContext, backupDir, createBackup, bibDatabaseContext.getDatabasePath().get()); + BackupManagerGit.shutdown(bibDatabaseContext, createBackup, bibDatabaseContext.getDatabasePath().get()); } @Test @@ -205,7 +178,7 @@ void testStartMethod() throws Exception { assertTrue(runningInstances.contains(backupManager), "Backup manager not added to running instances"); // Clean up by shutting down the backup manager - BackupManagerGit.shutdown(bibDatabaseContext, backupDirectory, false, databaseFile); + BackupManagerGit.shutdown(bibDatabaseContext, false, databaseFile); } @Test @@ -247,7 +220,7 @@ void testStartBackupTaskWithReflection() throws Exception { assertTrue(runningInstances.contains(backupManager), "Backup manager not added to running instances"); // Clean up - BackupManagerGit.shutdown(bibDatabaseContext, backupDirectory, false, databaseFile); + BackupManagerGit.shutdown(bibDatabaseContext, false, databaseFile); } @Test @@ -383,14 +356,14 @@ void testRetrieveCommitDetails() throws Exception { preferences, databaseFile ); - List> commitDetails = backupManager.retrieveCommitDetails(commits, backupDir); + List commitDetails = BackupManagerGit.retrieveCommitDetails(commits, backupDir); // Assert: Verify the number of commits assertEquals(5, commitDetails.size(), "Should retrieve details for 5 commits"); // Assert: Verify the content of the retrieved commit details for (int i = 0; i < 5; i++) { - List commitInfo = commitDetails.get(i); + List commitInfo = (List) commitDetails.get(i); RevCommit commit = commits.get(i); // Verify commit ID From 5d22a1a54dd9d5589e83ef7b500906e1c80fcfb9 Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Sat, 30 Nov 2024 23:44:02 +0100 Subject: [PATCH 40/84] editing the testRerieveCommitDetails test --- .../BackupManagerGitTest.java | 76 +++++++++---------- .../BackupManagerTestJGit.java | 4 + 2 files changed, 40 insertions(+), 40 deletions(-) create mode 100644 src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTestJGit.java diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index c015268ddba..01d0a1bbb56 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -1,19 +1,9 @@ package org.jabref.gui.autosaveandbackup; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Answers; - import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -26,6 +16,15 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -175,8 +174,8 @@ void testStartMethod() throws Exception { Set runningInstances = (Set) runningInstancesField.get(null); // Ensure the backup manager is added to the running instances - assertTrue(runningInstances.contains(backupManager), "Backup manager not added to running instances"); - +// Assert: Verify the backup task is active + assertTrue(runningInstances.contains(backupManager), "Backup manager should be added to running instances"); // Clean up by shutting down the backup manager BackupManagerGit.shutdown(bibDatabaseContext, false, databaseFile); } @@ -217,7 +216,8 @@ void testStartBackupTaskWithReflection() throws Exception { Set runningInstances = (Set) runningInstancesField.get(null); // Assert: Verify the backup task is active - assertTrue(runningInstances.contains(backupManager), "Backup manager not added to running instances"); + // Assert: Verify the backup task is active + assertTrue(runningInstances.contains(backupManager), "Backup manager should be added to running instances"); // Clean up BackupManagerGit.shutdown(bibDatabaseContext, false, databaseFile); @@ -228,7 +228,7 @@ void testRestoreBackup() throws Exception { // Create multiple commits ObjectId targetCommitId = null; for (int i = 1; i <= 3; i++) { - Path file = backupDir.resolve("file" + i + ".txt"); + Path file = backupDir.resolve("file" + i + ".bib"); Files.writeString(file, "Content of file " + i); git.add().addFilepattern(".").call(); RevCommit commit = git.commit().setMessage("Commit " + i).call(); @@ -239,34 +239,30 @@ void testRestoreBackup() throws Exception { } // Act: Call restoreBackup - BackupManagerGit.restoreBackup(tempDir.resolve("restored.txt"), backupDir, targetCommitId); - + BackupManagerGit.restoreBackup(tempDir.resolve("restored.bib"), backupDir, targetCommitId); + git.add().addFilepattern("restored.bib").call(); + git.commit().setMessage("Restored restored.bib from commit: " + targetCommitId.getName()).call(); // Assert: Verify the repository has a new commit after restoration try (RevWalk revWalk = new RevWalk(git.getRepository())) { - RevCommit headCommit = revWalk.parseCommit(git.getRepository().resolve("HEAD")); + + // Assert: Ensure the file from the restored commit exists assertTrue( - headCommit.getShortMessage().contains("Restored content from commit: " + targetCommitId.getName()), - "A new commit should indicate the restoration" + Files.exists(backupDir.resolve("file2.bib")), + "File from the restored commit should be present" ); - } - - // Assert: Ensure the file from the restored commit exists - assertTrue( - Files.exists(backupDir.resolve("file2.txt")), - "File from the restored commit should be present" - ); - // Assert: Ensure files from later commits still exist - assertTrue( - Files.exists(backupDir.resolve("file3.txt")), - "File from later commits should still exist after restoration" - ); + // Assert: Ensure files from later commits still exist + assertTrue( + Files.exists(backupDir.resolve("file3.bib")), + "File from later commits should still exist after restoration" + ); - // Assert: Ensure earlier files still exist - assertTrue( - Files.exists(backupDir.resolve("file1.txt")), - "File from earlier commits should still exist after restoration" - ); + // Assert: Ensure earlier files still exist + assertTrue( + Files.exists(backupDir.resolve("file1.bib")), + "File from earlier commits should still exist after restoration" + ); + } } @Test @@ -363,18 +359,18 @@ void testRetrieveCommitDetails() throws Exception { // Assert: Verify the content of the retrieved commit details for (int i = 0; i < 5; i++) { - List commitInfo = (List) commitDetails.get(i); + BackupEntry commitInfo = commitDetails.get(i); RevCommit commit = commits.get(i); // Verify commit ID - assertEquals(commit.getName(), commitInfo.get(0), "Commit ID should match"); + assertEquals(commit.getName(), commitInfo.getId().name(), "Commit ID should match"); // Verify commit size (this is a bit tricky, so just check it's a valid size string) - String sizeFormatted = commitInfo.get(1); + String sizeFormatted = commitInfo.getSize(); assertTrue(sizeFormatted.contains("Ko") || sizeFormatted.contains("Mo"), "Commit size should be properly formatted"); // Verify commit date - String commitDate = commitInfo.get(2); + String commitDate = commitInfo.getDate(); assertTrue(commitDate.contains(commit.getAuthorIdent().getWhen().toString()), "Commit date should match"); } } diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTestJGit.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTestJGit.java new file mode 100644 index 00000000000..971a51f8a73 --- /dev/null +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTestJGit.java @@ -0,0 +1,4 @@ +package org.jabref.gui.autosaveandbackup; + +public class BackupManagerTestJGit { +} From ae8583aefece24b31b7debdd89b1a890f9eba0ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Sun, 1 Dec 2024 00:16:52 +0100 Subject: [PATCH 41/84] Fix the shutdown / usage of old BackupManager --- src/main/java/org/jabref/gui/LibraryTab.java | 9 +-- .../BackupManagerGitTest.java | 75 ++++++++++--------- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 1cd6dfaaeef..6b1586ef9d9 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -42,7 +42,6 @@ import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.gui.autocompleter.SuggestionProviders; import org.jabref.gui.autosaveandbackup.AutosaveManager; -import org.jabref.gui.autosaveandbackup.BackupManager; import org.jabref.gui.autosaveandbackup.BackupManagerGit; import org.jabref.gui.collab.DatabaseChangeMonitor; import org.jabref.gui.dialogs.AutosaveUiManager; @@ -755,7 +754,7 @@ private boolean confirmClose() { } if (buttonType.equals(discardChanges)) { - BackupManager.discardBackup(bibDatabaseContext, preferences.getFilePreferences().getBackupDirectory()); + LOGGER.debug("Discarding changes"); return true; } @@ -803,9 +802,9 @@ private void onClosed(Event event) { LOGGER.error("Problem when shutting down autosave manager", e); } try { - BackupManager.shutdown(bibDatabaseContext, - preferences.getFilePreferences().getBackupDirectory(), - preferences.getFilePreferences().shouldCreateBackup()); + BackupManagerGit.shutdown(bibDatabaseContext, + preferences.getFilePreferences().shouldCreateBackup(), + preferences.getFilePreferences().getBackupDirectory()); } catch (RuntimeException e) { LOGGER.error("Problem when shutting down backup manager", e); } diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index 01d0a1bbb56..55769095124 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -1,5 +1,14 @@ package org.jabref.gui.autosaveandbackup; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -16,15 +25,6 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Answers; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -174,8 +174,8 @@ void testStartMethod() throws Exception { Set runningInstances = (Set) runningInstancesField.get(null); // Ensure the backup manager is added to the running instances -// Assert: Verify the backup task is active - assertTrue(runningInstances.contains(backupManager), "Backup manager should be added to running instances"); + assertTrue(runningInstances.contains(backupManager), "Backup manager not added to running instances"); + // Clean up by shutting down the backup manager BackupManagerGit.shutdown(bibDatabaseContext, false, databaseFile); } @@ -216,8 +216,7 @@ void testStartBackupTaskWithReflection() throws Exception { Set runningInstances = (Set) runningInstancesField.get(null); // Assert: Verify the backup task is active - // Assert: Verify the backup task is active - assertTrue(runningInstances.contains(backupManager), "Backup manager should be added to running instances"); + assertTrue(runningInstances.contains(backupManager), "Backup manager not added to running instances"); // Clean up BackupManagerGit.shutdown(bibDatabaseContext, false, databaseFile); @@ -228,7 +227,7 @@ void testRestoreBackup() throws Exception { // Create multiple commits ObjectId targetCommitId = null; for (int i = 1; i <= 3; i++) { - Path file = backupDir.resolve("file" + i + ".bib"); + Path file = backupDir.resolve("file" + i + ".txt"); Files.writeString(file, "Content of file " + i); git.add().addFilepattern(".").call(); RevCommit commit = git.commit().setMessage("Commit " + i).call(); @@ -239,30 +238,34 @@ void testRestoreBackup() throws Exception { } // Act: Call restoreBackup - BackupManagerGit.restoreBackup(tempDir.resolve("restored.bib"), backupDir, targetCommitId); - git.add().addFilepattern("restored.bib").call(); - git.commit().setMessage("Restored restored.bib from commit: " + targetCommitId.getName()).call(); + BackupManagerGit.restoreBackup(tempDir.resolve("restored.txt"), backupDir, targetCommitId); + // Assert: Verify the repository has a new commit after restoration try (RevWalk revWalk = new RevWalk(git.getRepository())) { - - // Assert: Ensure the file from the restored commit exists + RevCommit headCommit = revWalk.parseCommit(git.getRepository().resolve("HEAD")); assertTrue( - Files.exists(backupDir.resolve("file2.bib")), - "File from the restored commit should be present" + headCommit.getShortMessage().contains("Restored content from commit: " + targetCommitId.getName()), + "A new commit should indicate the restoration" ); + } - // Assert: Ensure files from later commits still exist - assertTrue( - Files.exists(backupDir.resolve("file3.bib")), - "File from later commits should still exist after restoration" - ); + // Assert: Ensure the file from the restored commit exists + assertTrue( + Files.exists(backupDir.resolve("file2.txt")), + "File from the restored commit should be present" + ); - // Assert: Ensure earlier files still exist - assertTrue( - Files.exists(backupDir.resolve("file1.bib")), - "File from earlier commits should still exist after restoration" - ); - } + // Assert: Ensure files from later commits still exist + assertTrue( + Files.exists(backupDir.resolve("file3.txt")), + "File from later commits should still exist after restoration" + ); + + // Assert: Ensure earlier files still exist + assertTrue( + Files.exists(backupDir.resolve("file1.txt")), + "File from earlier commits should still exist after restoration" + ); } @Test @@ -359,18 +362,18 @@ void testRetrieveCommitDetails() throws Exception { // Assert: Verify the content of the retrieved commit details for (int i = 0; i < 5; i++) { - BackupEntry commitInfo = commitDetails.get(i); + List commitInfo = (List) commitDetails.get(i); RevCommit commit = commits.get(i); // Verify commit ID - assertEquals(commit.getName(), commitInfo.getId().name(), "Commit ID should match"); + assertEquals(commit.getName(), commitInfo.get(0), "Commit ID should match"); // Verify commit size (this is a bit tricky, so just check it's a valid size string) - String sizeFormatted = commitInfo.getSize(); + String sizeFormatted = commitInfo.get(1); assertTrue(sizeFormatted.contains("Ko") || sizeFormatted.contains("Mo"), "Commit size should be properly formatted"); // Verify commit date - String commitDate = commitInfo.getDate(); + String commitDate = commitInfo.get(2); assertTrue(commitDate.contains(commit.getAuthorIdent().getWhen().toString()), "Commit date should match"); } } From 44f4e17a59d5ac4e71f2a0b0959f4ea37bd8192f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Sun, 1 Dec 2024 00:31:54 +0100 Subject: [PATCH 42/84] correct path --- .../java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 64457b6b6a7..adf4c5eba7e 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -68,7 +68,7 @@ public class BackupManagerGit { changeFilter.registerListener(this); // Ensure the backup directory exists - Path backupDirPath = bibDatabaseContext.getDatabasePath().orElseThrow().getParent().resolve("backup"); + Path backupDirPath = preferences.getFilePreferences().getBackupDirectory(); File backupDir = backupDirPath.toFile(); if (!backupDir.exists()) { boolean dirCreated = backupDir.mkdirs(); From 19b0ef5a75e71fefd33740d4079a0ee8754fe99a Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Sun, 1 Dec 2024 01:56:35 +0100 Subject: [PATCH 43/84] attempt to remove useless original path in some methods --- .../autosaveandbackup/BackupManagerGit.java | 44 +++++++++---------- .../BackupManagerGitTest.java | 38 +++++++--------- 2 files changed, 37 insertions(+), 45 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index adf4c5eba7e..376f8496a7c 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -101,16 +101,15 @@ public class BackupManagerGit { * @param bibDatabaseContext the BibDatabaseContext to be backed up * @param entryTypesManager the BibEntryTypesManager * @param preferences the CLI preferences - * @param originalPath the original path of the file to be backed up * @return the started BackupManagerGit instance * @throws IOException if an I/O error occurs * @throws GitAPIException if a Git API error occurs */ - public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { - LOGGER.info("Starting backup manager for file: {}", originalPath); + public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { + LOGGER.info("Starting backup manager for file: {}", bibDatabaseContext.getDatabasePath()); BackupManagerGit backupManagerGit = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - backupManagerGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); + backupManagerGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory()); runningInstances.add(backupManagerGit); return backupManagerGit; } @@ -135,16 +134,15 @@ public static void shutdown(BibDatabaseContext bibDatabaseContext, boolean creat * Starts the backup task that periodically checks for changes and commits them to the Git repository. * * @param backupDir the backup directory - * @param originalPath the original path of the file to be backed up */ - void startBackupTask(Path backupDir, Path originalPath) { + void startBackupTask(Path backupDir) { executor.scheduleAtFixedRate( () -> { try { - LOGGER.info("Starting backup task for file: {}", originalPath); - performBackup(backupDir, originalPath); - LOGGER.info("Backup task completed for file: {}", originalPath); + LOGGER.info("Starting backup task for file: {}", backupDir); + performBackup(backupDir); + LOGGER.info("Backup task completed for file: {}", backupDir); } catch (IOException | GitAPIException e) { LOGGER.error("Error during backup", e); } @@ -158,24 +156,23 @@ void startBackupTask(Path backupDir, Path originalPath) { * Performs the backup by checking for changes and committing them to the Git repository. * * @param backupDir the backup directory - * @param originalPath the original path of the file to be backed up * @throws IOException if an I/O error occurs * @throws GitAPIException if a Git API error occurs */ - protected void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { - LOGGER.info("Starting backup process for file: {}", originalPath); + protected void performBackup(Path backupDir) throws IOException, GitAPIException { + LOGGER.info("Starting backup process for file: {}", backupDir); // Check if the file needs a backup by comparing it to the last commit - boolean needsBackup = BackupManagerGit.backupGitDiffers(backupDir, originalPath); + boolean needsBackup = BackupManagerGit.backupGitDiffers(backupDir); if (!needsBackup) { - LOGGER.info("No changes detected. Backup not required for file: {}", originalPath); + LOGGER.info("No changes detected. Backup not required for file: {}", backupDir); return; } - // Stage the file for commit - Path relativePath = backupDir.relativize(originalPath); // Ensure relative path for Git - git.add().addFilepattern(relativePath.toString().replace("\\", "/")).call(); + // Stage the file for commit //Path relativePath = backupDir.relativize(originalPath); // Ensure relative path for Git + + git.add().addFilepattern(backupDir.toString().replace("\\", "/")).call(); // Commit the staged changes RevCommit commit = git.commit() @@ -214,14 +211,13 @@ public static void restoreBackup(Path originalPath, Path backupDir, ObjectId obj /** * Checks if there are differences between the original file and the backup. * - * @param originalPath the original path of the file * @param backupDir the backup directory * @return true if there are differences, false otherwise * @throws IOException if an I/O error occurs * @throws GitAPIException if a Git API error occurs */ - public static boolean backupGitDiffers(Path backupDir, Path originalPath) throws IOException, GitAPIException { + public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAPIException { // Ensure Git repository exists File repoDir = backupDir.toFile(); Repository repository = new FileRepositoryBuilder() @@ -237,12 +233,12 @@ public static boolean backupGitDiffers(Path backupDir, Path originalPath) throws } // Determine the path relative to the Git repository - Path relativePath = backupDir.relativize(originalPath); + // Path relativePath = backupDir.relativize(originalPath); // Attempt to retrieve the file content from the last commit ObjectLoader loader; try { - loader = repository.open(repository.resolve("HEAD:" + relativePath.toString().replace("\\", "/"))); + loader = repository.open(repository.resolve("HEAD:" + backupDir.toString().replace("\\", "/"))); } catch (MissingObjectException e) { // File not found in the last commit; assume it differs return true; @@ -252,11 +248,11 @@ public static boolean backupGitDiffers(Path backupDir, Path originalPath) throws String committedContent = new String(loader.getBytes(), StandardCharsets.UTF_8); // Read the current content of the file - if (!Files.exists(originalPath)) { + if (!Files.exists(backupDir)) { // If the file doesn't exist in the working directory, it differs return true; } - String currentContent = Files.readString(originalPath, StandardCharsets.UTF_8); + String currentContent = Files.readString(backupDir, StandardCharsets.UTF_8); // Compare the current content to the committed content return !currentContent.equals(committedContent); @@ -404,7 +400,7 @@ private void shutdownGit(Path backupDir, boolean createBackup, Path originalPath if (createBackup) { try { // Ensure the backup is a recent one by performing the Git commit - performBackup(backupDir, originalPath); + performBackup(backupDir); } catch (IOException | GitAPIException e) { LOGGER.error("Error during Git backup on shutdown", e); } diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index 55769095124..2f340a04236 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -1,14 +1,5 @@ package org.jabref.gui.autosaveandbackup; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Answers; - import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -25,6 +16,15 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntryTypesManager; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Answers; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -91,7 +91,7 @@ void testBackupManagerInitializesGitRepository() throws Exception { void testBackupGitDiffers_NoDifferences() throws Exception { // Verify that there is no difference between the file and the last commit Path testFile = backupDir.resolve("testfile.bib"); - boolean differs = BackupManagerGit.backupGitDiffers(backupDir, testFile); + boolean differs = BackupManagerGit.backupGitDiffers(backupDir); assertFalse(differs, "Expected no difference between the file and the last commit"); } @@ -101,7 +101,7 @@ void testBackupGitDiffers_WithDifferences() throws Exception { Path testFile = backupDir.resolve("testfile.bib"); Files.writeString(testFile, "Modified content", StandardCharsets.UTF_8); - boolean differs = BackupManagerGit.backupGitDiffers(backupDir, testFile); + boolean differs = BackupManagerGit.backupGitDiffers(backupDir); assertTrue(differs, "Expected differences between the file and the last commit"); } @@ -122,7 +122,7 @@ void testNoNewRepositoryCreated() throws Exception { // Use backupGitDiffers to check if the backup differs boolean createBackup; if (bibDatabaseContext.getDatabasePath().isPresent()) { - createBackup = BackupManagerGit.backupGitDiffers(bibDatabaseContext.getDatabasePath().get(), backupDir); + createBackup = BackupManagerGit.backupGitDiffers(backupDir); } else { fail("Database path is not present"); return; // Avoid further execution if the path is missing @@ -159,8 +159,7 @@ void testStartMethod() throws Exception { libraryTab, bibDatabaseContext, entryTypesManager, - preferences, - databaseFile + preferences ); // Assert: Verify the outcomes @@ -198,13 +197,12 @@ void testStartBackupTaskWithReflection() throws Exception { libraryTab, bibDatabaseContext, entryTypesManager, - preferences, - databaseFile + preferences ); // Act: Start the backup task // private void startBackupTask(Path backupDir, Path originalPath) - backupManager.startBackupTask(backupDirectory, databaseFile); + backupManager.startBackupTask(backupDirectory); // Simulate passage of time Thread.sleep(100); @@ -297,8 +295,7 @@ void testRetrieveCommits() throws Exception { libraryTab, bibDatabaseContext, entryTypesManager, - preferences, - databaseFile + preferences ); List retrievedCommits = backupManager.retrieveCommits(backupDir, 5); @@ -352,8 +349,7 @@ void testRetrieveCommitDetails() throws Exception { libraryTab, bibDatabaseContext, entryTypesManager, - preferences, - databaseFile + preferences ); List commitDetails = BackupManagerGit.retrieveCommitDetails(commits, backupDir); From 7f89d89e41328c4ad74ee1704ae3d5f6b8b8d1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Sun, 1 Dec 2024 02:08:42 +0100 Subject: [PATCH 44/84] CORRECTING PATHS ! a clean launch (except for the 1st exception) --- src/main/java/org/jabref/gui/LibraryTab.java | 2 +- .../autosaveandbackup/BackupManagerGit.java | 104 ++++++++++-------- .../jabref/gui/dialogs/BackupUIManager.java | 4 +- .../importer/actions/OpenDatabaseAction.java | 10 +- .../BackupManagerGitTest.java | 14 +-- 5 files changed, 74 insertions(+), 60 deletions(-) diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 6b1586ef9d9..181cc9fa5bb 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -374,7 +374,7 @@ public void installAutosaveManagerAndBackupManager() throws GitAPIException, IOE autosaveManager.registerListener(new AutosaveUiManager(this, dialogService, preferences, entryTypesManager)); } if (isDatabaseReadyForBackup(bibDatabaseContext) && preferences.getFilePreferences().shouldCreateBackup()) { - BackupManagerGit.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences, bibDatabaseContext.getDatabasePath().get().getParent()); + BackupManagerGit.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences); } } diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index adf4c5eba7e..2f4ae53ea29 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -16,6 +16,8 @@ import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jabref.gui.LibraryTab; import org.jabref.gui.backup.BackupEntry; @@ -69,6 +71,8 @@ public class BackupManagerGit { // Ensure the backup directory exists Path backupDirPath = preferences.getFilePreferences().getBackupDirectory(); + LOGGER.info("backupDirPath" + backupDirPath); + File backupDir = backupDirPath.toFile(); if (!backupDir.exists()) { boolean dirCreated = backupDir.mkdirs(); @@ -97,20 +101,18 @@ public class BackupManagerGit { /** * Starts a new BackupManagerGit instance and begins the backup task. * - * @param libraryTab the library tab + * @param libraryTab the library tab * @param bibDatabaseContext the BibDatabaseContext to be backed up - * @param entryTypesManager the BibEntryTypesManager - * @param preferences the CLI preferences - * @param originalPath the original path of the file to be backed up + * @param entryTypesManager the BibEntryTypesManager + * @param preferences the CLI preferences * @return the started BackupManagerGit instance - * @throws IOException if an I/O error occurs + * @throws IOException if an I/O error occurs * @throws GitAPIException if a Git API error occurs */ - public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences, Path originalPath) throws IOException, GitAPIException { - LOGGER.info("Starting backup manager for file: {}", originalPath); + public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { BackupManagerGit backupManagerGit = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - backupManagerGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), originalPath); + backupManagerGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory()); runningInstances.add(backupManagerGit); return backupManagerGit; } @@ -129,22 +131,21 @@ public static void shutdown(BibDatabaseContext bibDatabaseContext, boolean creat // Remove the instances associated with the BibDatabaseContext after shutdown runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); + LOGGER.info("Shut down backup manager for file: {}", originalPath); } /** * Starts the backup task that periodically checks for changes and commits them to the Git repository. * * @param backupDir the backup directory - * @param originalPath the original path of the file to be backed up */ - void startBackupTask(Path backupDir, Path originalPath) { + void startBackupTask(Path backupDir) { + LOGGER.info("Initializing backup task for directory: {} and file: {}", backupDir); executor.scheduleAtFixedRate( () -> { try { - LOGGER.info("Starting backup task for file: {}", originalPath); - performBackup(backupDir, originalPath); - LOGGER.info("Backup task completed for file: {}", originalPath); + performBackup(backupDir); } catch (IOException | GitAPIException e) { LOGGER.error("Error during backup", e); } @@ -152,30 +153,26 @@ void startBackupTask(Path backupDir, Path originalPath) { DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, TimeUnit.SECONDS); + LOGGER.info("Backup task scheduled with a delay of {} seconds", DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS); } /** * Performs the backup by checking for changes and committing them to the Git repository. * * @param backupDir the backup directory - * @param originalPath the original path of the file to be backed up - * @throws IOException if an I/O error occurs + * @throws IOException if an I/O error occurs * @throws GitAPIException if a Git API error occurs */ - protected void performBackup(Path backupDir, Path originalPath) throws IOException, GitAPIException { - LOGGER.info("Starting backup process for file: {}", originalPath); - + protected void performBackup(Path backupDir) throws IOException, GitAPIException { // Check if the file needs a backup by comparing it to the last commit - boolean needsBackup = BackupManagerGit.backupGitDiffers(backupDir, originalPath); + boolean needsBackup = BackupManagerGit.backupGitDiffers(backupDir); if (!needsBackup) { - LOGGER.info("No changes detected. Backup not required for file: {}", originalPath); return; } // Stage the file for commit - Path relativePath = backupDir.relativize(originalPath); // Ensure relative path for Git - git.add().addFilepattern(relativePath.toString().replace("\\", "/")).call(); + git.add().addFilepattern(".").call(); // Commit the staged changes RevCommit commit = git.commit() @@ -187,12 +184,11 @@ protected void performBackup(Path backupDir, Path originalPath) throws IOExcepti /** * Restores the backup from the specified commit. * - * @param originalPath the original path of the file to be restored * @param backupDir the backup directory * @param objectId the commit ID to restore from */ - public static void restoreBackup(Path originalPath, Path backupDir, ObjectId objectId) { + public static void restoreBackup(Path backupDir, ObjectId objectId) { try { Git git = Git.open(backupDir.toFile()); @@ -214,14 +210,15 @@ public static void restoreBackup(Path originalPath, Path backupDir, ObjectId obj /** * Checks if there are differences between the original file and the backup. * - * @param originalPath the original path of the file * @param backupDir the backup directory * @return true if there are differences, false otherwise * @throws IOException if an I/O error occurs * @throws GitAPIException if a Git API error occurs */ - public static boolean backupGitDiffers(Path backupDir, Path originalPath) throws IOException, GitAPIException { + public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAPIException { + LOGGER.info("Checking if backup differs for directory: {}", backupDir); + // Ensure Git repository exists File repoDir = backupDir.toFile(); Repository repository = new FileRepositoryBuilder() @@ -233,34 +230,44 @@ public static boolean backupGitDiffers(Path backupDir, Path originalPath) throws ObjectId headCommitId = repository.resolve("HEAD"); if (headCommitId == null) { // No commits in the repository; assume the file differs + LOGGER.info("No commits found in the repository. Assuming the file differs."); return true; } - // Determine the path relative to the Git repository - Path relativePath = backupDir.relativize(originalPath); + // Iterate over files in the backup directory + try (Stream paths = Files.walk(backupDir)) { + for (Path path : paths.filter(Files::isRegularFile).collect(Collectors.toList())) { + // Determine the path relative to the Git repository + Path relativePath = backupDir.relativize(path); + LOGGER.info("Relative path: {}", relativePath); - // Attempt to retrieve the file content from the last commit - ObjectLoader loader; - try { - loader = repository.open(repository.resolve("HEAD:" + relativePath.toString().replace("\\", "/"))); - } catch (MissingObjectException e) { - // File not found in the last commit; assume it differs - return true; - } + // Attempt to retrieve the file content from the last commit + ObjectLoader loader; + try { + loader = repository.open(repository.resolve("HEAD:" + relativePath.toString().replace("\\", "/"))); + } catch (MissingObjectException e) { + // File not found in the last commit; assume it differs + LOGGER.info("File not found in the last commit. Assuming the file differs."); + return true; + } - // Read the content from the last commit - String committedContent = new String(loader.getBytes(), StandardCharsets.UTF_8); + // Read the content from the last commit + String committedContent = new String(loader.getBytes(), StandardCharsets.UTF_8); - // Read the current content of the file - if (!Files.exists(originalPath)) { - // If the file doesn't exist in the working directory, it differs - return true; - } - String currentContent = Files.readString(originalPath, StandardCharsets.UTF_8); + // Read the current content of the file + String currentContent = Files.readString(path, StandardCharsets.UTF_8); - // Compare the current content to the committed content - return !currentContent.equals(committedContent); + // Compare the current content to the committed content + if (!currentContent.equals(committedContent)) { + LOGGER.info("Current content of file {} differs from the committed content.", path); + return true; + } + } + } } + + LOGGER.info("All files in the backup directory match the committed content."); + return false; } /** @@ -393,18 +400,21 @@ private void shutdownGit(Path backupDir, boolean createBackup, Path originalPath if (changeFilter != null) { changeFilter.unregisterListener(this); changeFilter.shutdown(); + LOGGER.info("Shut down change filter for file: {}", originalPath); } // Shut down the executor if it's not already shut down if (executor != null && !executor.isShutdown()) { executor.shutdown(); + LOGGER.info("Shut down backup task for file: {}", originalPath); } // If backup is requested, ensure that we perform the Git-based backup if (createBackup) { try { // Ensure the backup is a recent one by performing the Git commit - performBackup(backupDir, originalPath); + performBackup(backupDir); + LOGGER.info("Backup created on shutdown for file: {}", originalPath); } catch (IOException | GitAPIException e) { LOGGER.error("Error during Git backup on shutdown", e); } diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index c2f9687e86a..c344ccb9069 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -64,7 +64,7 @@ public static Optional showRestoreBackupDialog(DialogService dialo if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { try { ObjectId commitId = BackupManagerGit.retrieveCommits(preferences.getFilePreferences().getBackupDirectory(), 1).getFirst().getId(); - BackupManagerGit.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory(), commitId); + BackupManagerGit.restoreBackup(preferences.getFilePreferences().getBackupDirectory(), commitId); } catch ( IOException | GitAPIException e @@ -82,7 +82,7 @@ public static Optional showRestoreBackupDialog(DialogService dialo if (recordBackupChoice.get().action() == BackupChoiceDialog.RESTORE_BACKUP) { LOGGER.warn(recordBackupChoice.get().entry().getSize()); ObjectId commitId = recordBackupChoice.get().entry().getId(); - BackupManagerGit.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory(), commitId); + BackupManagerGit.restoreBackup(preferences.getFilePreferences().getBackupDirectory(), commitId); return Optional.empty(); } if (recordBackupChoice.get().action() == BackupChoiceDialog.REVIEW_BACKUP) { 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 2c3436e2476..bf6fb1ff84c 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -96,7 +96,9 @@ public OpenDatabaseAction(LibraryTabContainer tabContainer, public static void performPostOpenActions(ParserResult result, DialogService dialogService, CliPreferences preferences) { for (GUIPostOpenAction action : OpenDatabaseAction.POST_OPEN_ACTIONS) { + LOGGER.info("Performing post open action: {}", action.getClass().getSimpleName()); if (action.isActionNecessary(result, dialogService, preferences)) { + LOGGER.info("Action is necessary"); action.performAction(result, dialogService, preferences); } } @@ -104,6 +106,7 @@ public static void performPostOpenActions(ParserResult result, DialogService dia @Override public void execute() { + LOGGER.info("OpenDatabaseAction"); List filesToOpen = getFilesToOpen(); openFiles(new ArrayList<>(filesToOpen)); } @@ -118,6 +121,7 @@ List getFilesToOpen() { } catch (IllegalArgumentException e) { // See https://github.com/JabRef/jabref/issues/10548 for details // Rebuild a new config with the home directory + LOGGER.error("Error while opening file dialog", e); FileDialogConfiguration homeDirectoryConfig = getFileDialogConfiguration(Directories.getUserDirectory()); filesToOpen = dialogService.showFileOpenDialogAndGetMultipleFiles(homeDirectoryConfig); } @@ -242,16 +246,19 @@ private void openTheFile(Path file) { } private ParserResult loadDatabase(Path file) throws Exception { + LOGGER.info("Opening {}", file); Path fileToLoad = file.toAbsolutePath(); dialogService.notify(Localization.lang("Opening") + ": '" + file + "'"); + LOGGER.info("Opening {}", fileToLoad); preferences.getFilePreferences().setWorkingDirectory(fileToLoad.getParent()); Path backupDir = preferences.getFilePreferences().getBackupDirectory(); ParserResult parserResult = null; - if (BackupManagerGit.backupGitDiffers(fileToLoad, backupDir)) { + if (BackupManagerGit.backupGitDiffers(backupDir)) { // In case the backup differs, ask the user what to do. + LOGGER.info("Backup differs from saved file, 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, preferences, fileUpdateMonitor, undoManager, stateManager) .orElse(null); @@ -260,6 +267,7 @@ private ParserResult loadDatabase(Path file) throws Exception { try { if (parserResult == null) { // No backup was restored, do the "normal" loading + LOGGER.info("No backup was restored, do the \"normal\" loading"); parserResult = OpenDatabase.loadDatabase(fileToLoad, preferences.getImportFormatPreferences(), fileUpdateMonitor); diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index 55769095124..1fc8a24c237 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -159,8 +159,7 @@ void testStartMethod() throws Exception { libraryTab, bibDatabaseContext, entryTypesManager, - preferences, - databaseFile + preferences ); // Assert: Verify the outcomes @@ -198,13 +197,12 @@ void testStartBackupTaskWithReflection() throws Exception { libraryTab, bibDatabaseContext, entryTypesManager, - preferences, - databaseFile + preferences ); // Act: Start the backup task // private void startBackupTask(Path backupDir, Path originalPath) - backupManager.startBackupTask(backupDirectory, databaseFile); + backupManager.startBackupTask(backupDirectory); // Simulate passage of time Thread.sleep(100); @@ -297,8 +295,7 @@ void testRetrieveCommits() throws Exception { libraryTab, bibDatabaseContext, entryTypesManager, - preferences, - databaseFile + preferences ); List retrievedCommits = backupManager.retrieveCommits(backupDir, 5); @@ -352,8 +349,7 @@ void testRetrieveCommitDetails() throws Exception { libraryTab, bibDatabaseContext, entryTypesManager, - preferences, - databaseFile + preferences ); List commitDetails = BackupManagerGit.retrieveCommitDetails(commits, backupDir); From 119e5d038bfc259920664803f2fc06d6e3180619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Sun, 1 Dec 2024 02:26:13 +0100 Subject: [PATCH 45/84] Fixed the dialog of the 1st exception) --- .../org/jabref/gui/autosaveandbackup/BackupManagerGit.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 2f4ae53ea29..066cd1c75e5 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -244,7 +244,12 @@ public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAP // Attempt to retrieve the file content from the last commit ObjectLoader loader; try { - loader = repository.open(repository.resolve("HEAD:" + relativePath.toString().replace("\\", "/"))); + ObjectId objectId = repository.resolve("HEAD:" + relativePath.toString().replace("\\", "/")); + if (objectId == null) { + LOGGER.info("Object ID for path {} is null. Assuming the file differs.", relativePath); + return true; + } + loader = repository.open(objectId); } catch (MissingObjectException e) { // File not found in the last commit; assume it differs LOGGER.info("File not found in the last commit. Assuming the file differs."); From a7d271b97491a03bc354e7518e17f09f55ec545e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Sun, 1 Dec 2024 02:52:42 +0100 Subject: [PATCH 46/84] Fixed the infinite loops of commits --- .../org/jabref/gui/autosaveandbackup/BackupManagerGit.java | 7 ++++++- src/main/java/org/jabref/gui/dialogs/BackupUIManager.java | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 066cd1c75e5..ff113303e7d 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -237,6 +237,11 @@ public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAP // Iterate over files in the backup directory try (Stream paths = Files.walk(backupDir)) { for (Path path : paths.filter(Files::isRegularFile).collect(Collectors.toList())) { + // Exclude internal Git files (e.g., .git directory files) + if (path.toString().contains(".git")) { + continue; // Skip Git internals like .git/config + } + // Determine the path relative to the Git repository Path relativePath = backupDir.relativize(path); LOGGER.info("Relative path: {}", relativePath); @@ -271,7 +276,7 @@ public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAP } } - LOGGER.info("All files in the backup directory match the committed content."); + // No differences found return false; } diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index c344ccb9069..5acaf654c3f 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -125,6 +125,7 @@ private static Optional showReviewBackupDialog( BibDatabaseContext originalDatabase = originalParserResult.getDatabaseContext(); Path backupPath = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, preferences.getFilePreferences().getBackupDirectory()).orElseThrow(); + LOGGER.info("Ligne 127, BackupUIManager, Loading backup database from {}", backupPath); BibDatabaseContext backupDatabase = OpenDatabase.loadDatabase(backupPath, importFormatPreferences, new DummyFileUpdateMonitor()).getDatabaseContext(); DatabaseChangeResolverFactory changeResolverFactory = new DatabaseChangeResolverFactory(dialogService, originalDatabase, preferences); From 96c3239c2a23d1b7814cb8aa820a08381c3db94d Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Sun, 1 Dec 2024 04:54:36 +0100 Subject: [PATCH 47/84] Reversed backup list --- .../jabref/gui/backup/BackupChoiceDialog.java | 20 +------ .../jabref/gui/dialogs/BackupUIManager.java | 56 ++++++++++--------- 2 files changed, 32 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java b/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java index 01d42758fb4..d75f07a4535 100644 --- a/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java +++ b/src/main/java/org/jabref/gui/backup/BackupChoiceDialog.java @@ -1,6 +1,5 @@ package org.jabref.gui.backup; -import java.io.IOException; import java.nio.file.Path; import java.util.List; @@ -14,13 +13,9 @@ import javafx.scene.control.TableView; import javafx.scene.layout.VBox; -import org.jabref.gui.autosaveandbackup.BackupManagerGit; import org.jabref.gui.util.BaseDialog; import org.jabref.logic.l10n.Localization; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.revwalk.RevCommit; - public class BackupChoiceDialog extends BaseDialog { public static final ButtonType RESTORE_BACKUP = new ButtonType(Localization.lang("Restore from backup"), ButtonBar.ButtonData.OK_DONE); public static final ButtonType IGNORE_BACKUP = new ButtonType(Localization.lang("Ignore backup"), ButtonBar.ButtonData.CANCEL_CLOSE); @@ -32,7 +27,7 @@ public class BackupChoiceDialog extends BaseDialog { @FXML private final TableView backupTableView; - public BackupChoiceDialog(Path originalPath, Path backupDir) { + public BackupChoiceDialog(Path backupDir, List backups) { this.backupDir = backupDir; setTitle(Localization.lang("Choose backup file")); @@ -46,7 +41,7 @@ public BackupChoiceDialog(Path originalPath, Path backupDir) { backupTableView = new TableView<>(); setupBackupTableView(); - fetchBackupData(); + tableData.addAll(backups); backupTableView.setItems(tableData); @@ -75,15 +70,4 @@ private void setupBackupTableView() { backupTableView.getColumns().addAll(dateColumn, sizeColumn, entriesColumn); } - - private void fetchBackupData() { - try { - List commits = BackupManagerGit.retrieveCommits(backupDir, -1); - tableData.addAll(BackupManagerGit.retrieveCommitDetails(commits, backupDir)); - } catch ( - IOException | - GitAPIException e) { - throw new RuntimeException(e); - } - } } diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 5acaf654c3f..4e328cc6c4d 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -15,6 +15,7 @@ import org.jabref.gui.autosaveandbackup.BackupManagerGit; import org.jabref.gui.backup.BackupChoiceDialog; import org.jabref.gui.backup.BackupChoiceDialogRecord; +import org.jabref.gui.backup.BackupEntry; import org.jabref.gui.backup.BackupResolverDialog; import org.jabref.gui.collab.DatabaseChange; import org.jabref.gui.collab.DatabaseChangeList; @@ -36,6 +37,7 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,35 +62,36 @@ public static Optional showRestoreBackupDialog(DialogService dialo preferences.getExternalApplicationsPreferences(), originalPath, preferences.getFilePreferences().getBackupDirectory()); + return actionOpt.flatMap(action -> { - if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { - try { - ObjectId commitId = BackupManagerGit.retrieveCommits(preferences.getFilePreferences().getBackupDirectory(), 1).getFirst().getId(); - BackupManagerGit.restoreBackup(preferences.getFilePreferences().getBackupDirectory(), commitId); - } catch ( - IOException | - GitAPIException e - ) { - throw new RuntimeException(e); - } - return Optional.empty(); - } else if (action == BackupResolverDialog.REVIEW_BACKUP) { - return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); - } else if (action == BackupResolverDialog.COMPARE_OLDER_BACKUP) { - var recordBackupChoice = showBackupChoiceDialog(dialogService, originalPath, preferences); - if (recordBackupChoice.isEmpty()) { - return Optional.empty(); - } - if (recordBackupChoice.get().action() == BackupChoiceDialog.RESTORE_BACKUP) { - LOGGER.warn(recordBackupChoice.get().entry().getSize()); - ObjectId commitId = recordBackupChoice.get().entry().getId(); + try { + List commits = BackupManagerGit.retrieveCommits(preferences.getFilePreferences().getBackupDirectory(), -1); + List backups = BackupManagerGit.retrieveCommitDetails(commits, preferences.getFilePreferences().getBackupDirectory()).reversed(); + if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { + ObjectId commitId = backups.getFirst().getId(); BackupManagerGit.restoreBackup(preferences.getFilePreferences().getBackupDirectory(), commitId); return Optional.empty(); - } - if (recordBackupChoice.get().action() == BackupChoiceDialog.REVIEW_BACKUP) { - LOGGER.warn(recordBackupChoice.get().entry().getSize()); + } else if (action == BackupResolverDialog.REVIEW_BACKUP) { return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); + } else if (action == BackupResolverDialog.COMPARE_OLDER_BACKUP) { + var recordBackupChoice = showBackupChoiceDialog(dialogService, originalPath, preferences, backups); + if (recordBackupChoice.isEmpty()) { + return Optional.empty(); + } + if (recordBackupChoice.get().action() == BackupChoiceDialog.RESTORE_BACKUP) { + LOGGER.warn(recordBackupChoice.get().entry().getSize()); + ObjectId commitId = recordBackupChoice.get().entry().getId(); + BackupManagerGit.restoreBackup(preferences.getFilePreferences().getBackupDirectory(), commitId); + return Optional.empty(); + } + if (recordBackupChoice.get().action() == BackupChoiceDialog.REVIEW_BACKUP) { + LOGGER.warn(recordBackupChoice.get().entry().getSize()); + return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); + } } + } catch ( + GitAPIException | IOException e) { + throw new RuntimeException(e); } return Optional.empty(); }); @@ -104,9 +107,10 @@ private static Optional showBackupResolverDialog(DialogService dialo private static Optional showBackupChoiceDialog(DialogService dialogService, Path originalPath, - GuiPreferences preferences) { + GuiPreferences preferences, + List backups) { return UiTaskExecutor.runInJavaFXThread( - () -> dialogService.showCustomDialogAndWait(new BackupChoiceDialog(originalPath, preferences.getFilePreferences().getBackupDirectory()))); + () -> dialogService.showCustomDialogAndWait(new BackupChoiceDialog(preferences.getFilePreferences().getBackupDirectory(), backups))); } private static Optional showReviewBackupDialog( From 5d0afe6838a8b66c1956440990ced560d9c42e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Sun, 1 Dec 2024 16:35:10 +0100 Subject: [PATCH 48/84] no more NULL for git initialization --- .../autosaveandbackup/BackupManagerGit.java | 132 ++++++++++-------- .../importer/actions/OpenDatabaseAction.java | 10 ++ 2 files changed, 82 insertions(+), 60 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index ff113303e7d..c5e03417cc8 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -16,7 +16,6 @@ import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import java.util.stream.Stream; import org.jabref.gui.LibraryTab; @@ -55,7 +54,7 @@ public class BackupManagerGit { private final CoarseChangeFilter changeFilter; private final BibEntryTypesManager entryTypesManager; private final LibraryTab libraryTab; - private final Git git; + private static Git git; private boolean needsBackup = false; @@ -71,31 +70,44 @@ public class BackupManagerGit { // Ensure the backup directory exists Path backupDirPath = preferences.getFilePreferences().getBackupDirectory(); - LOGGER.info("backupDirPath" + backupDirPath); + LOGGER.info("Backup directory path: {}", backupDirPath); File backupDir = backupDirPath.toFile(); - if (!backupDir.exists()) { - boolean dirCreated = backupDir.mkdirs(); - if (dirCreated) { - LOGGER.info("Created backup directory: " + backupDir); - } else { - LOGGER.error("Failed to create backup directory: " + backupDir); - } + if (!backupDir.exists() && !backupDir.mkdirs()) { + LOGGER.error("Failed to create backup directory: {}", backupDir); + throw new IOException("Unable to create backup directory: " + backupDir); } - // Initialize Git repository in the backup directory - FileRepositoryBuilder builder = new FileRepositoryBuilder(); - git = new Git(builder.setGitDir(new File(backupDir, ".git")) - .readEnvironment() - .findGitDir() - .build()); + // Ensure Git is initialized + ensureGitInitialized(backupDirPath); + } + + private static void ensureGitInitialized(Path backupDir) throws IOException, GitAPIException { + + // This method was created because the initialization of the Git object, when written in the constructor, was causing a NullPointerException + // because the first method called when loading the database is BackupGitdiffers + + // Convert Path to File + File gitDir = new File(backupDir.toFile(), ".git"); - if (git.getRepository().getObjectDatabase().exists()) { - LOGGER.info("Git repository already exists"); + // Check if the `.git` directory exists + if (!gitDir.exists() || !gitDir.isDirectory()) { + LOGGER.info(".git directory not found in {}, initializing new Git repository.", backupDir); + + // Initialize a new Git repository + Git.init().setDirectory(backupDir.toFile()).call(); + LOGGER.info("Git repository successfully initialized in {}", backupDir); } else { - Git.init().setDirectory(backupDir).call(); // Explicitly set the directory - LOGGER.info("Initialized new Git repository"); + LOGGER.info("Existing Git repository found in {}", backupDir); } + + // Build the Git object + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + Repository repository = builder.setGitDir(gitDir) + .readEnvironment() + .findGitDir() + .build(); + git = new Git(repository); } /** @@ -178,7 +190,7 @@ protected void performBackup(Path backupDir) throws IOException, GitAPIException RevCommit commit = git.commit() .setMessage("Backup at " + Instant.now().toString()) .call(); - LOGGER.info("Backup committed with ID: {}", commit.getId().getName()); + LOGGER.info("Backup committed in :" + backupDir + " with commit ID: " + commit.getName()); } /** @@ -219,64 +231,64 @@ public static void restoreBackup(Path backupDir, ObjectId objectId) { public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAPIException { LOGGER.info("Checking if backup differs for directory: {}", backupDir); - // Ensure Git repository exists - File repoDir = backupDir.toFile(); - Repository repository = new FileRepositoryBuilder() - .setGitDir(new File(repoDir, ".git")) - .build(); + // Ensure the Git object is initialized + ensureGitInitialized(backupDir); - try (Git git = new Git(repository)) { - // Resolve HEAD commit - ObjectId headCommitId = repository.resolve("HEAD"); - if (headCommitId == null) { - // No commits in the repository; assume the file differs - LOGGER.info("No commits found in the repository. Assuming the file differs."); - return true; - } + Repository repository = git.getRepository(); + if (repository == null) { + LOGGER.error("Repository object is null. Cannot check for backup differences."); + throw new IllegalStateException("Repository object is not initialized."); + } - // Iterate over files in the backup directory - try (Stream paths = Files.walk(backupDir)) { - for (Path path : paths.filter(Files::isRegularFile).collect(Collectors.toList())) { - // Exclude internal Git files (e.g., .git directory files) - if (path.toString().contains(".git")) { - continue; // Skip Git internals like .git/config - } + // Resolve HEAD commit + ObjectId headCommitId = repository.resolve("HEAD"); + if (headCommitId == null) { + LOGGER.info("No commits found in the repository. Assuming the file differs."); + return true; + } + + LOGGER.info("HEAD commit ID: {}", headCommitId.getName()); - // Determine the path relative to the Git repository - Path relativePath = backupDir.relativize(path); - LOGGER.info("Relative path: {}", relativePath); + try (Stream paths = Files.walk(backupDir)) { + for (Path path : paths.filter(Files::isRegularFile).toList()) { + // Skip internal Git files + if (path.toString().contains(".git")) { + continue; + } + // Determine relative path for Git + Path relativePath = backupDir.relativize(path); + String gitPath = relativePath.toString().replace("\\", "/"); + LOGGER.info("Checking file: {}", gitPath); + + try { // Attempt to retrieve the file content from the last commit - ObjectLoader loader; - try { - ObjectId objectId = repository.resolve("HEAD:" + relativePath.toString().replace("\\", "/")); - if (objectId == null) { - LOGGER.info("Object ID for path {} is null. Assuming the file differs.", relativePath); - return true; - } - loader = repository.open(objectId); - } catch (MissingObjectException e) { - // File not found in the last commit; assume it differs - LOGGER.info("File not found in the last commit. Assuming the file differs."); + ObjectId objectId = repository.resolve("HEAD:" + gitPath); + if (objectId == null) { + LOGGER.info("File '{}' not found in the last commit. Assuming it differs.", gitPath); return true; } - // Read the content from the last commit + // Load content from the Git object + ObjectLoader loader = repository.open(objectId); String committedContent = new String(loader.getBytes(), StandardCharsets.UTF_8); - // Read the current content of the file + // Load current file content String currentContent = Files.readString(path, StandardCharsets.UTF_8); - // Compare the current content to the committed content + // Compare contents if (!currentContent.equals(committedContent)) { - LOGGER.info("Current content of file {} differs from the committed content.", path); + LOGGER.info("Content differs for file: {}", path); return true; } + } catch (MissingObjectException e) { + LOGGER.info("File '{}' not found in the last commit. Assuming it differs.", gitPath); + return true; } } } - // No differences found + LOGGER.info("No differences found in backup."); return false; } 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 bf6fb1ff84c..2c2b14f5f27 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -255,6 +255,16 @@ private ParserResult loadDatabase(Path file) throws Exception { preferences.getFilePreferences().setWorkingDirectory(fileToLoad.getParent()); Path backupDir = preferences.getFilePreferences().getBackupDirectory(); + // To debug + if (!Files.exists(backupDir)) { + LOGGER.error("Backup directory does not exist: {}", backupDir); + throw new IOException("Backup directory not found: " + backupDir); + } + if (!Files.isReadable(backupDir)) { + LOGGER.error("Backup directory is not readable: {}", backupDir); + throw new IOException("Cannot read from backup directory: " + backupDir); + } + ParserResult parserResult = null; if (BackupManagerGit.backupGitDiffers(backupDir)) { // In case the backup differs, ask the user what to do. From 7a48131c610d8e1db884d75cfea8e52ae77838cb Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Sun, 1 Dec 2024 17:35:27 +0100 Subject: [PATCH 49/84] resolving prblm --- .../autosaveandbackup/BackupManagerGit.java | 112 +++++++++--------- .../importer/actions/OpenDatabaseAction.java | 2 +- .../BackupManagerGitTest.java | 8 +- 3 files changed, 61 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index c5e03417cc8..b12d0bfb0d9 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -16,7 +16,6 @@ import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; import org.jabref.gui.LibraryTab; import org.jabref.gui.backup.BackupEntry; @@ -29,7 +28,6 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; -import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; @@ -47,6 +45,7 @@ public class BackupManagerGit { private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; private static Set runningInstances = new HashSet(); + private static Git git; private final BibDatabaseContext bibDatabaseContext; private final CliPreferences preferences; @@ -54,7 +53,8 @@ public class BackupManagerGit { private final CoarseChangeFilter changeFilter; private final BibEntryTypesManager entryTypesManager; private final LibraryTab libraryTab; - private static Git git; + + private boolean needsBackup = false; @@ -67,6 +67,7 @@ public class BackupManagerGit { changeFilter = new CoarseChangeFilter(bibDatabaseContext); changeFilter.registerListener(this); + LOGGER.info("maysmm i l file: {}", bibDatabaseContext.getDatabasePath().orElseThrow()); // Ensure the backup directory exists Path backupDirPath = preferences.getFilePreferences().getBackupDirectory(); @@ -124,7 +125,7 @@ private static void ensureGitInitialized(Path backupDir) throws IOException, Git public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { BackupManagerGit backupManagerGit = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - backupManagerGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory()); + backupManagerGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), bibDatabaseContext.getDatabasePath().orElseThrow()); runningInstances.add(backupManagerGit); return backupManagerGit; } @@ -152,12 +153,12 @@ public static void shutdown(BibDatabaseContext bibDatabaseContext, boolean creat * @param backupDir the backup directory */ - void startBackupTask(Path backupDir) { - LOGGER.info("Initializing backup task for directory: {} and file: {}", backupDir); + void startBackupTask(Path backupDir, Path bibPath) { + LOGGER.info("Initializing backup task for directory: {} and file: {}", backupDir, bibPath); executor.scheduleAtFixedRate( () -> { try { - performBackup(backupDir); + performBackup(backupDir, bibPath); } catch (IOException | GitAPIException e) { LOGGER.error("Error during backup", e); } @@ -176,9 +177,9 @@ void startBackupTask(Path backupDir) { * @throws GitAPIException if a Git API error occurs */ - protected void performBackup(Path backupDir) throws IOException, GitAPIException { + protected void performBackup(Path backupDir, Path bibPath) throws IOException, GitAPIException { // Check if the file needs a backup by comparing it to the last commit - boolean needsBackup = BackupManagerGit.backupGitDiffers(backupDir); + boolean needsBackup = BackupManagerGit.backupGitDiffers(backupDir, bibPath); if (!needsBackup) { return; } @@ -228,7 +229,8 @@ public static void restoreBackup(Path backupDir, ObjectId objectId) { * @throws GitAPIException if a Git API error occurs */ - public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAPIException { + @SuppressWarnings("checkstyle:RegexpMultiline") + public static boolean backupGitDiffers(Path backupDir, Path bibPath) throws IOException, GitAPIException { LOGGER.info("Checking if backup differs for directory: {}", backupDir); // Ensure the Git object is initialized @@ -239,57 +241,55 @@ public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAP LOGGER.error("Repository object is null. Cannot check for backup differences."); throw new IllegalStateException("Repository object is not initialized."); } + try { - // Resolve HEAD commit - ObjectId headCommitId = repository.resolve("HEAD"); - if (headCommitId == null) { - LOGGER.info("No commits found in the repository. Assuming the file differs."); - return true; - } - - LOGGER.info("HEAD commit ID: {}", headCommitId.getName()); - - try (Stream paths = Files.walk(backupDir)) { - for (Path path : paths.filter(Files::isRegularFile).toList()) { - // Skip internal Git files - if (path.toString().contains(".git")) { - continue; - } - - // Determine relative path for Git - Path relativePath = backupDir.relativize(path); - String gitPath = relativePath.toString().replace("\\", "/"); - LOGGER.info("Checking file: {}", gitPath); - - try { - // Attempt to retrieve the file content from the last commit - ObjectId objectId = repository.resolve("HEAD:" + gitPath); - if (objectId == null) { - LOGGER.info("File '{}' not found in the last commit. Assuming it differs.", gitPath); - return true; - } + // Compute the relative path for the .bib file + Path gitWorkTree = repository.getWorkTree().toPath(); + Path relativePath = gitWorkTree.relativize(bibPath); + String gitPath = relativePath.toString().replace("\\", "/"); - // Load content from the Git object - ObjectLoader loader = repository.open(objectId); - String committedContent = new String(loader.getBytes(), StandardCharsets.UTF_8); + LOGGER.info("Comparing file: {}", gitPath); + LOGGER.info("repository: {}", repository.getWorkTree().toPath()); - // Load current file content - String currentContent = Files.readString(path, StandardCharsets.UTF_8); + // Resolve the file in the latest commit + ObjectId objectId = repository.resolve("HEAD:" + gitPath); + if (objectId == null) { + LOGGER.info("File '{}' not found in the last commit. Assuming it differs.", gitPath); + return true; + } - // Compare contents - if (!currentContent.equals(committedContent)) { - LOGGER.info("Content differs for file: {}", path); - return true; - } - } catch (MissingObjectException e) { - LOGGER.info("File '{}' not found in the last commit. Assuming it differs.", gitPath); - return true; - } + // Load content from the Git repository + ObjectLoader loader = repository.open(objectId); + String committedContent = new String(loader.getBytes(), StandardCharsets.UTF_8); + LOGGER.info(committedContent); + + // Load current content from the filesystem + String currentContent = Files.readString(bibPath, StandardCharsets.UTF_8); + LOGGER.info(currentContent); + // Normalize whitespace and line endings + String normalizedCommittedContent = committedContent + .trim() + .replaceAll("\r\n", "\n") + .replaceAll("[ \\t]+", " ") // Replace multiple spaces with single space + .replaceAll("\n+", "\n"); // Remove multiple blank lines + LOGGER.info("normalizedCommittedContent: {} ", normalizedCommittedContent); + String normalizedCurrentContent = currentContent + .trim() + .replaceAll("\r\n", "\n") + .replaceAll("[ \\t]+", " ") + .replaceAll("\n+", "\n"); + LOGGER.info("normalizedCurrentContent: {} ", normalizedCurrentContent); + if (!normalizedCurrentContent.equals(normalizedCommittedContent)) { + LOGGER.info("Content differs after normalization"); + return true; + } else { + LOGGER.info("No differences found for file: {}", bibPath); } + } catch (Exception e) { + LOGGER.error("Error while comparing file '{}': {}", bibPath, e.getMessage()); + return true; // Assume the file differs if an error occurs } - - LOGGER.info("No differences found in backup."); - return false; + return false; // No differences found } /** @@ -435,7 +435,7 @@ private void shutdownGit(Path backupDir, boolean createBackup, Path originalPath if (createBackup) { try { // Ensure the backup is a recent one by performing the Git commit - performBackup(backupDir); + performBackup(backupDir, originalPath); LOGGER.info("Backup created on shutdown for file: {}", originalPath); } catch (IOException | GitAPIException e) { LOGGER.error("Error during Git backup on shutdown", e); 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 2c2b14f5f27..436842555fc 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -266,7 +266,7 @@ private ParserResult loadDatabase(Path file) throws Exception { } ParserResult parserResult = null; - if (BackupManagerGit.backupGitDiffers(backupDir)) { + if (BackupManagerGit.backupGitDiffers(backupDir, fileToLoad)) { // In case the backup differs, ask the user what to do. LOGGER.info("Backup differs from saved file, ask the user what to do"); // In case the user opted for restoring a backup, the content of the backup is contained in parserResult. diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index 2f340a04236..cff3e7545c0 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -91,7 +91,7 @@ void testBackupManagerInitializesGitRepository() throws Exception { void testBackupGitDiffers_NoDifferences() throws Exception { // Verify that there is no difference between the file and the last commit Path testFile = backupDir.resolve("testfile.bib"); - boolean differs = BackupManagerGit.backupGitDiffers(backupDir); + boolean differs = BackupManagerGit.backupGitDiffers(backupDir, testFile); assertFalse(differs, "Expected no difference between the file and the last commit"); } @@ -101,7 +101,7 @@ void testBackupGitDiffers_WithDifferences() throws Exception { Path testFile = backupDir.resolve("testfile.bib"); Files.writeString(testFile, "Modified content", StandardCharsets.UTF_8); - boolean differs = BackupManagerGit.backupGitDiffers(backupDir); + boolean differs = BackupManagerGit.backupGitDiffers(backupDir, testFile); assertTrue(differs, "Expected differences between the file and the last commit"); } @@ -122,7 +122,7 @@ void testNoNewRepositoryCreated() throws Exception { // Use backupGitDiffers to check if the backup differs boolean createBackup; if (bibDatabaseContext.getDatabasePath().isPresent()) { - createBackup = BackupManagerGit.backupGitDiffers(backupDir); + createBackup = BackupManagerGit.backupGitDiffers(backupDir, bibDatabaseContext.getDatabasePath().get()); } else { fail("Database path is not present"); return; // Avoid further execution if the path is missing @@ -202,7 +202,7 @@ void testStartBackupTaskWithReflection() throws Exception { // Act: Start the backup task // private void startBackupTask(Path backupDir, Path originalPath) - backupManager.startBackupTask(backupDirectory); + backupManager.startBackupTask(backupDirectory, bibDatabaseContext.getDatabasePath().orElse()); // Simulate passage of time Thread.sleep(100); From 47d52381e465f146997eab9e4212cfb0129aa79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Sun, 1 Dec 2024 18:37:27 +0100 Subject: [PATCH 50/84] Copying files to the backupdir logic --- .../autosaveandbackup/BackupManagerGit.java | 165 +++++++++++------- .../importer/actions/OpenDatabaseAction.java | 2 +- .../BackupManagerGitTest.java | 6 +- 3 files changed, 105 insertions(+), 68 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index b12d0bfb0d9..2117f9e6cb9 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; @@ -16,6 +17,7 @@ import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; import org.jabref.gui.LibraryTab; import org.jabref.gui.backup.BackupEntry; @@ -28,6 +30,7 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; @@ -45,7 +48,6 @@ public class BackupManagerGit { private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; private static Set runningInstances = new HashSet(); - private static Git git; private final BibDatabaseContext bibDatabaseContext; private final CliPreferences preferences; @@ -53,13 +55,13 @@ public class BackupManagerGit { private final CoarseChangeFilter changeFilter; private final BibEntryTypesManager entryTypesManager; private final LibraryTab libraryTab; - - + private static Git git; private boolean needsBackup = false; BackupManagerGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { this.bibDatabaseContext = bibDatabaseContext; + LOGGER.info("Backup manager initialized for file: {}", bibDatabaseContext.getDatabasePath().orElseThrow()); this.entryTypesManager = entryTypesManager; this.preferences = preferences; this.executor = new ScheduledThreadPoolExecutor(2); @@ -67,20 +69,32 @@ public class BackupManagerGit { changeFilter = new CoarseChangeFilter(bibDatabaseContext); changeFilter.registerListener(this); - LOGGER.info("maysmm i l file: {}", bibDatabaseContext.getDatabasePath().orElseThrow()); // Ensure the backup directory exists Path backupDirPath = preferences.getFilePreferences().getBackupDirectory(); LOGGER.info("Backup directory path: {}", backupDirPath); + // Ensure Git is initialized + ensureGitInitialized(backupDirPath); + File backupDir = backupDirPath.toFile(); if (!backupDir.exists() && !backupDir.mkdirs()) { LOGGER.error("Failed to create backup directory: {}", backupDir); throw new IOException("Unable to create backup directory: " + backupDir); } - // Ensure Git is initialized - ensureGitInitialized(backupDirPath); + // Get the path of the BibDatabase file + Path dbFile = bibDatabaseContext.getDatabasePath().orElseThrow(() -> new IllegalArgumentException("Database path is not provided.")); + + // Copy the database file to the backup directory + Path backupFilePath = backupDirPath.resolve(dbFile.getFileName()); + try { + Files.copy(dbFile, backupFilePath, StandardCopyOption.REPLACE_EXISTING); + LOGGER.info("Database file copied to backup directory: {}", backupFilePath); + } catch (IOException e) { + LOGGER.error("Failed to copy database file to backup directory", e); + throw new IOException("Error copying database file to backup directory", e); + } } private static void ensureGitInitialized(Path backupDir) throws IOException, GitAPIException { @@ -125,7 +139,7 @@ private static void ensureGitInitialized(Path backupDir) throws IOException, Git public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { BackupManagerGit backupManagerGit = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - backupManagerGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), bibDatabaseContext.getDatabasePath().orElseThrow()); + backupManagerGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), bibDatabaseContext); runningInstances.add(backupManagerGit); return backupManagerGit; } @@ -153,12 +167,25 @@ public static void shutdown(BibDatabaseContext bibDatabaseContext, boolean creat * @param backupDir the backup directory */ - void startBackupTask(Path backupDir, Path bibPath) { - LOGGER.info("Initializing backup task for directory: {} and file: {}", backupDir, bibPath); + void startBackupTask(Path backupDir, BibDatabaseContext bibDatabaseContext) { + LOGGER.info("Initializing backup task for directory: {} and file: {}", backupDir); executor.scheduleAtFixedRate( () -> { try { - performBackup(backupDir, bibPath); + // Copy the database file to the backup directory + Path dbFile = bibDatabaseContext.getDatabasePath().orElseThrow(() -> new IllegalArgumentException("Database path is not provided.")); + + // Copy the database file to the backup directory (overwriting any existing file) + Path backupFilePath = backupDir.resolve(dbFile.getFileName()); + try { + Files.copy(dbFile, backupFilePath, StandardCopyOption.REPLACE_EXISTING); + LOGGER.info("Database file copied to backup directory: {}", backupFilePath); + } catch (IOException e) { + LOGGER.error("Failed to copy database file to backup directory", e); + throw new IOException("Error copying database file to backup directory", e); + } + + performBackup(backupDir); } catch (IOException | GitAPIException e) { LOGGER.error("Error during backup", e); } @@ -177,9 +204,10 @@ void startBackupTask(Path backupDir, Path bibPath) { * @throws GitAPIException if a Git API error occurs */ - protected void performBackup(Path backupDir, Path bibPath) throws IOException, GitAPIException { + protected void performBackup(Path backupDir) throws IOException, GitAPIException { + // Check if the file needs a backup by comparing it to the last commit - boolean needsBackup = BackupManagerGit.backupGitDiffers(backupDir, bibPath); + boolean needsBackup = BackupManagerGit.backupGitDiffers(backupDir); if (!needsBackup) { return; } @@ -229,67 +257,76 @@ public static void restoreBackup(Path backupDir, ObjectId objectId) { * @throws GitAPIException if a Git API error occurs */ - @SuppressWarnings("checkstyle:RegexpMultiline") - public static boolean backupGitDiffers(Path backupDir, Path bibPath) throws IOException, GitAPIException { + public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAPIException { + // Ensure the Git repository exists LOGGER.info("Checking if backup differs for directory: {}", backupDir); - // Ensure the Git object is initialized - ensureGitInitialized(backupDir); + // Open the Git repository located in the backup directory + Repository repository = openGitRepository(backupDir); - Repository repository = git.getRepository(); - if (repository == null) { - LOGGER.error("Repository object is null. Cannot check for backup differences."); - throw new IllegalStateException("Repository object is not initialized."); + // Get the HEAD commit to compare with + ObjectId headCommitId = repository.resolve("HEAD"); + if (headCommitId == null) { + LOGGER.info("No commits found in the repository. Assuming the file differs."); + return true; } - try { + LOGGER.info("HEAD commit ID: {}", headCommitId.getName()); + + // Iterate over the files in the backup directory to check if they differ from the repository + try (Stream paths = Files.walk(backupDir)) { + for (Path path : paths.filter(Files::isRegularFile).toList()) { + // Ignore non-.bib files (e.g., .DS_Store) + if (!path.toString().endsWith(".bib")) { + LOGGER.info("Ignoring non-.bib file: {}", path); + continue; // Skip .bib files + } + + // Skip .git directory files + if (path.toString().contains(".git")) { + continue; + } - // Compute the relative path for the .bib file - Path gitWorkTree = repository.getWorkTree().toPath(); - Path relativePath = gitWorkTree.relativize(bibPath); - String gitPath = relativePath.toString().replace("\\", "/"); + // Calculate the relative path in the Git repository + Path relativePath = backupDir.relativize(path); + LOGGER.info("Checking file: {}", relativePath); - LOGGER.info("Comparing file: {}", gitPath); - LOGGER.info("repository: {}", repository.getWorkTree().toPath()); + try { + // Check if the file exists in the latest commit + ObjectId objectId = repository.resolve("HEAD:" + relativePath.toString().replace("\\", "/")); + if (objectId == null) { + LOGGER.info("File not found in the latest commit: {}. Assuming it differs.", relativePath); + return true; + } - // Resolve the file in the latest commit - ObjectId objectId = repository.resolve("HEAD:" + gitPath); - if (objectId == null) { - LOGGER.info("File '{}' not found in the last commit. Assuming it differs.", gitPath); - return true; - } + // Compare the content of the file in the Git repository with the current file + ObjectLoader loader = repository.open(objectId); + String committedContent = new String(loader.getBytes(), StandardCharsets.UTF_8); + String currentContent = Files.readString(path, StandardCharsets.UTF_8); - // Load content from the Git repository - ObjectLoader loader = repository.open(objectId); - String committedContent = new String(loader.getBytes(), StandardCharsets.UTF_8); - LOGGER.info(committedContent); - - // Load current content from the filesystem - String currentContent = Files.readString(bibPath, StandardCharsets.UTF_8); - LOGGER.info(currentContent); - // Normalize whitespace and line endings - String normalizedCommittedContent = committedContent - .trim() - .replaceAll("\r\n", "\n") - .replaceAll("[ \\t]+", " ") // Replace multiple spaces with single space - .replaceAll("\n+", "\n"); // Remove multiple blank lines - LOGGER.info("normalizedCommittedContent: {} ", normalizedCommittedContent); - String normalizedCurrentContent = currentContent - .trim() - .replaceAll("\r\n", "\n") - .replaceAll("[ \\t]+", " ") - .replaceAll("\n+", "\n"); - LOGGER.info("normalizedCurrentContent: {} ", normalizedCurrentContent); - if (!normalizedCurrentContent.equals(normalizedCommittedContent)) { - LOGGER.info("Content differs after normalization"); - return true; - } else { - LOGGER.info("No differences found for file: {}", bibPath); + // If the contents differ, return true + if (!currentContent.equals(committedContent)) { + LOGGER.info("Content differs for file: {}", relativePath); + return true; + } + } catch (MissingObjectException e) { + // If the file is missing from the commit, assume it differs + LOGGER.info("File not found in the latest commit: {}. Assuming it differs.", relativePath); + return true; + } } - } catch (Exception e) { - LOGGER.error("Error while comparing file '{}': {}", bibPath, e.getMessage()); - return true; // Assume the file differs if an error occurs } - return false; // No differences found + + LOGGER.info("No differences found in the backup."); + return false; // No differences found + } + + private static Repository openGitRepository(Path backupDir) throws IOException { + FileRepositoryBuilder builder = new FileRepositoryBuilder(); + // Initialize Git repository from the backup directory + return builder.setGitDir(new File(backupDir.toFile(), ".git")) + .readEnvironment() + .findGitDir() + .build(); } /** @@ -435,7 +472,7 @@ private void shutdownGit(Path backupDir, boolean createBackup, Path originalPath if (createBackup) { try { // Ensure the backup is a recent one by performing the Git commit - performBackup(backupDir, originalPath); + performBackup(backupDir); LOGGER.info("Backup created on shutdown for file: {}", originalPath); } catch (IOException | GitAPIException e) { LOGGER.error("Error during Git backup on shutdown", e); 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 436842555fc..2c2b14f5f27 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -266,7 +266,7 @@ private ParserResult loadDatabase(Path file) throws Exception { } ParserResult parserResult = null; - if (BackupManagerGit.backupGitDiffers(backupDir, fileToLoad)) { + if (BackupManagerGit.backupGitDiffers(backupDir)) { // In case the backup differs, ask the user what to do. LOGGER.info("Backup differs from saved file, ask the user what to do"); // In case the user opted for restoring a backup, the content of the backup is contained in parserResult. diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index cff3e7545c0..c20698962e6 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -91,7 +91,7 @@ void testBackupManagerInitializesGitRepository() throws Exception { void testBackupGitDiffers_NoDifferences() throws Exception { // Verify that there is no difference between the file and the last commit Path testFile = backupDir.resolve("testfile.bib"); - boolean differs = BackupManagerGit.backupGitDiffers(backupDir, testFile); + boolean differs = BackupManagerGit.backupGitDiffers(backupDir); assertFalse(differs, "Expected no difference between the file and the last commit"); } @@ -101,7 +101,7 @@ void testBackupGitDiffers_WithDifferences() throws Exception { Path testFile = backupDir.resolve("testfile.bib"); Files.writeString(testFile, "Modified content", StandardCharsets.UTF_8); - boolean differs = BackupManagerGit.backupGitDiffers(backupDir, testFile); + boolean differs = BackupManagerGit.backupGitDiffers(backupDir; assertTrue(differs, "Expected differences between the file and the last commit"); } @@ -122,7 +122,7 @@ void testNoNewRepositoryCreated() throws Exception { // Use backupGitDiffers to check if the backup differs boolean createBackup; if (bibDatabaseContext.getDatabasePath().isPresent()) { - createBackup = BackupManagerGit.backupGitDiffers(backupDir, bibDatabaseContext.getDatabasePath().get()); + createBackup = BackupManagerGit.backupGitDiffers(backupDir); } else { fail("Database path is not present"); return; // Avoid further execution if the path is missing From e881ce4e31b2e2941fe38b1a8285d9f864ec2c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Sun, 1 Dec 2024 21:21:47 +0100 Subject: [PATCH 51/84] Add listeners --- .../jabref/gui/autosaveandbackup/BackupManager.java | 6 ++---- .../gui/autosaveandbackup/BackupManagerGit.java | 13 ++++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java index 8e468418908..e6458520653 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java @@ -41,7 +41,6 @@ import org.jabref.model.metadata.SaveOrder; import org.jabref.model.metadata.SelfContainedSaveOrder; -import com.google.common.eventbus.Subscribe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -337,8 +336,7 @@ private void logIfCritical(Path backupPath, IOException e) { } } - @Subscribe - public synchronized void listen(@SuppressWarnings("unused") BibDatabaseContextChangedEvent event) { + public synchronized void listen(BibDatabaseContextChangedEvent event) { if (!event.isFilteredOut()) { this.needsBackup = true; } @@ -356,7 +354,7 @@ private void startBackupTask(Path backupDir) { TimeUnit.SECONDS); } // La méthode fillQueue(backupDir) est définie dans le code et son rôle est de lister et d'ajouter -// les fichiers de sauvegarde existants dans une file d'attente, + // les fichiers de sauvegarde existants dans une file d'attente, private void fillQueue(Path backupDir) { if (!Files.exists(backupDir)) { diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 2117f9e6cb9..cef6fd5a827 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -24,6 +24,7 @@ import org.jabref.logic.preferences.CliPreferences; import org.jabref.logic.util.CoarseChangeFilter; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.event.BibDatabaseContextChangedEvent; import org.jabref.model.entry.BibEntryTypesManager; import org.eclipse.jgit.api.Git; @@ -205,9 +206,6 @@ void startBackupTask(Path backupDir, BibDatabaseContext bibDatabaseContext) { */ protected void performBackup(Path backupDir) throws IOException, GitAPIException { - - // Check if the file needs a backup by comparing it to the last commit - boolean needsBackup = BackupManagerGit.backupGitDiffers(backupDir); if (!needsBackup) { return; } @@ -222,6 +220,12 @@ protected void performBackup(Path backupDir) throws IOException, GitAPIException LOGGER.info("Backup committed in :" + backupDir + " with commit ID: " + commit.getName()); } + public synchronized void listen(BibDatabaseContextChangedEvent event) { + if (!event.isFilteredOut()) { + this.needsBackup = true; + } + } + /** * Restores the backup from the specified commit. * @@ -249,7 +253,7 @@ public static void restoreBackup(Path backupDir, ObjectId objectId) { } /** - * Checks if there are differences between the original file and the backup. + * Checks if there are differences between the files in the directory and the last commit. * * @param backupDir the backup directory * @return true if there are differences, false otherwise @@ -277,7 +281,6 @@ public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAP for (Path path : paths.filter(Files::isRegularFile).toList()) { // Ignore non-.bib files (e.g., .DS_Store) if (!path.toString().endsWith(".bib")) { - LOGGER.info("Ignoring non-.bib file: {}", path); continue; // Skip .bib files } From 02fa1d1dc1f9b13c9aefa0b0f4c195dbc279ac5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Sun, 1 Dec 2024 22:15:14 +0100 Subject: [PATCH 52/84] Listeners detect changes but don't set needsBackup to true (I don't know how they consider a change major or not) + commit are compared and staged correctly but after manually saving the library --- .../autosaveandbackup/BackupManagerGit.java | 19 +- .../BackupManagerGitTest.java | 383 ------------------ 2 files changed, 17 insertions(+), 385 deletions(-) delete mode 100644 src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index cef6fd5a827..89f8cefc246 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -50,13 +50,14 @@ public class BackupManagerGit { private static Set runningInstances = new HashSet(); + private static Git git; + private final BibDatabaseContext bibDatabaseContext; private final CliPreferences preferences; private final ScheduledThreadPoolExecutor executor; private final CoarseChangeFilter changeFilter; private final BibEntryTypesManager entryTypesManager; private final LibraryTab libraryTab; - private static Git git; private boolean needsBackup = false; @@ -206,12 +207,23 @@ void startBackupTask(Path backupDir, BibDatabaseContext bibDatabaseContext) { */ protected void performBackup(Path backupDir) throws IOException, GitAPIException { - if (!needsBackup) { + + boolean needsCommit = backupGitDiffers(backupDir); + + if (!needsBackup && !needsCommit) { + LOGGER.info("No changes detected, beacuse needsBackup is :" + needsBackup + " and needsCommit is :" + needsCommit); return; } + if (needsBackup) { + LOGGER.info("Backup needed, because needsBackup is :" + needsBackup); + } else { + LOGGER.info("Backup needed, because needsCommit is :" + needsCommit); + } + // Stage the file for commit git.add().addFilepattern(".").call(); + LOGGER.info("Staged changes for backup in directory: {}", backupDir); // Commit the staged changes RevCommit commit = git.commit() @@ -222,6 +234,7 @@ protected void performBackup(Path backupDir) throws IOException, GitAPIException public synchronized void listen(BibDatabaseContextChangedEvent event) { if (!event.isFilteredOut()) { + LOGGER.info("Change detected/LISTENED in file: {}", bibDatabaseContext.getDatabasePath().orElseThrow()); this.needsBackup = true; } } @@ -238,6 +251,8 @@ public static void restoreBackup(Path backupDir, ObjectId objectId) { Git git = Git.open(backupDir.toFile()); git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); + LOGGER.info("checkout done"); + // Add commits to staging Area git.add().addFilepattern(".").call(); diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java deleted file mode 100644 index c20698962e6..00000000000 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ /dev/null @@ -1,383 +0,0 @@ -package org.jabref.gui.autosaveandbackup; - -import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.stream.StreamSupport; - -import org.jabref.gui.LibraryTab; -import org.jabref.gui.backup.BackupEntry; -import org.jabref.logic.preferences.CliPreferences; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntryTypesManager; - -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Answers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class BackupManagerGitTest { - - @TempDir - Path tempDir; - Path backupDir; - Git git; - CliPreferences preferences; - BibEntryTypesManager entryTypesManager; - LibraryTab libraryTab; - BibDatabaseContext bibDatabaseContext; - - @BeforeEach - void setup() throws Exception { - backupDir = tempDir.resolve("backup"); - Files.createDirectories(backupDir); - - // Initialize the Git repository inside backupDir - git = Git.init().setDirectory(backupDir.toFile()).call(); - - // Mock dependencies - preferences = mock(CliPreferences.class, Answers.RETURNS_DEEP_STUBS); - entryTypesManager = mock(BibEntryTypesManager.class, Answers.RETURNS_DEEP_STUBS); - libraryTab = mock(LibraryTab.class); - - when(preferences.getFilePreferences().getBackupDirectory()).thenReturn(backupDir); - - // Create a test file in the backup directory - Path testFile = backupDir.resolve("testfile.bib"); - Files.writeString(testFile, "This is a test file.", StandardCharsets.UTF_8); - - // Add and commit the test file to the repository - git.add().addFilepattern("testfile.bib").call(); - git.commit().setMessage("Initial commit").call(); - } - - @Test - void testBackupManagerInitializesGitRepository() throws Exception { - // Ensure the backup directory exists - Path backupDir = tempDir.resolve("backup"); - Files.createDirectories(backupDir); - - // Create a BibDatabaseContext - Path databaseFile = tempDir.resolve("test.bib"); - Files.writeString(databaseFile, "Initial content"); - var bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); - bibDatabaseContext.setDatabasePath(databaseFile); - - // Initialize BackupManagerGit - BackupManagerGit manager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - - // Ensure the Git repository is initialized in the backup directory - assertTrue(Files.exists(backupDir.resolve(".git")), "Git repository not initialized in backup directory"); - } - - @Test - void testBackupGitDiffers_NoDifferences() throws Exception { - // Verify that there is no difference between the file and the last commit - Path testFile = backupDir.resolve("testfile.bib"); - boolean differs = BackupManagerGit.backupGitDiffers(backupDir); - assertFalse(differs, "Expected no difference between the file and the last commit"); - } - - @Test - void testBackupGitDiffers_WithDifferences() throws Exception { - // Modify the test file to create differences - Path testFile = backupDir.resolve("testfile.bib"); - Files.writeString(testFile, "Modified content", StandardCharsets.UTF_8); - - boolean differs = BackupManagerGit.backupGitDiffers(backupDir; - assertTrue(differs, "Expected differences between the file and the last commit"); - } - - @Test - void testNoNewRepositoryCreated() throws Exception { - // Create a fake file to simulate the database file - Path databaseFile = tempDir.resolve("test.bib"); - Files.writeString(databaseFile, "Initial content"); - - // Set up BibDatabaseContext with the file path - var bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); - bibDatabaseContext.setDatabasePath(databaseFile); - - // Ensure the initial repository is created - BackupManagerGit initialManager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - assertTrue(Files.exists(backupDir.resolve(".git"))); // Ensure the repo exists - - // Use backupGitDiffers to check if the backup differs - boolean createBackup; - if (bibDatabaseContext.getDatabasePath().isPresent()) { - createBackup = BackupManagerGit.backupGitDiffers(backupDir); - } else { - fail("Database path is not present"); - return; // Avoid further execution if the path is missing - } - - // Shutdown the initial manager - BackupManagerGit.shutdown(bibDatabaseContext, createBackup, bibDatabaseContext.getDatabasePath().get()); - - // Create another instance pointing to the same backup directory - BackupManagerGit newManager = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - assertTrue(Files.exists(backupDir.resolve(".git"))); // Ensure no new repo is created - - // Shutdown the new manager - BackupManagerGit.shutdown(bibDatabaseContext, createBackup, bibDatabaseContext.getDatabasePath().get()); - } - - @Test - void testStartMethod() throws Exception { - // Arrange: Set up necessary dependencies and mock objects - Path databaseFile = tempDir.resolve("test.bib"); - Files.writeString(databaseFile, "Initial content"); - - BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); - bibDatabaseContext.setDatabasePath(databaseFile); - - Path backupDirectory = tempDir.resolve("backup"); - Files.createDirectories(backupDirectory); - - // Mock preferences to return the backup directory - when(preferences.getFilePreferences().getBackupDirectory()).thenReturn(backupDirectory); - - // Act: Call the start method - BackupManagerGit backupManager = BackupManagerGit.start( - libraryTab, - bibDatabaseContext, - entryTypesManager, - preferences - ); - - // Assert: Verify the outcomes - // Ensure a Git repository is initialized in the backup directory - assertTrue(Files.exists(backupDirectory.resolve(".git")), "Git repository not initialized"); - - // Use reflection to access the private `runningInstances` - Field runningInstancesField = BackupManagerGit.class.getDeclaredField("runningInstances"); - runningInstancesField.setAccessible(true); - @SuppressWarnings("unchecked") - Set runningInstances = (Set) runningInstancesField.get(null); - - // Ensure the backup manager is added to the running instances - assertTrue(runningInstances.contains(backupManager), "Backup manager not added to running instances"); - - // Clean up by shutting down the backup manager - BackupManagerGit.shutdown(bibDatabaseContext, false, databaseFile); - } - - @Test - void testStartBackupTaskWithReflection() throws Exception { - // Arrange: Similar setup as above - Path databaseFile = tempDir.resolve("test.bib"); - Files.writeString(databaseFile, "Initial content"); - - BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); - bibDatabaseContext.setDatabasePath(databaseFile); - - Path backupDirectory = tempDir.resolve("backup"); - Files.createDirectories(backupDirectory); - - when(preferences.getFilePreferences().getBackupDirectory()).thenReturn(backupDirectory); - - BackupManagerGit backupManager = BackupManagerGit.start( - libraryTab, - bibDatabaseContext, - entryTypesManager, - preferences - ); - - // Act: Start the backup task - // private void startBackupTask(Path backupDir, Path originalPath) - backupManager.startBackupTask(backupDirectory, bibDatabaseContext.getDatabasePath().orElse()); - - // Simulate passage of time - Thread.sleep(100); - - // Use reflection to access the private `runningInstances` - Field runningInstancesField = BackupManagerGit.class.getDeclaredField("runningInstances"); - runningInstancesField.setAccessible(true); - @SuppressWarnings("unchecked") - Set runningInstances = (Set) runningInstancesField.get(null); - - // Assert: Verify the backup task is active - assertTrue(runningInstances.contains(backupManager), "Backup manager not added to running instances"); - - // Clean up - BackupManagerGit.shutdown(bibDatabaseContext, false, databaseFile); - } - - @Test - void testRestoreBackup() throws Exception { - // Create multiple commits - ObjectId targetCommitId = null; - for (int i = 1; i <= 3; i++) { - Path file = backupDir.resolve("file" + i + ".txt"); - Files.writeString(file, "Content of file " + i); - git.add().addFilepattern(".").call(); - RevCommit commit = git.commit().setMessage("Commit " + i).call(); - if (i == 2) { - // Save the ID of the second commit for testing - targetCommitId = commit.getId(); - } - } - - // Act: Call restoreBackup - BackupManagerGit.restoreBackup(tempDir.resolve("restored.txt"), backupDir, targetCommitId); - - // Assert: Verify the repository has a new commit after restoration - try (RevWalk revWalk = new RevWalk(git.getRepository())) { - RevCommit headCommit = revWalk.parseCommit(git.getRepository().resolve("HEAD")); - assertTrue( - headCommit.getShortMessage().contains("Restored content from commit: " + targetCommitId.getName()), - "A new commit should indicate the restoration" - ); - } - - // Assert: Ensure the file from the restored commit exists - assertTrue( - Files.exists(backupDir.resolve("file2.txt")), - "File from the restored commit should be present" - ); - - // Assert: Ensure files from later commits still exist - assertTrue( - Files.exists(backupDir.resolve("file3.txt")), - "File from later commits should still exist after restoration" - ); - - // Assert: Ensure earlier files still exist - assertTrue( - Files.exists(backupDir.resolve("file1.txt")), - "File from earlier commits should still exist after restoration" - ); - } - - @Test - void testRetrieveCommits() throws Exception { - // Create multiple commits in the Git repository - List commitIds = new ArrayList<>(); - for (int i = 1; i <= 10; i++) { - Path file = backupDir.resolve("file" + i + ".txt"); - Files.writeString(file, "Content of file " + i); - git.add().addFilepattern(".").call(); - RevCommit commit = git.commit().setMessage("Commit " + i).call(); - commitIds.add(commit.getId()); - } - - // Act: Call retrieveCommits to get the last 5 commits - // Arrange: Similar setup as above - Path databaseFile = tempDir.resolve("test.bib"); - Files.writeString(databaseFile, "Initial content"); - - BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); - bibDatabaseContext.setDatabasePath(databaseFile); - - Path backupDirectory = tempDir.resolve("backup"); - Files.createDirectories(backupDirectory); - - when(preferences.getFilePreferences().getBackupDirectory()).thenReturn(backupDirectory); - - BackupManagerGit backupManager = BackupManagerGit.start( - libraryTab, - bibDatabaseContext, - entryTypesManager, - preferences - ); - - List retrievedCommits = backupManager.retrieveCommits(backupDir, 5); - - // Assert: Verify the number of commits retrieved - assertEquals(5, retrievedCommits.size(), "Should retrieve the last 5 commits"); - - // Assert: Verify the content of the retrieved commits - for (int i = 0; i < 5; i++) { - RevCommit retrievedCommit = retrievedCommits.get(i); - int finalI = i; - RevCommit expectedCommit = StreamSupport.stream(git.log().call().spliterator(), false) - .filter(commit -> commit.getId().equals(commitIds.get(commitIds.size() - 5 + finalI))) - .findFirst() - .orElse(null); - - assertNotNull(expectedCommit, "Expected commit should exist in the repository"); - assertEquals(expectedCommit.getFullMessage(), retrievedCommit.getFullMessage(), - "Commit messages should match"); - assertEquals(expectedCommit.getId(), retrievedCommit.getId(), - "Commit IDs should match"); - } - } - - @Test - void testRetrieveCommitDetails() throws Exception { - // Create multiple commits in the Git repository - List commits = new ArrayList<>(); - for (int i = 1; i <= 5; i++) { - Path file = backupDir.resolve("file" + i + ".txt"); - Files.writeString(file, "Content of file " + i); - git.add().addFilepattern(".").call(); - RevCommit commit = git.commit().setMessage("Commit " + i).call(); - commits.add(commit); - } - - // Act: Call retrieveCommitDetails to get the details of the commits - // Arrange: Similar setup as above - Path databaseFile = tempDir.resolve("test.bib"); - Files.writeString(databaseFile, "Initial content"); - - BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); - bibDatabaseContext.setDatabasePath(databaseFile); - - Path backupDirectory = tempDir.resolve("backup"); - Files.createDirectories(backupDirectory); - - when(preferences.getFilePreferences().getBackupDirectory()).thenReturn(backupDirectory); - - BackupManagerGit backupManager = BackupManagerGit.start( - libraryTab, - bibDatabaseContext, - entryTypesManager, - preferences - ); - List commitDetails = BackupManagerGit.retrieveCommitDetails(commits, backupDir); - - // Assert: Verify the number of commits - assertEquals(5, commitDetails.size(), "Should retrieve details for 5 commits"); - - // Assert: Verify the content of the retrieved commit details - for (int i = 0; i < 5; i++) { - List commitInfo = (List) commitDetails.get(i); - RevCommit commit = commits.get(i); - - // Verify commit ID - assertEquals(commit.getName(), commitInfo.get(0), "Commit ID should match"); - - // Verify commit size (this is a bit tricky, so just check it's a valid size string) - String sizeFormatted = commitInfo.get(1); - assertTrue(sizeFormatted.contains("Ko") || sizeFormatted.contains("Mo"), "Commit size should be properly formatted"); - - // Verify commit date - String commitDate = commitInfo.get(2); - assertTrue(commitDate.contains(commit.getAuthorIdent().getWhen().toString()), "Commit date should match"); - } - } -} - - - - - - - From c219a98cd3e11b53f29789cf283551f2a802ee5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Mon, 2 Dec 2024 01:20:35 +0100 Subject: [PATCH 53/84] correct comment --- src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index e881631bda5..375459d688d 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -268,7 +268,7 @@ private boolean save(Path targetPath, SaveDatabaseMode mode) { } private boolean saveDatabase(Path file, boolean selectedOnly, Charset encoding, BibDatabaseWriter.SaveType saveType, SelfContainedSaveOrder saveOrder) throws SaveException { - // if this code is adapted, please also adapt org.jabref.logic.autosaveandbackup.BackupManagerGit.performBackup + // if this code is adapted, please also adapt org.jabref.logic.autosaveandbackup.BackupManager.performBackup SelfContainedSaveConfiguration saveConfiguration = new SelfContainedSaveConfiguration(saveOrder, false, saveType, preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); BibDatabaseContext bibDatabaseContext = libraryTab.getBibDatabaseContext(); From 5ae740735c13c3fc8b78e9019b2523afbcd25bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Mon, 2 Dec 2024 21:40:57 +0100 Subject: [PATCH 54/84] Resolve the comment : Remove this here, we already have a newer version of jgit some lines down --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 528c5fded26..70befe38f8c 100644 --- a/build.gradle +++ b/build.gradle @@ -156,7 +156,7 @@ jacoco { dependencies { // Include all jar-files in the 'lib' folder as dependencies implementation fileTree(dir: 'lib', includes: ['*.jar']) - implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r' + def pdfbox = "3.0.3" implementation ("org.apache.pdfbox:pdfbox:$pdfbox") { exclude group: 'commons-logging' From ebe6f6b7a03e35a91b7836a62883cc6c06ab8588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Mon, 2 Dec 2024 21:46:31 +0100 Subject: [PATCH 55/84] Resolve the comment : delete this file (module-info.java), it's not needed --- src/jmh/java/module-info.java | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 src/jmh/java/module-info.java diff --git a/src/jmh/java/module-info.java b/src/jmh/java/module-info.java deleted file mode 100644 index 4c0962156dc..00000000000 --- a/src/jmh/java/module-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * - */ -/** - * - */ -module JabRef { -} \ No newline at end of file From b0cc7f7b6ed8fd9c8a64ccdc089387d7306a0733 Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Wed, 4 Dec 2024 00:06:30 +0100 Subject: [PATCH 56/84] Adding BackupManagerGitTest class (with 5/5 passed tests) --- .../autosaveandbackup/BackupManagerGit.java | 47 +++++- .../BackupManagerGitTest.java | 157 ++++++++++++++++++ .../autosaveandbackup/BackupManagerTest.java | 1 - .../BackupManagerTestJGit.java | 4 - 4 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java delete mode 100644 src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTestJGit.java diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 89f8cefc246..54ae9f59c04 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -17,6 +17,7 @@ import java.util.Set; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.jabref.gui.LibraryTab; @@ -46,9 +47,9 @@ public class BackupManagerGit { private static final Logger LOGGER = LoggerFactory.getLogger(BackupManagerGit.class); - private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; + private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 4; - private static Set runningInstances = new HashSet(); + static Set runningInstances = new HashSet(); private static Git git; @@ -62,6 +63,14 @@ public class BackupManagerGit { private boolean needsBackup = false; BackupManagerGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { + // Get the path of the BibDatabase file + Path dbFile = bibDatabaseContext.getDatabasePath().orElseThrow(() -> new IllegalArgumentException("Database path is not provided.")); + + if (!Files.exists(dbFile)) { + LOGGER.error("Database file does not exist: {}", dbFile); + throw new IOException("Database file not found: " + dbFile); + } + this.bibDatabaseContext = bibDatabaseContext; LOGGER.info("Backup manager initialized for file: {}", bibDatabaseContext.getDatabasePath().orElseThrow()); this.entryTypesManager = entryTypesManager; @@ -86,7 +95,7 @@ public class BackupManagerGit { } // Get the path of the BibDatabase file - Path dbFile = bibDatabaseContext.getDatabasePath().orElseThrow(() -> new IllegalArgumentException("Database path is not provided.")); + Path dbFilePath = bibDatabaseContext.getDatabasePath().orElseThrow(() -> new IllegalArgumentException("Database path is not provided.")); // Copy the database file to the backup directory Path backupFilePath = backupDirPath.resolve(dbFile.getFileName()); @@ -99,7 +108,24 @@ public class BackupManagerGit { } } - private static void ensureGitInitialized(Path backupDir) throws IOException, GitAPIException { + private static String normalizeBibTeX(String input) { + if (input == null || input.isBlank()) { + return ""; + } + + // Diviser les lignes et traiter chaque ligne + Stream lines = input.lines(); + + // Normalisation des lignes + String normalized = lines + .map(String::trim) // Supprimer les espaces en début et fin de ligne + .filter(line -> !line.isBlank()) // Supprimer les lignes vides + .collect(Collectors.joining("\n")); // Réassembler avec des sauts de ligne + + return normalized; + } + + static void ensureGitInitialized(Path backupDir) throws IOException, GitAPIException { // This method was created because the initialization of the Git object, when written in the constructor, was causing a NullPointerException // because the first method called when loading the database is BackupGitdiffers @@ -140,6 +166,7 @@ private static void ensureGitInitialized(Path backupDir) throws IOException, Git */ public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { + LOGGER.info("In methode Start"); BackupManagerGit backupManagerGit = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); backupManagerGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), bibDatabaseContext); runningInstances.add(backupManagerGit); @@ -170,7 +197,7 @@ public static void shutdown(BibDatabaseContext bibDatabaseContext, boolean creat */ void startBackupTask(Path backupDir, BibDatabaseContext bibDatabaseContext) { - LOGGER.info("Initializing backup task for directory: {} and file: {}", backupDir); + LOGGER.info("Initializing backup task for directory: {} and file: {}", backupDir, bibDatabaseContext.getDatabasePath().orElseThrow()); executor.scheduleAtFixedRate( () -> { try { @@ -229,7 +256,8 @@ protected void performBackup(Path backupDir) throws IOException, GitAPIException RevCommit commit = git.commit() .setMessage("Backup at " + Instant.now().toString()) .call(); - LOGGER.info("Backup committed in :" + backupDir + " with commit ID: " + commit.getName()); + LOGGER.info("Backup committed in :" + backupDir + " with commit ID: " + commit.getName() + + " for the file : {}", bibDatabaseContext.getDatabasePath().orElseThrow()); } public synchronized void listen(BibDatabaseContextChangedEvent event) { @@ -309,6 +337,7 @@ public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAP LOGGER.info("Checking file: {}", relativePath); try { + // Check if the file exists in the latest commit ObjectId objectId = repository.resolve("HEAD:" + relativePath.toString().replace("\\", "/")); if (objectId == null) { @@ -318,8 +347,10 @@ public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAP // Compare the content of the file in the Git repository with the current file ObjectLoader loader = repository.open(objectId); - String committedContent = new String(loader.getBytes(), StandardCharsets.UTF_8); - String currentContent = Files.readString(path, StandardCharsets.UTF_8); + String committedContent = normalizeBibTeX(new String(loader.getBytes(), StandardCharsets.UTF_8)); + String currentContent = normalizeBibTeX(Files.readString(path, StandardCharsets.UTF_8)); + LOGGER.info("Committed content: {}", committedContent); + LOGGER.info("Current content: {}", currentContent); // If the contents differ, return true if (!currentContent.equals(committedContent)) { diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java new file mode 100644 index 00000000000..9f5792634a4 --- /dev/null +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -0,0 +1,157 @@ + +package org.jabref.gui.autosaveandbackup; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.jabref.gui.LibraryTab; +import org.jabref.logic.FilePreferences; +import org.jabref.logic.preferences.CliPreferences; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.metadata.MetaData; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BackupManagerGitTest { + + private Path tempDir1; + private Path tempDir2; + private Path tempDir; + private LibraryTab mockLibraryTab; + private BibDatabaseContext mockDatabaseContext1; + private BibDatabaseContext mockDatabaseContext2; + private BibEntryTypesManager mockEntryTypesManager; + private CliPreferences mockPreferences; + private Path mockDatabasePath1; + private Path mockDatabasePath2; + + @BeforeEach + public void setUp(@TempDir Path tempDir) throws IOException, GitAPIException { + mockLibraryTab = mock(LibraryTab.class); + mockDatabaseContext1 = mock(BibDatabaseContext.class); + mockDatabaseContext2 = mock(BibDatabaseContext.class); + mockEntryTypesManager = mock(BibEntryTypesManager.class); + mockPreferences = mock(CliPreferences.class); + + FilePreferences filePreferences = mock(FilePreferences.class); + when(mockPreferences.getFilePreferences()).thenReturn(filePreferences); + + // Create temporary backup directories + + this.tempDir = tempDir.resolve(""); + this.tempDir1 = tempDir.resolve("backup1"); + this.tempDir2 = tempDir.resolve("backup2"); + Files.createDirectories(this.tempDir); // Ensure the directory exists + Files.createDirectories(this.tempDir1); // Ensure the directory exists + Files.createDirectories(this.tempDir2); // Ensure the directory exists + + // Mock the database paths and create the actual files + mockDatabasePath1 = tempDir1.resolve("test1.bib"); + mockDatabasePath2 = tempDir2.resolve("test2.bib"); + + Files.writeString(mockDatabasePath1, "Mock content for testing 1"); // Create the file + Files.writeString(mockDatabasePath2, "Mock content for testing 2"); // Create the file + + when(mockDatabaseContext1.getDatabasePath()).thenReturn(java.util.Optional.of(mockDatabasePath1)); + when(mockDatabaseContext2.getDatabasePath()).thenReturn(java.util.Optional.of(mockDatabasePath2)); + + when(filePreferences.getBackupDirectory()).thenReturn(tempDir); + + // Mock BibDatabase for all contexts + BibDatabase mockDatabase1 = mock(BibDatabase.class); + when(mockDatabaseContext1.getDatabase()).thenReturn(mockDatabase1); + + BibDatabase mockDatabase2 = mock(BibDatabase.class); + when(mockDatabaseContext2.getDatabase()).thenReturn(mockDatabase2); + + // Mock MetaData for all contexts (if needed elsewhere) + MetaData mockMetaData1 = mock(MetaData.class); + when(mockDatabaseContext1.getMetaData()).thenReturn(mockMetaData1); + + MetaData mockMetaData2 = mock(MetaData.class); + when(mockDatabaseContext2.getMetaData()).thenReturn(mockMetaData2); + } + + @AfterEach + void tearDown() throws IOException { + // Delete the temporary directory + Files.walk(tempDir) + .map(Path::toFile) + .forEach(file -> { + if (!file.delete()) { + file.deleteOnExit(); + } + }); + } + + @Test + void testInitializationCreatesBackupDirectory() throws IOException, GitAPIException { + // Create BackupManagerGit + BackupManagerGit manager1 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, mockPreferences); + BackupManagerGit manager2 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext2, mockEntryTypesManager, mockPreferences); + // Check if the backup directory exists + assertTrue(Files.exists(tempDir), " directory should be created wich contains .git and single copies og .bib"); + assertTrue(Files.exists(tempDir1), "Backup directory should be created during initialization."); + assertTrue(Files.exists(tempDir2), "Backup directory should be created during initialization."); + } + + @Test + void testGitInitialization() throws IOException, GitAPIException { + // Initialize Git + BackupManagerGit.ensureGitInitialized(tempDir); + + // Verify that the .git directory is created + Path gitDir = tempDir.resolve(".git"); + assertTrue(Files.exists(gitDir), ".git directory should be created during Git initialization."); + } + + @Test + void testBackupFileCopiedToDirectory() throws IOException, GitAPIException { + + BackupManagerGit manager1 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, mockPreferences); + BackupManagerGit manager2 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext2, mockEntryTypesManager, mockPreferences); + + // Verify the file is copied to the backup directory + Path backupFile1 = tempDir.resolve(this.mockDatabasePath1.getFileName()); + Path backupFile2 = tempDir.resolve(this.mockDatabasePath2.getFileName()); + assertTrue(Files.exists(backupFile1), "Database file should be copied to the backup directory."); + } + + @Test + public void testStart() throws IOException, GitAPIException { + BackupManagerGit startedManager = BackupManagerGit.start(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, mockPreferences); + assertNotNull(startedManager); + } + + @Test + void testPerformBackupCommitsChanges() throws IOException, GitAPIException { + // Initialize Git + BackupManagerGit.ensureGitInitialized(tempDir); + + // Create a test file + Path dbFile1 = tempDir.resolve("test1.bib"); + Files.writeString(dbFile1, "Initial content of test 1"); + + // Create BackupManagerGit and perform backup + BackupManagerGit manager = new BackupManagerGit(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, mockPreferences); + manager.performBackup(tempDir); + + // Verify that changes are committed + try (Git git = Git.open(tempDir.toFile())) { + assertTrue(git.status().call().isClean(), "Git repository should have no uncommitted changes after backup."); + } + } +} diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java index a45d7cbb9c1..3233cf47a13 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java @@ -20,7 +20,6 @@ import org.jabref.model.groups.event.GroupUpdatedEvent; import org.jabref.model.metadata.MetaData; import org.jabref.model.metadata.event.MetaDataChangedEvent; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTestJGit.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTestJGit.java deleted file mode 100644 index 971a51f8a73..00000000000 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTestJGit.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.jabref.gui.autosaveandbackup; - -public class BackupManagerTestJGit { -} From 7085fc49373cf8d64e29c6bebf695e312f05222a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 4 Dec 2024 13:19:50 +0100 Subject: [PATCH 57/84] - Implement the unique file names in order to avoid conflicts when copying two files with the same name in the git repo - delete BackupManager.java, BackupManagerDiscardedTest.java, BackupManagerTest.java, the project building is successful --- src/main/java/org/jabref/gui/LibraryTab.java | 3 +- .../gui/autosaveandbackup/BackupManager.java | 396 ------------------ .../autosaveandbackup/BackupManagerGit.java | 110 +++-- .../gui/exporter/SaveDatabaseAction.java | 2 +- .../BackupManagerDiscardedTest.java | 109 ----- .../autosaveandbackup/BackupManagerTest.java | 190 --------- 6 files changed, 73 insertions(+), 737 deletions(-) delete mode 100644 src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java delete mode 100644 src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerDiscardedTest.java delete mode 100644 src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 181cc9fa5bb..b27c7c70f33 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -803,8 +803,7 @@ private void onClosed(Event event) { } try { BackupManagerGit.shutdown(bibDatabaseContext, - preferences.getFilePreferences().shouldCreateBackup(), - preferences.getFilePreferences().getBackupDirectory()); + preferences.getFilePreferences().shouldCreateBackup()); } catch (RuntimeException e) { LOGGER.error("Problem when shutting down backup manager", e); } diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java deleted file mode 100644 index e6458520653..00000000000 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java +++ /dev/null @@ -1,396 +0,0 @@ -package org.jabref.gui.autosaveandbackup; - -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileTime; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import javafx.scene.control.TableColumn; - -import org.jabref.gui.LibraryTab; -import org.jabref.gui.maintable.BibEntryTableViewModel; -import org.jabref.gui.maintable.columns.MainTableColumn; -import org.jabref.logic.bibtex.InvalidFieldValueException; -import org.jabref.logic.exporter.AtomicFileWriter; -import org.jabref.logic.exporter.BibWriter; -import org.jabref.logic.exporter.BibtexDatabaseWriter; -import org.jabref.logic.exporter.SelfContainedSaveConfiguration; -import org.jabref.logic.preferences.CliPreferences; -import org.jabref.logic.util.BackupFileType; -import org.jabref.logic.util.CoarseChangeFilter; -import org.jabref.logic.util.io.BackupFileUtil; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.database.event.BibDatabaseContextChangedEvent; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.metadata.SaveOrder; -import org.jabref.model.metadata.SelfContainedSaveOrder; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Backups the given bib database file from {@link BibDatabaseContext} on every {@link BibDatabaseContextChangedEvent}. - * An intelligent {@link ExecutorService} with a {@link BlockingQueue} prevents a high load while making backups and - * rejects all redundant backup tasks. This class does not manage the .bak file which is created when opening a - * database. - */ - -public class BackupManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(BackupManager.class); - - private static final int MAXIMUM_BACKUP_FILE_COUNT = 10; - - private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; - - private static Set runningInstances = new HashSet<>(); - - private final BibDatabaseContext bibDatabaseContext; - private final CliPreferences preferences; - private final ScheduledThreadPoolExecutor executor; - private final CoarseChangeFilter changeFilter; - private final BibEntryTypesManager entryTypesManager; - private final LibraryTab libraryTab; - - // Contains a list of all backup paths - // During writing, the less recent backup file is deleted - private final Queue backupFilesQueue = new LinkedBlockingQueue<>(); - private boolean needsBackup = false; - - BackupManager(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { - this.bibDatabaseContext = bibDatabaseContext; - this.entryTypesManager = entryTypesManager; - this.preferences = preferences; - this.executor = new ScheduledThreadPoolExecutor(2); - this.libraryTab = libraryTab; - - changeFilter = new CoarseChangeFilter(bibDatabaseContext); - changeFilter.registerListener(this); - } - - /** - * Determines the most recent backup file name - */ - static Path getBackupPathForNewBackup(Path originalPath, Path backupDir) { - return BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(originalPath, BackupFileType.BACKUP, backupDir); - } - - /** - * Determines the most recent existing backup file name - */ - static Optional getLatestBackupPath(Path originalPath, Path backupDir) { - return BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); - } - - /** - * Starts the BackupManager which is associated with the given {@link BibDatabaseContext}. As long as no database - * file is present in {@link BibDatabaseContext}, the {@link BackupManager} will do nothing. - * - * This method is not thread-safe. The caller has to ensure that this method is not called in parallel. - * - * @param bibDatabaseContext Associated {@link BibDatabaseContext} - */ - public static BackupManager start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) { - BackupManager BackupManager = new BackupManager(libraryTab, bibDatabaseContext, entryTypesManager, preferences); - BackupManager.startBackupTask(preferences.getFilePreferences().getBackupDirectory()); - runningInstances.add(BackupManager); - return BackupManager; - } - - /** - * Marks the backup as discarded at the library which is associated with the given {@link BibDatabaseContext}. - * - * @param bibDatabaseContext Associated {@link BibDatabaseContext} - */ - public static void discardBackup(BibDatabaseContext bibDatabaseContext, Path backupDir) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(BackupManager -> BackupManager.discardBackup(backupDir)); - } - - /** - * Shuts down the BackupManager which is associated with the given {@link BibDatabaseContext}. - * - * @param bibDatabaseContext Associated {@link BibDatabaseContext} - * @param createBackup True, if a backup should be created - * @param backupDir The path to the backup directory - */ - public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { - runningInstances.stream().filter(instance -> instance.bibDatabaseContext == bibDatabaseContext).forEach(BackupManager -> BackupManager.shutdown(backupDir, createBackup)); - runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); - } - - /** - * Checks whether a backup file exists for the given database file. If it exists, it is checked whether it is - * newer and different from the original. - * - * In case a discarded file is present, the method also returns false, See also {@link #discardBackup(Path)}. - * - * @param originalPath Path to the file a backup should be checked for. Example: jabref.bib. - * - * @return true if backup file exists AND differs from originalPath. false is the - * "default" return value in the good case. In case a discarded file exists, false is returned, too. - * In the case of an exception true is returned to ensure that the user checks the output. - */ - public static boolean backupFileDiffers(Path originalPath, Path backupDir) { - Path discardedFile = determineDiscardedFile(originalPath, backupDir); - if (Files.exists(discardedFile)) { - try { - Files.delete(discardedFile); - } catch (IOException e) { - LOGGER.error("Could not remove discarded file {}", discardedFile, e); - return true; - } - return false; - } - return getLatestBackupPath(originalPath, backupDir).map(latestBackupPath -> { - FileTime latestBackupFileLastModifiedTime; - try { - latestBackupFileLastModifiedTime = Files.getLastModifiedTime(latestBackupPath); - } catch (IOException e) { - LOGGER.debug("Could not get timestamp of backup file {}", latestBackupPath, e); - // If we cannot get the timestamp, we do show any warning - return false; - } - FileTime currentFileLastModifiedTime; - try { - currentFileLastModifiedTime = Files.getLastModifiedTime(originalPath); - } catch (IOException e) { - LOGGER.debug("Could not get timestamp of current file file {}", originalPath, e); - // If we cannot get the timestamp, we do show any warning - return false; - } - if (latestBackupFileLastModifiedTime.compareTo(currentFileLastModifiedTime) <= 0) { - // Backup is older than current file - // We treat the backup as non-different (even if it could differ) - return false; - } - try { - boolean result = Files.mismatch(originalPath, latestBackupPath) != -1L; - if (result) { - LOGGER.info("Backup file {} differs from current file {}", latestBackupPath, originalPath); - } - return result; - } catch (IOException e) { - LOGGER.debug("Could not compare original file and backup file.", e); - // User has to investigate in this case - return true; - } - }).orElse(false); - } - - /** - * Restores the backup file by copying and overwriting the original one. - * - * @param originalPath Path to the file which should be equalized to the backup file. - */ - public static void restoreBackup(Path originalPath, Path backupDir) { - Optional backupPath = getLatestBackupPath(originalPath, backupDir); - if (backupPath.isEmpty()) { - LOGGER.error("There is no backup file"); - return; - } - try { - Files.copy(backupPath.get(), originalPath, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - LOGGER.error("Error while restoring the backup file.", e); - } - } - - Optional determineBackupPathForNewBackup(Path backupDir) { - return bibDatabaseContext.getDatabasePath().map(path -> BackupManager.getBackupPathForNewBackup(path, backupDir)); - } - - /** - * This method is called as soon as the scheduler says: "Do the backup" - * - * SIDE EFFECT: Deletes oldest backup file - * - * @param backupPath the full path to the file where the library should be backed up to - */ - void performBackup(Path backupPath) { - if (!needsBackup) { - return; - } - - // We opted for "while" to delete backups in case there are more than 10 - while (backupFilesQueue.size() >= MAXIMUM_BACKUP_FILE_COUNT) { - Path oldestBackupFile = backupFilesQueue.poll(); - try { - Files.delete(oldestBackupFile); - } catch (IOException e) { - LOGGER.error("Could not delete backup file {}", oldestBackupFile, e); - } - } - - // l'ordre dans lequel les entrées BibTeX doivent être écrites dans le fichier de sauvegarde. - // Si l'utilisateur a trié la table d'affichage des entrées dans JabRef, cet ordre est récupéré. - // Sinon, un ordre par défaut est utilisé. - // code similar to org.jabref.gui.exporter.SaveDatabaseAction.saveDatabase - SelfContainedSaveOrder saveOrder = bibDatabaseContext - .getMetaData().getSaveOrder() - .map(so -> { - if (so.getOrderType() == SaveOrder.OrderType.TABLE) { - // We need to "flatten out" SaveOrder.OrderType.TABLE as BibWriter does not have access to preferences - List> sortOrder = libraryTab.getMainTable().getSortOrder(); - return new SelfContainedSaveOrder( - SaveOrder.OrderType.SPECIFIED, - sortOrder.stream() - .filter(col -> col instanceof MainTableColumn) - .map(column -> ((MainTableColumn) column).getModel()) - .flatMap(model -> model.getSortCriteria().stream()) - .toList()); - } else { - return SelfContainedSaveOrder.of(so); - } - }) - .orElse(SaveOrder.getDefaultSaveOrder()); - - // Elle configure la sauvegarde, en indiquant qu'aucune sauvegarde supplémentaire (backup) ne doit être créée, - // que l'ordre de sauvegarde doit être celui défini, et que les entrées doivent être formatées selon les préférences - // utilisateur. - SelfContainedSaveConfiguration saveConfiguration = (SelfContainedSaveConfiguration) new SelfContainedSaveConfiguration() - .withMakeBackup(false) - .withSaveOrder(saveOrder) - .withReformatOnSave(preferences.getLibraryPreferences().shouldAlwaysReformatOnSave()); - - // "Clone" the database context - // We "know" that "only" the BibEntries might be changed during writing (see [org.jabref.logic.exporter.BibDatabaseWriter.savePartOfDatabase]) - // Chaque entrée BibTeX (comme un article, livre, etc.) est clonée en utilisant la méthode clone(). - // Cela garantit que les modifications faites pendant la sauvegarde n'affecteront pas l'entrée originale. - List list = bibDatabaseContext.getDatabase().getEntries().stream() - .map(BibEntry::clone) - .map(BibEntry.class::cast) - .toList(); - BibDatabase bibDatabaseClone = new BibDatabase(list); - BibDatabaseContext bibDatabaseContextClone = new BibDatabaseContext(bibDatabaseClone, bibDatabaseContext.getMetaData()); - // Elle définit l'encodage à utiliser pour écrire le fichier. Cela garantit que les caractères spéciaux sont bien sauvegardés. - Charset encoding = bibDatabaseContext.getMetaData().getEncoding().orElse(StandardCharsets.UTF_8); - // We want to have successful backups only - // Thus, we do not use a plain "FileWriter", but the "AtomicFileWriter" - // Example: What happens if one hard powers off the machine (or kills the jabref process) during writing of the backup? - // This MUST NOT create a broken backup file that then jabref wants to "restore" from? - try (Writer writer = new AtomicFileWriter(backupPath, encoding, false)) { - BibWriter bibWriter = new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator()); - new BibtexDatabaseWriter( - bibWriter, - saveConfiguration, - preferences.getFieldPreferences(), - preferences.getCitationKeyPatternPreferences(), - entryTypesManager) - // we save the clone to prevent the original database (and thus the UI) from being changed - .saveDatabase(bibDatabaseContextClone); - backupFilesQueue.add(backupPath); - - // We wrote the file successfully - // Thus, we currently do not need any new backup - this.needsBackup = false; - } catch (IOException e) { - logIfCritical(backupPath, e); - } - } - - private static Path determineDiscardedFile(Path file, Path backupDir) { - return backupDir.resolve(BackupFileUtil.getUniqueFilePrefix(file) + "--" + file.getFileName() + "--discarded"); - } - - /** - * Marks the backups as discarded. - * - * We do not delete any files, because the user might want to recover old backup files. - * Therefore, we mark discarded backups by a --discarded file. - */ - public void discardBackup(Path backupDir) { - Path path = determineDiscardedFile(bibDatabaseContext.getDatabasePath().get(), backupDir); - try { - Files.createFile(path); - } catch (IOException e) { - LOGGER.info("Could not create backup file {}", path, e); - } - } - - private void logIfCritical(Path backupPath, IOException e) { - Throwable innermostCause = e; - while (innermostCause.getCause() != null) { - innermostCause = innermostCause.getCause(); - } - boolean isErrorInField = innermostCause instanceof InvalidFieldValueException; - - // do not print errors in field values into the log during autosave - if (!isErrorInField) { - LOGGER.error("Error while saving to file {}", backupPath, e); - } - } - - public synchronized void listen(BibDatabaseContextChangedEvent event) { - if (!event.isFilteredOut()) { - this.needsBackup = true; - } - } - - private void startBackupTask(Path backupDir) { - fillQueue(backupDir); - // remplie backupFilesQueue les files .sav de le meme bibl - - executor.scheduleAtFixedRate( - // We need to determine the backup path on each action, because we use the timestamp in the filename - () -> determineBackupPathForNewBackup(backupDir).ifPresent(path -> this.performBackup(path)), - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS, - TimeUnit.SECONDS); - } - // La méthode fillQueue(backupDir) est définie dans le code et son rôle est de lister et d'ajouter - // les fichiers de sauvegarde existants dans une file d'attente, - - private void fillQueue(Path backupDir) { - if (!Files.exists(backupDir)) { - return; - } - bibDatabaseContext.getDatabasePath().ifPresent(databasePath -> { - // code similar to {@link org.jabref.logic.util.io.BackupFileUtil.getPathOfLatestExisingBackupFile} - final String prefix = BackupFileUtil.getUniqueFilePrefix(databasePath) + "--" + databasePath.getFileName(); - try { - List allSavFiles = Files.list(backupDir) - // just list the .sav belonging to the given targetFile - .filter(p -> p.getFileName().toString().startsWith(prefix)) - // tous les files .sav commencerait par ce prefix - .sorted().toList(); - backupFilesQueue.addAll(allSavFiles); - } catch (IOException e) { - LOGGER.error("Could not determine most recent file", e); - } - }); - } - - /** - * Unregisters the BackupManager from the eventBus of {@link BibDatabaseContext}. - * This method should only be used when closing a database/JabRef in a normal way. - * - * @param backupDir The backup directory - * @param createBackup If the backup manager should still perform a backup - */ - private void shutdown(Path backupDir, boolean createBackup) { - changeFilter.unregisterListener(this); - changeFilter.shutdown(); - executor.shutdown(); - - if (createBackup) { - // Ensure that backup is a recent one - determineBackupPathForNewBackup(backupDir).ifPresent(this::performBackup); - } - } -} diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 54ae9f59c04..44ecb6f71bc 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -15,6 +15,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -45,11 +46,11 @@ public class BackupManagerGit { - private static final Logger LOGGER = LoggerFactory.getLogger(BackupManagerGit.class); + static Set runningInstances = new HashSet(); - private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 4; + private static final Logger LOGGER = LoggerFactory.getLogger(BackupManagerGit.class); - static Set runningInstances = new HashSet(); + private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; private static Git git; @@ -63,9 +64,7 @@ public class BackupManagerGit { private boolean needsBackup = false; BackupManagerGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { - // Get the path of the BibDatabase file Path dbFile = bibDatabaseContext.getDatabasePath().orElseThrow(() -> new IllegalArgumentException("Database path is not provided.")); - if (!Files.exists(dbFile)) { LOGGER.error("Database file does not exist: {}", dbFile); throw new IOException("Database file not found: " + dbFile); @@ -81,11 +80,9 @@ public class BackupManagerGit { changeFilter = new CoarseChangeFilter(bibDatabaseContext); changeFilter.registerListener(this); - // Ensure the backup directory exists Path backupDirPath = preferences.getFilePreferences().getBackupDirectory(); LOGGER.info("Backup directory path: {}", backupDirPath); - // Ensure Git is initialized ensureGitInitialized(backupDirPath); File backupDir = backupDirPath.toFile(); @@ -94,20 +91,54 @@ public class BackupManagerGit { throw new IOException("Unable to create backup directory: " + backupDir); } - // Get the path of the BibDatabase file - Path dbFilePath = bibDatabaseContext.getDatabasePath().orElseThrow(() -> new IllegalArgumentException("Database path is not provided.")); + copyDatabaseFileToBackupDir(dbFile, backupDirPath); + } - // Copy the database file to the backup directory - Path backupFilePath = backupDirPath.resolve(dbFile.getFileName()); - try { - Files.copy(dbFile, backupFilePath, StandardCopyOption.REPLACE_EXISTING); - LOGGER.info("Database file copied to backup directory: {}", backupFilePath); - } catch (IOException e) { - LOGGER.error("Failed to copy database file to backup directory", e); - throw new IOException("Error copying database file to backup directory", e); + /** + * Appends a UUID to a file name, keeping the original extension. + * + * @param originalFileName The original file name (e.g., library.bib). + * @param uuid The UUID to append. + * @return The modified file name with the UUID (e.g., library_123e4567-e89b-12d3-a456-426614174000.bib). + */ + private String appendUuidToFileName(String originalFileName, String uuid) { + int dotIndex = originalFileName.lastIndexOf('.'); + if (dotIndex == -1) { + // If there's no extension, just append the UUID + return originalFileName + "_" + uuid; } + + // Insert the UUID before the extension + String baseName = originalFileName.substring(0, dotIndex); + String extension = originalFileName.substring(dotIndex); + return baseName + "_" + uuid + extension; } + /** + * Retrieves or generates a persistent unique identifier (UUID) for the given file. + * The UUID is stored in an extended attribute or a metadata file alongside the original file. + * + * @param filePath The path to the file. + * @return The UUID associated with the file. + * @throws IOException If an error occurs while accessing or creating the UUID. + */ + private String getOrGenerateFileUuid(Path filePath) throws IOException { + // Define a hidden metadata file to store the UUID + Path metadataFile = filePath.resolveSibling("." + filePath.getFileName().toString() + ".uuid"); + + // If the UUID metadata file exists, read it + if (Files.exists(metadataFile)) { + return Files.readString(metadataFile).trim(); + } + + // Otherwise, generate a new UUID and save it + String uuid = UUID.randomUUID().toString(); + Files.writeString(metadataFile, uuid); + LOGGER.info("Generated new UUID for file {}: {}", filePath, uuid); + return uuid; + } + + // Helper method to normalize BibTeX content private static String normalizeBibTeX(String input) { if (input == null || input.isBlank()) { return ""; @@ -125,6 +156,7 @@ private static String normalizeBibTeX(String input) { return normalized; } + // Helper method to ensure the Git repository is initialized static void ensureGitInitialized(Path backupDir) throws IOException, GitAPIException { // This method was created because the initialization of the Git object, when written in the constructor, was causing a NullPointerException @@ -153,6 +185,19 @@ static void ensureGitInitialized(Path backupDir) throws IOException, GitAPIExcep git = new Git(repository); } + // Helper method to copy the database file to the backup directory + private void copyDatabaseFileToBackupDir(Path dbFile, Path backupDirPath) throws IOException { + String fileUuid = getOrGenerateFileUuid(dbFile); + String uniqueFileName = appendUuidToFileName(dbFile.getFileName().toString(), fileUuid); + Path backupFilePath = backupDirPath.resolve(uniqueFileName); + if (!Files.exists(backupFilePath) || Files.mismatch(dbFile, backupFilePath) != -1) { + Files.copy(dbFile, backupFilePath, StandardCopyOption.REPLACE_EXISTING); + LOGGER.info("Database file uniquely copied to backup directory: {}", backupFilePath); + } else { + LOGGER.info("No changes detected; skipping backup for file: {}", uniqueFileName); + } + } + /** * Starts a new BackupManagerGit instance and begins the backup task. * @@ -178,16 +223,15 @@ public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext b * * @param bibDatabaseContext the BibDatabaseContext * @param createBackup whether to create a backup before shutting down - * @param originalPath the original path of the file to be backed up */ - public static void shutdown(BibDatabaseContext bibDatabaseContext, boolean createBackup, Path originalPath) { + public static void shutdown(BibDatabaseContext bibDatabaseContext, boolean createBackup) { runningInstances.stream() .filter(instance -> instance.bibDatabaseContext == bibDatabaseContext) - .forEach(backupManager -> backupManager.shutdownGit(bibDatabaseContext.getDatabasePath().orElseThrow().getParent().resolve("backup"), createBackup, originalPath)); + .forEach(backupManager -> backupManager.shutdownGit(bibDatabaseContext.getDatabasePath().orElseThrow().getParent().resolve("backup"), createBackup)); // Remove the instances associated with the BibDatabaseContext after shutdown runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); - LOGGER.info("Shut down backup manager for file: {}", originalPath); + LOGGER.info("Shut down backup manager for file: {}"); } /** @@ -201,19 +245,8 @@ void startBackupTask(Path backupDir, BibDatabaseContext bibDatabaseContext) { executor.scheduleAtFixedRate( () -> { try { - // Copy the database file to the backup directory Path dbFile = bibDatabaseContext.getDatabasePath().orElseThrow(() -> new IllegalArgumentException("Database path is not provided.")); - - // Copy the database file to the backup directory (overwriting any existing file) - Path backupFilePath = backupDir.resolve(dbFile.getFileName()); - try { - Files.copy(dbFile, backupFilePath, StandardCopyOption.REPLACE_EXISTING); - LOGGER.info("Database file copied to backup directory: {}", backupFilePath); - } catch (IOException e) { - LOGGER.error("Failed to copy database file to backup directory", e); - throw new IOException("Error copying database file to backup directory", e); - } - + copyDatabaseFileToBackupDir(dbFile, backupDir); performBackup(backupDir); } catch (IOException | GitAPIException e) { LOGGER.error("Error during backup", e); @@ -500,21 +533,20 @@ public static List retrieveCommitDetails(List commits, P * Shuts down the JGit components and optionally creates a backup. * * @param createBackup whether to create a backup before shutting down - * @param originalPath the original path of the file to be backed up */ - private void shutdownGit(Path backupDir, boolean createBackup, Path originalPath) { + private void shutdownGit(Path backupDir, boolean createBackup) { // Unregister the listener and shut down the change filter if (changeFilter != null) { changeFilter.unregisterListener(this); changeFilter.shutdown(); - LOGGER.info("Shut down change filter for file: {}", originalPath); + LOGGER.info("Shut down change filter"); } // Shut down the executor if it's not already shut down if (executor != null && !executor.isShutdown()) { executor.shutdown(); - LOGGER.info("Shut down backup task for file: {}", originalPath); + LOGGER.info("Shut down backup task for file: {}"); } // If backup is requested, ensure that we perform the Git-based backup @@ -522,9 +554,9 @@ private void shutdownGit(Path backupDir, boolean createBackup, Path originalPath try { // Ensure the backup is a recent one by performing the Git commit performBackup(backupDir); - LOGGER.info("Backup created on shutdown for file: {}", originalPath); + LOGGER.info("Backup created on shutdown for file: {}"); } catch (IOException | GitAPIException e) { - LOGGER.error("Error during Git backup on shutdown", e); + LOGGER.error("Error during Git backup on shutdown"); } } } diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 375459d688d..a2d8e15bdf5 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -149,7 +149,7 @@ boolean saveAs(Path file, SaveDatabaseMode mode) throws GitAPIException, IOExcep if (databasePath.isPresent()) { // Close AutosaveManager, BackupManagerGit, and IndexManager for original library AutosaveManager.shutdown(context); - BackupManagerGit.shutdown(context, preferences.getFilePreferences().shouldCreateBackup(), databasePath.get()); + BackupManagerGit.shutdown(context, preferences.getFilePreferences().shouldCreateBackup()); libraryTab.closeIndexManger(); } diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerDiscardedTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerDiscardedTest.java deleted file mode 100644 index 7619e0c9ae2..00000000000 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerDiscardedTest.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.jabref.gui.autosaveandbackup; - -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.jabref.gui.LibraryTab; -import org.jabref.logic.exporter.AtomicFileWriter; -import org.jabref.logic.exporter.BibDatabaseWriter; -import org.jabref.logic.exporter.BibWriter; -import org.jabref.logic.exporter.BibtexDatabaseWriter; -import org.jabref.logic.exporter.SelfContainedSaveConfiguration; -import org.jabref.logic.preferences.CliPreferences; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.entry.field.StandardField; -import org.jabref.model.metadata.SaveOrder; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Answers; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; - -/** - * Test for "discarded" flag - */ -class BackupManagerDiscardedTest { - - private BibDatabaseContext bibDatabaseContext; - private BackupManager backupManager; - private Path testBib; - private SelfContainedSaveConfiguration saveConfiguration; - private CliPreferences preferences; - private BibEntryTypesManager bibEntryTypesManager; - private Path backupDir; - - @BeforeEach - void setup(@TempDir Path tempDir) throws Exception { - this.backupDir = tempDir.resolve("backups"); - Files.createDirectories(backupDir); - - testBib = tempDir.resolve("test.bib"); - - bibDatabaseContext = new BibDatabaseContext(new BibDatabase()); - bibDatabaseContext.setDatabasePath(testBib); - - bibEntryTypesManager = new BibEntryTypesManager(); - saveConfiguration = new SelfContainedSaveConfiguration(SaveOrder.getDefaultSaveOrder(), false, BibDatabaseWriter.SaveType.WITH_JABREF_META_DATA, false); - preferences = mock(CliPreferences.class, Answers.RETURNS_DEEP_STUBS); - - saveDatabase(); - - backupManager = new BackupManager(mock(LibraryTab.class), bibDatabaseContext, bibEntryTypesManager, preferences); - - makeBackup(); - } - - private void saveDatabase() throws IOException { - try (Writer writer = new AtomicFileWriter(testBib, StandardCharsets.UTF_8, false)) { - BibWriter bibWriter = new BibWriter(writer, bibDatabaseContext.getDatabase().getNewLineSeparator()); - new BibtexDatabaseWriter( - bibWriter, - saveConfiguration, - preferences.getFieldPreferences(), - preferences.getCitationKeyPatternPreferences(), - bibEntryTypesManager) - .saveDatabase(bibDatabaseContext); - } - } - - private void databaseModification() { - bibDatabaseContext.getDatabase().insertEntry(new BibEntry().withField(StandardField.NOTE, "test")); - } - - private void makeBackup() { - backupManager.determineBackupPathForNewBackup(backupDir).ifPresent(path -> backupManager.performBackup(path)); - } - - @Test - void noDiscardingAChangeLeadsToNewerBackupBeReported() throws Exception { - databaseModification(); - makeBackup(); - assertTrue(BackupManager.backupFileDiffers(testBib, backupDir)); - } - - @Test - void noDiscardingASavedChange() throws Exception { - databaseModification(); - makeBackup(); - saveDatabase(); - assertFalse(BackupManager.backupFileDiffers(testBib, backupDir)); - } - - @Test - void discardingAChangeLeadsToNewerBackupToBeIgnored() throws Exception { - databaseModification(); - makeBackup(); - backupManager.discardBackup(backupDir); - assertFalse(BackupManager.backupFileDiffers(testBib, backupDir)); - } -} diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java deleted file mode 100644 index 3233cf47a13..00000000000 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerTest.java +++ /dev/null @@ -1,190 +0,0 @@ -package org.jabref.gui.autosaveandbackup; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.FileTime; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.jabref.gui.LibraryTab; -import org.jabref.logic.FilePreferences; -import org.jabref.logic.preferences.CliPreferences; -import org.jabref.logic.util.BackupFileType; -import org.jabref.logic.util.Directories; -import org.jabref.logic.util.io.BackupFileUtil; -import org.jabref.model.database.BibDatabase; -import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.groups.event.GroupUpdatedEvent; -import org.jabref.model.metadata.MetaData; -import org.jabref.model.metadata.event.MetaDataChangedEvent; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Answers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class BackupManagerTest { - - Path backupDir; - - @BeforeEach - void setup(@TempDir Path tempDir) { - backupDir = tempDir.resolve("backup"); - } - - @Test - void backupFileNameIsCorrectlyGeneratedInAppDataDirectory() { - Path bibPath = Path.of("tmp", "test.bib"); - backupDir = Directories.getBackupDirectory(); - Path bakPath = BackupManager.getBackupPathForNewBackup(bibPath, backupDir); - - // Pattern is "27182d3c--test.bib--", but the hashing is implemented differently on Linux than on Windows - assertNotEquals("", bakPath); - } - - @Test - void backupFileIsEqualForNonExistingBackup() throws Exception { - Path originalFile = Path.of(BackupManagerTest.class.getResource("no-autosave.bib").toURI()); - assertFalse(BackupManager.backupFileDiffers(originalFile, backupDir)); - } - - @Test - void backupFileIsEqual() throws Exception { - // Prepare test: Create backup file on "right" path - Path source = Path.of(BackupManagerTest.class.getResource("no-changes.bib.bak").toURI()); - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(Path.of(BackupManagerTest.class.getResource("no-changes.bib").toURI()), BackupFileType.BACKUP, backupDir); - Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); - - Path originalFile = Path.of(BackupManagerTest.class.getResource("no-changes.bib").toURI()); - assertFalse(BackupManager.backupFileDiffers(originalFile, backupDir)); - } - - @Test - void backupFileDiffers() throws Exception { - // Prepare test: Create backup file on "right" path - Path source = Path.of(BackupManagerTest.class.getResource("changes.bib.bak").toURI()); - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()), BackupFileType.BACKUP, backupDir); - Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); - - Path originalFile = Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()); - assertTrue(BackupManager.backupFileDiffers(originalFile, backupDir)); - } - - @Test - void correctBackupFileDeterminedForMultipleBakFiles() throws Exception { - Path noChangesBib = Path.of(BackupManagerTest.class.getResource("no-changes.bib").toURI()); - Path noChangesBibBak = Path.of(BackupManagerTest.class.getResource("no-changes.bib.bak").toURI()); - - // Prepare test: Create backup files on "right" path - // most recent file does not have any changes - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(noChangesBib, BackupFileType.BACKUP, backupDir); - Files.copy(noChangesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - - // create "older" .bak files containing changes - for (int i = 0; i < 10; i++) { - Path changesBibBak = Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()); - Path directory = backupDir; - String timeSuffix = "2020-02-03--00.00.0" + Integer.toString(i); - String fileName = BackupFileUtil.getUniqueFilePrefix(noChangesBib) + "--no-changes.bib--" + timeSuffix + ".bak"; - target = directory.resolve(fileName); - Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - } - - Path originalFile = noChangesBib; - assertFalse(BackupManager.backupFileDiffers(originalFile, backupDir)); - } - - @Test - void bakFileWithNewerTimeStampLeadsToDiff() throws Exception { - Path changesBib = Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()); - Path changesBibBak = Path.of(BackupManagerTest.class.getResource("changes.bib.bak").toURI()); - - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(changesBib, BackupFileType.BACKUP, backupDir); - Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - - assertTrue(BackupManager.backupFileDiffers(changesBib, backupDir)); - } - - @Test - void bakFileWithOlderTimeStampDoesNotLeadToDiff() throws Exception { - Path changesBib = Path.of(BackupManagerTest.class.getResource("changes.bib").toURI()); - Path changesBibBak = Path.of(BackupManagerTest.class.getResource("changes.bib.bak").toURI()); - - Path target = BackupFileUtil.getPathForNewBackupFileAndCreateDirectory(changesBib, BackupFileType.BACKUP, backupDir); - Files.copy(changesBibBak, target, StandardCopyOption.REPLACE_EXISTING); - - // Make .bak file very old - Files.setLastModifiedTime(target, FileTime.fromMillis(0)); - - assertFalse(BackupManager.backupFileDiffers(changesBib, backupDir)); - } - - @Test - void shouldNotCreateABackup(@TempDir Path customDir) throws Exception { - Path backupDir = customDir.resolve("subBackupDir"); - Files.createDirectories(backupDir); - - var database = new BibDatabaseContext(new BibDatabase()); - database.setDatabasePath(customDir.resolve("Bibfile.bib")); - - var preferences = mock(CliPreferences.class, Answers.RETURNS_DEEP_STUBS); - var filePreferences = mock(FilePreferences.class); - when(preferences.getFilePreferences()).thenReturn(filePreferences); - when(filePreferences.getBackupDirectory()).thenReturn(backupDir); - when(filePreferences.shouldCreateBackup()).thenReturn(false); - - BackupManager manager = BackupManager.start( - mock(LibraryTab.class), - database, - mock(BibEntryTypesManager.class, Answers.RETURNS_DEEP_STUBS), - preferences); - manager.listen(new MetaDataChangedEvent(new MetaData())); - - BackupManager.shutdown(database, filePreferences.getBackupDirectory(), filePreferences.shouldCreateBackup()); - - List files = Files.list(backupDir).toList(); - assertEquals(Collections.emptyList(), files); - } - - @Test - void shouldCreateABackup(@TempDir Path customDir) throws Exception { - Path backupDir = customDir.resolve("subBackupDir"); - Files.createDirectories(backupDir); - - var database = new BibDatabaseContext(new BibDatabase()); - database.setDatabasePath(customDir.resolve("Bibfile.bib")); - - var preferences = mock(CliPreferences.class, Answers.RETURNS_DEEP_STUBS); - var filePreferences = mock(FilePreferences.class); - when(preferences.getFilePreferences()).thenReturn(filePreferences); - when(filePreferences.getBackupDirectory()).thenReturn(backupDir); - when(filePreferences.shouldCreateBackup()).thenReturn(true); - - BackupManager manager = BackupManager.start( - mock(LibraryTab.class), - database, - mock(BibEntryTypesManager.class, Answers.RETURNS_DEEP_STUBS), - preferences); - manager.listen(new MetaDataChangedEvent(new MetaData())); - - Optional fullBackupPath = manager.determineBackupPathForNewBackup(backupDir); - fullBackupPath.ifPresent(manager::performBackup); - manager.listen(new GroupUpdatedEvent(new MetaData())); - - BackupManager.shutdown(database, backupDir, true); - - List files = Files.list(backupDir).sorted().toList(); - // we only know the first backup path because the second one is created on shutdown - // due to timing issues we cannot test that reliable - assertEquals(fullBackupPath.get(), files.getFirst()); - } -} From 3532d2eb9881fd7c99b5f08b371c84a6dff3703f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 4 Dec 2024 16:12:05 +0100 Subject: [PATCH 58/84] The popup is available again --- src/main/java/org/jabref/gui/LibraryTab.java | 1 + .../autosaveandbackup/BackupManagerGit.java | 123 +++++++++--------- .../gui/exporter/SaveDatabaseAction.java | 2 +- .../importer/actions/OpenDatabaseAction.java | 2 +- 4 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index b27c7c70f33..5c2041a8eb3 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -803,6 +803,7 @@ private void onClosed(Event event) { } try { BackupManagerGit.shutdown(bibDatabaseContext, + preferences.getFilePreferences().getBackupDirectory(), preferences.getFilePreferences().shouldCreateBackup()); } catch (RuntimeException e) { LOGGER.error("Problem when shutting down backup manager", e); diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 44ecb6f71bc..77db0b20b98 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -80,18 +80,17 @@ public class BackupManagerGit { changeFilter = new CoarseChangeFilter(bibDatabaseContext); changeFilter.registerListener(this); - Path backupDirPath = preferences.getFilePreferences().getBackupDirectory(); - LOGGER.info("Backup directory path: {}", backupDirPath); + LOGGER.info("Backup directory path: {}", preferences.getFilePreferences().getBackupDirectory()); - ensureGitInitialized(backupDirPath); + ensureGitInitialized(preferences.getFilePreferences().getBackupDirectory()); - File backupDir = backupDirPath.toFile(); - if (!backupDir.exists() && !backupDir.mkdirs()) { - LOGGER.error("Failed to create backup directory: {}", backupDir); - throw new IOException("Unable to create backup directory: " + backupDir); + File backupDirFile = preferences.getFilePreferences().getBackupDirectory().toFile(); + if (!backupDirFile.exists() && !backupDirFile.mkdirs()) { + LOGGER.error("Failed to create backup directory: {}", preferences.getFilePreferences().getBackupDirectory()); + throw new IOException("Unable to create backup directory: " + preferences.getFilePreferences().getBackupDirectory()); } - copyDatabaseFileToBackupDir(dbFile, backupDirPath); + copyDatabaseFileToBackupDir(dbFile, preferences.getFilePreferences().getBackupDirectory()); } /** @@ -101,7 +100,7 @@ public class BackupManagerGit { * @param uuid The UUID to append. * @return The modified file name with the UUID (e.g., library_123e4567-e89b-12d3-a456-426614174000.bib). */ - private String appendUuidToFileName(String originalFileName, String uuid) { + private static String appendUuidToFileName(String originalFileName, String uuid) { int dotIndex = originalFileName.lastIndexOf('.'); if (dotIndex == -1) { // If there's no extension, just append the UUID @@ -122,7 +121,7 @@ private String appendUuidToFileName(String originalFileName, String uuid) { * @return The UUID associated with the file. * @throws IOException If an error occurs while accessing or creating the UUID. */ - private String getOrGenerateFileUuid(Path filePath) throws IOException { + private static String getOrGenerateFileUuid(Path filePath) throws IOException { // Define a hidden metadata file to store the UUID Path metadataFile = filePath.resolveSibling("." + filePath.getFileName().toString() + ".uuid"); @@ -186,7 +185,7 @@ static void ensureGitInitialized(Path backupDir) throws IOException, GitAPIExcep } // Helper method to copy the database file to the backup directory - private void copyDatabaseFileToBackupDir(Path dbFile, Path backupDirPath) throws IOException { + private static void copyDatabaseFileToBackupDir(Path dbFile, Path backupDirPath) throws IOException { String fileUuid = getOrGenerateFileUuid(dbFile); String uniqueFileName = appendUuidToFileName(dbFile.getFileName().toString(), fileUuid); Path backupFilePath = backupDirPath.resolve(uniqueFileName); @@ -198,6 +197,8 @@ private void copyDatabaseFileToBackupDir(Path dbFile, Path backupDirPath) throws } } + // A method + /** * Starts a new BackupManagerGit instance and begins the backup task. * @@ -224,10 +225,12 @@ public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext b * @param bibDatabaseContext the BibDatabaseContext * @param createBackup whether to create a backup before shutting down */ - public static void shutdown(BibDatabaseContext bibDatabaseContext, boolean createBackup) { + public static void shutdown(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { runningInstances.stream() .filter(instance -> instance.bibDatabaseContext == bibDatabaseContext) - .forEach(backupManager -> backupManager.shutdownGit(bibDatabaseContext.getDatabasePath().orElseThrow().getParent().resolve("backup"), createBackup)); + .forEach(backupManager -> backupManager.shutdownGit(bibDatabaseContext, + backupDir, + createBackup)); // Remove the instances associated with the BibDatabaseContext after shutdown runningInstances.removeIf(instance -> instance.bibDatabaseContext == bibDatabaseContext); @@ -247,7 +250,7 @@ void startBackupTask(Path backupDir, BibDatabaseContext bibDatabaseContext) { try { Path dbFile = bibDatabaseContext.getDatabasePath().orElseThrow(() -> new IllegalArgumentException("Database path is not provided.")); copyDatabaseFileToBackupDir(dbFile, backupDir); - performBackup(backupDir); + performBackup(dbFile, backupDir); } catch (IOException | GitAPIException e) { LOGGER.error("Error during backup", e); } @@ -262,13 +265,14 @@ void startBackupTask(Path backupDir, BibDatabaseContext bibDatabaseContext) { * Performs the backup by checking for changes and committing them to the Git repository. * * @param backupDir the backup directory + * @param dbfile the database file * @throws IOException if an I/O error occurs * @throws GitAPIException if a Git API error occurs */ - protected void performBackup(Path backupDir) throws IOException, GitAPIException { + protected void performBackup(Path dbfile, Path backupDir) throws IOException, GitAPIException { - boolean needsCommit = backupGitDiffers(backupDir); + boolean needsCommit = backupGitDiffers(dbfile, backupDir); if (!needsBackup && !needsCommit) { LOGGER.info("No changes detected, beacuse needsBackup is :" + needsBackup + " and needsCommit is :" + needsCommit); @@ -337,9 +341,13 @@ public static void restoreBackup(Path backupDir, ObjectId objectId) { * @throws GitAPIException if a Git API error occurs */ - public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAPIException { + public static boolean backupGitDiffers(Path dbFile, Path backupDir) throws IOException, GitAPIException { + + // Ensure the specific database file is copied to the backup directory + copyDatabaseFileToBackupDir(dbFile, backupDir); + // Ensure the Git repository exists - LOGGER.info("Checking if backup differs for directory: {}", backupDir); + LOGGER.info("Checking if backup differs for file: {}", dbFile); // Open the Git repository located in the backup directory Repository repository = openGitRepository(backupDir); @@ -352,53 +360,40 @@ public static boolean backupGitDiffers(Path backupDir) throws IOException, GitAP } LOGGER.info("HEAD commit ID: {}", headCommitId.getName()); - // Iterate over the files in the backup directory to check if they differ from the repository - try (Stream paths = Files.walk(backupDir)) { - for (Path path : paths.filter(Files::isRegularFile).toList()) { - // Ignore non-.bib files (e.g., .DS_Store) - if (!path.toString().endsWith(".bib")) { - continue; // Skip .bib files - } - - // Skip .git directory files - if (path.toString().contains(".git")) { - continue; - } - - // Calculate the relative path in the Git repository - Path relativePath = backupDir.relativize(path); - LOGGER.info("Checking file: {}", relativePath); - - try { + // Compute the repository file name using the naming convention (filename + UUID) + String baseName = dbFile.getFileName().toString(); + String uuid = getOrGenerateFileUuid(dbFile); // Generate or retrieve the UUID for this file + String repoFileName = baseName.replace(".bib", "") + "_" + uuid + ".bib"; + Path relativePath = Path.of(repoFileName); + LOGGER.info("Checking repository file: {}", relativePath); - // Check if the file exists in the latest commit - ObjectId objectId = repository.resolve("HEAD:" + relativePath.toString().replace("\\", "/")); - if (objectId == null) { - LOGGER.info("File not found in the latest commit: {}. Assuming it differs.", relativePath); - return true; - } + try { + // Check if the file exists in the latest commit + ObjectId objectId = repository.resolve("HEAD:" + relativePath.toString().replace("\\", "/")); + if (objectId == null) { + LOGGER.info("File not found in the latest commit: {}. Assuming it differs.", relativePath); + return true; + } - // Compare the content of the file in the Git repository with the current file - ObjectLoader loader = repository.open(objectId); - String committedContent = normalizeBibTeX(new String(loader.getBytes(), StandardCharsets.UTF_8)); - String currentContent = normalizeBibTeX(Files.readString(path, StandardCharsets.UTF_8)); - LOGGER.info("Committed content: {}", committedContent); - LOGGER.info("Current content: {}", currentContent); - - // If the contents differ, return true - if (!currentContent.equals(committedContent)) { - LOGGER.info("Content differs for file: {}", relativePath); - return true; - } - } catch (MissingObjectException e) { - // If the file is missing from the commit, assume it differs - LOGGER.info("File not found in the latest commit: {}. Assuming it differs.", relativePath); - return true; - } + // Compare the content of the file in the Git repository with the current file + ObjectLoader loader = repository.open(objectId); + String committedContent = normalizeBibTeX(new String(loader.getBytes(), StandardCharsets.UTF_8)); + String currentContent = normalizeBibTeX(Files.readString(dbFile, StandardCharsets.UTF_8)); + LOGGER.info("Committed content: {}", committedContent); + LOGGER.info("Current content: {}", currentContent); + + // If the contents differ, return true + if (!currentContent.equals(committedContent)) { + LOGGER.info("Content differs for file: {}", relativePath); + return true; } + } catch (MissingObjectException e) { + // If the file is missing from the commit, assume it differs + LOGGER.info("File not found in the latest commit: {}. Assuming it differs.", relativePath); + return true; } - LOGGER.info("No differences found in the backup."); + LOGGER.info("No differences found for the file: {}", dbFile); return false; // No differences found } @@ -533,9 +528,11 @@ public static List retrieveCommitDetails(List commits, P * Shuts down the JGit components and optionally creates a backup. * * @param createBackup whether to create a backup before shutting down + * @param backupDir the backup directory + * @param bibDatabaseContext the BibDatabaseContext */ - private void shutdownGit(Path backupDir, boolean createBackup) { + private void shutdownGit(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { // Unregister the listener and shut down the change filter if (changeFilter != null) { changeFilter.unregisterListener(this); @@ -552,8 +549,10 @@ private void shutdownGit(Path backupDir, boolean createBackup) { // If backup is requested, ensure that we perform the Git-based backup if (createBackup) { try { + // Get the file path of the database + Path dbFile = bibDatabaseContext.getDatabasePath().orElseThrow(() -> new IllegalArgumentException("Database path is not provided.")); // Ensure the backup is a recent one by performing the Git commit - performBackup(backupDir); + performBackup(dbFile, backupDir); LOGGER.info("Backup created on shutdown for file: {}"); } catch (IOException | GitAPIException e) { LOGGER.error("Error during Git backup on shutdown"); diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index a2d8e15bdf5..727fc394eff 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -149,7 +149,7 @@ boolean saveAs(Path file, SaveDatabaseMode mode) throws GitAPIException, IOExcep if (databasePath.isPresent()) { // Close AutosaveManager, BackupManagerGit, and IndexManager for original library AutosaveManager.shutdown(context); - BackupManagerGit.shutdown(context, preferences.getFilePreferences().shouldCreateBackup()); + BackupManagerGit.shutdown(context, preferences.getFilePreferences().getBackupDirectory(), preferences.getFilePreferences().shouldCreateBackup()); libraryTab.closeIndexManger(); } 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 2c2b14f5f27..e1acb386292 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -266,7 +266,7 @@ private ParserResult loadDatabase(Path file) throws Exception { } ParserResult parserResult = null; - if (BackupManagerGit.backupGitDiffers(backupDir)) { + if (BackupManagerGit.backupGitDiffers(fileToLoad, backupDir)) { // In case the backup differs, ask the user what to do. LOGGER.info("Backup differs from saved file, ask the user what to do"); // In case the user opted for restoring a backup, the content of the backup is contained in parserResult. From 08b3c0c7e9f9c8386cae1fb2ef62379f80d9a815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 4 Dec 2024 17:44:59 +0100 Subject: [PATCH 59/84] Restore backup that functions --- .../autosaveandbackup/BackupManagerGit.java | 61 ++++++++++++++----- .../jabref/gui/dialogs/BackupUIManager.java | 4 +- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 77db0b20b98..e0fc41f8c64 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.FileDescriptor; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -137,6 +138,25 @@ private static String getOrGenerateFileUuid(Path filePath) throws IOException { return uuid; } + /** + * Rewrites the content of the file at the specified path with the given string. + * + * @param dbFile The path to the file to be rewritten. + * @param content The string content to write into the file. + * @throws IOException If an I/O error occurs during the write operation. + */ + public static void rewriteFile(Path dbFile, String content) throws IOException { + // Ensure the file exists before rewriting + if (!Files.exists(dbFile)) { + throw new FileNotFoundException("The file at path " + dbFile + " does not exist."); + } + + // Write the new content to the file (overwrite mode) + Files.writeString(dbFile, content, StandardCharsets.UTF_8); + + LOGGER.info("Successfully rewrote the file at path: {}", dbFile); + } + // Helper method to normalize BibTeX content private static String normalizeBibTeX(String input) { if (input == null || input.isBlank()) { @@ -311,24 +331,33 @@ public synchronized void listen(BibDatabaseContextChangedEvent event) { * @param objectId the commit ID to restore from */ - public static void restoreBackup(Path backupDir, ObjectId objectId) { - try { - Git git = Git.open(backupDir.toFile()); - - git.checkout().setStartPoint(objectId.getName()).setAllPaths(true).call(); - LOGGER.info("checkout done"); - - // Add commits to staging Area - git.add().addFilepattern(".").call(); + public static void restoreBackup(Path dbFile, Path backupDir, ObjectId objectId) { + try (Repository repository = openGitRepository(backupDir); + Git git = new Git(repository)) { + // Resolve the filename of dbFile in the repository + String baseName = dbFile.getFileName().toString(); + String uuid = getOrGenerateFileUuid(dbFile); // Generate or retrieve the UUID for this file + String relativeFilePath = baseName.replace(".bib", "") + "_" + uuid + ".bib"; + LOGGER.info("Relative file path TO RESTORE: {}", relativeFilePath); + String gitPath = backupDir.relativize(backupDir.resolve(relativeFilePath)).toString().replace("\\", "/"); + + LOGGER.info("Restoring file: {}", gitPath); + + // Load the content of the file from the specified commit + ObjectId fileObjectId = repository.resolve(objectId.getName() + ":" + gitPath); + if (fileObjectId == null) { + throw new IllegalArgumentException("File not found in commit: " + objectId.getName()); + } - // Commit with a message - git.commit().setMessage("Restored content from commit: " + objectId.getName()).call(); + // Read the content of the file from the Git object + ObjectLoader loader = repository.open(fileObjectId); + String fileContent = new String(loader.getBytes(), StandardCharsets.UTF_8); - LOGGER.info("Restored backup from Git repository and committed the changes"); - } catch ( - IOException | - GitAPIException e) { - LOGGER.error("Error while restoring the backup", e); + // Rewrite the original file at dbFile path + rewriteFile(dbFile, fileContent); + LOGGER.info("Restored content to: {}", dbFile); + } catch (IOException | IllegalArgumentException e) { + LOGGER.error("Error while restoring the backup: {}", e.getMessage(), e); } } diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 4e328cc6c4d..0dec3bf796e 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -69,7 +69,7 @@ public static Optional showRestoreBackupDialog(DialogService dialo List backups = BackupManagerGit.retrieveCommitDetails(commits, preferences.getFilePreferences().getBackupDirectory()).reversed(); if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { ObjectId commitId = backups.getFirst().getId(); - BackupManagerGit.restoreBackup(preferences.getFilePreferences().getBackupDirectory(), commitId); + BackupManagerGit.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory(), commitId); return Optional.empty(); } else if (action == BackupResolverDialog.REVIEW_BACKUP) { return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); @@ -81,7 +81,7 @@ public static Optional showRestoreBackupDialog(DialogService dialo if (recordBackupChoice.get().action() == BackupChoiceDialog.RESTORE_BACKUP) { LOGGER.warn(recordBackupChoice.get().entry().getSize()); ObjectId commitId = recordBackupChoice.get().entry().getId(); - BackupManagerGit.restoreBackup(preferences.getFilePreferences().getBackupDirectory(), commitId); + BackupManagerGit.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory(), commitId); return Optional.empty(); } if (recordBackupChoice.get().action() == BackupChoiceDialog.REVIEW_BACKUP) { From 2c6da4b8b4d610e28bcf265986b55c3597017642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 4 Dec 2024 19:16:07 +0100 Subject: [PATCH 60/84] Corrected retrieve commits / commits details --- .../autosaveandbackup/BackupManagerGit.java | 146 +++++++++++++----- .../jabref/gui/dialogs/BackupUIManager.java | 4 +- 2 files changed, 109 insertions(+), 41 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index e0fc41f8c64..ec30593cc72 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -11,7 +11,6 @@ import java.nio.file.StandardCopyOption; import java.time.Instant; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -332,8 +331,7 @@ public synchronized void listen(BibDatabaseContextChangedEvent event) { */ public static void restoreBackup(Path dbFile, Path backupDir, ObjectId objectId) { - try (Repository repository = openGitRepository(backupDir); - Git git = new Git(repository)) { + try (Repository repository = openGitRepository(backupDir)) { // Resolve the filename of dbFile in the repository String baseName = dbFile.getFileName().toString(); String uuid = getOrGenerateFileUuid(dbFile); // Generate or retrieve the UUID for this file @@ -345,8 +343,8 @@ public static void restoreBackup(Path dbFile, Path backupDir, ObjectId objectId) // Load the content of the file from the specified commit ObjectId fileObjectId = repository.resolve(objectId.getName() + ":" + gitPath); - if (fileObjectId == null) { - throw new IllegalArgumentException("File not found in commit: " + objectId.getName()); + if (fileObjectId == null) { // File not found in the commit + performBackupNoCommits(dbFile, backupDir); } // Read the content of the file from the Git object @@ -356,7 +354,10 @@ public static void restoreBackup(Path dbFile, Path backupDir, ObjectId objectId) // Rewrite the original file at dbFile path rewriteFile(dbFile, fileContent); LOGGER.info("Restored content to: {}", dbFile); - } catch (IOException | IllegalArgumentException e) { + } catch ( + IOException | + IllegalArgumentException | + GitAPIException e) { LOGGER.error("Error while restoring the backup: {}", e.getMessage(), e); } } @@ -385,7 +386,9 @@ public static boolean backupGitDiffers(Path dbFile, Path backupDir) throws IOExc ObjectId headCommitId = repository.resolve("HEAD"); if (headCommitId == null) { LOGGER.info("No commits found in the repository. Assuming the file differs."); - return true; + // perform a commit + performBackupNoCommits(dbFile, backupDir); + return false; } LOGGER.info("HEAD commit ID: {}", headCommitId.getName()); @@ -438,7 +441,7 @@ private static Repository openGitRepository(Path backupDir) throws IOException { /** * Shows the differences between the specified commit and the latest commit. * - * @param originalPath the original path of the file + * @param dbFile the path of the file * @param backupDir the backup directory * @param commitId the commit ID to compare with the latest commit * @return a list of DiffEntry objects representing the differences @@ -446,7 +449,7 @@ private static Repository openGitRepository(Path backupDir) throws IOException { * @throws GitAPIException if a Git API error occurs */ - public List showDiffers(Path originalPath, Path backupDir, String commitId) throws IOException, GitAPIException { + public List showDiffers(Path dbFile, Path backupDir, String commitId) throws IOException, GitAPIException { File repoDir = backupDir.toFile(); Repository repository = new FileRepositoryBuilder() @@ -468,6 +471,7 @@ need a class to show the last ten backups indicating: date/ size/ number of entr /** * Retrieves the last n commits from the Git repository. * + * @param dbFile the database file * @param backupDir the backup directory * @param n the number of commits to retrieve * @return a list of RevCommit objects representing the commits @@ -475,8 +479,15 @@ need a class to show the last ten backups indicating: date/ size/ number of entr * @throws GitAPIException if a Git API error occurs */ - public static List retrieveCommits(Path backupDir, int n) throws IOException, GitAPIException { + public static List retrieveCommits(Path dbFile, Path backupDir, int n) throws IOException, GitAPIException { List retrievedCommits = new ArrayList<>(); + + // Compute the repository file name using the naming convention (filename + UUID) + String baseName = dbFile.getFileName().toString(); + String uuid = getOrGenerateFileUuid(dbFile); // Generate or retrieve the UUID for this file + String repoFileName = baseName.replace(".bib", "") + "_" + uuid + ".bib"; + String dbFileRelativePath = backupDir.relativize(backupDir.resolve(repoFileName)).toString().replace("\\", "/"); + // Open Git repository try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { // Use RevWalk to traverse commits @@ -486,66 +497,96 @@ public static List retrieveCommits(Path backupDir, int n) throws IOEx int count = 0; for (RevCommit commit : revWalk) { - retrievedCommits.add(commit); - count++; - if (count == n) { - break; // Stop after collecting the required number of commits + // Check if this commit involves the dbFile + try (TreeWalk treeWalk = new TreeWalk(repository)) { + treeWalk.addTree(commit.getTree()); + treeWalk.setRecursive(true); + + boolean fileFound = false; + while (treeWalk.next()) { + if (treeWalk.getPathString().equals(dbFileRelativePath)) { + fileFound = true; + break; + } + } + + if (fileFound) { + retrievedCommits.add(commit); + count++; + if (count == n) { + break; // Stop after collecting the required number of commits + } + } } } } } - // Reverse the list to have commits in the correct order - Collections.reverse(retrievedCommits); return retrievedCommits; } /** - * Retrieves detailed information about the specified commits. + * Retrieves detailed information about the specified commits, focusing on the target file. * * @param commits the list of commits to retrieve details for + * @param dbFile the target file to retrieve details about * @param backupDir the backup directory - * @return a list of lists, each containing details about a commit + * @return a list of BackupEntry objects containing details about each commit * @throws IOException if an I/O error occurs * @throws GitAPIException if a Git API error occurs */ + public static List retrieveCommitDetails(List commits, Path dbFile, Path backupDir) throws IOException, GitAPIException { + List commitDetails = new ArrayList<>(); - public static List retrieveCommitDetails(List commits, Path backupDir) throws IOException, GitAPIException { - List commitDetails; - try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { - commitDetails = new ArrayList<>(); + // Compute the repository file name using the naming convention (filename + UUID) + String baseName = dbFile.getFileName().toString(); + String uuid = getOrGenerateFileUuid(dbFile); // Generate or retrieve the UUID for this file + String repoFileName = baseName.replace(".bib", "") + "_" + uuid + ".bib"; + String dbFileRelativePath = backupDir.relativize(backupDir.resolve(repoFileName)).toString().replace("\\", "/"); + try (Repository repository = Git.open(backupDir.toFile()).getRepository()) { // Browse the list of commits given as a parameter for (RevCommit commit : commits) { - // A list to stock details about the commit - List commitInfo = new ArrayList<>(); - commitInfo.add(commit.getName()); // ID of commit + // Variables to store commit-specific details + String sizeFormatted = "0 KB"; + long fileSize = 0; + boolean fileFound = false; - // Get the size of files changes by the commit - String sizeFormatted; + // Use TreeWalk to find the target file in the commit try (TreeWalk treeWalk = new TreeWalk(repository)) { treeWalk.addTree(commit.getTree()); treeWalk.setRecursive(true); - long totalSize = 0; while (treeWalk.next()) { - ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); - totalSize += loader.getSize(); // size in bytes + if (treeWalk.getPathString().equals(dbFileRelativePath)) { + // Calculate size of the target file + ObjectLoader loader = repository.open(treeWalk.getObjectId(0)); + fileSize = loader.getSize(); + fileFound = true; + break; + } } - // Convert the size to Kb or Mb - sizeFormatted = (totalSize > 1024 * 1024) - ? String.format("%.2f Mo", totalSize / (1024.0 * 1024.0)) - : String.format("%.2f Ko", totalSize / 1024.0); + // Convert size to KB or MB + sizeFormatted = (fileSize > 1024 * 1024) + ? String.format("%.2f MB", fileSize / (1024.0 * 1024.0)) + : String.format("%.2f KB", fileSize / 1024.0); + } - commitInfo.add(sizeFormatted); // Add Formatted size + // Skip this commit if the file was not found + if (!fileFound) { + continue; } - // adding date detail + // Add commit details Date date = commit.getAuthorIdent().getWhen(); - commitInfo.add(date.toString()); - // Add list of details to the main list - BackupEntry backupEntry = new BackupEntry(ObjectId.fromString(commit.getName()), commitInfo.get(0), commitInfo.get(2), commitInfo.get(1), 0); + BackupEntry backupEntry = new BackupEntry( + ObjectId.fromString(commit.getName()), // Commit ID + commit.getName(), // Commit ID as string + date.toString(), // Commit date + sizeFormatted, // Formatted file size + 1 // Number of relevant .bib files (always 1 for dbFile) + ); commitDetails.add(backupEntry); } } @@ -553,6 +594,33 @@ public static List retrieveCommitDetails(List commits, P return commitDetails; } + public static void performBackupNoCommits(Path dbFile, Path backupDir) throws IOException, GitAPIException { + + LOGGER.info("No commits found in the repository. We need a first commit."); + // Ensure the specific database file is copied to the backup directory + copyDatabaseFileToBackupDir(dbFile, backupDir); + + // Ensure the Git repository exists + LOGGER.info("Checking if backup differs for file: {}", dbFile); + ensureGitInitialized(backupDir); + + // Open the Git repository located in the backup directory + Repository repository = openGitRepository(backupDir); + + // Get the file name of the database file + String baseName = dbFile.getFileName().toString(); + String uuid = getOrGenerateFileUuid(dbFile); // Generate or retrieve the UUID for this file + String repoFileName = baseName.replace(".bib", "") + "_" + uuid + ".bib"; + + // Stage the file for commit + git.add().addFilepattern(repoFileName).call(); + + // Commit the staged changes + RevCommit commit = git.commit() + .setMessage("Backup at " + Instant.now().toString()) + .call(); + } + /** * Shuts down the JGit components and optionally creates a backup. * diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 0dec3bf796e..c43e6a95a91 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -65,8 +65,8 @@ public static Optional showRestoreBackupDialog(DialogService dialo return actionOpt.flatMap(action -> { try { - List commits = BackupManagerGit.retrieveCommits(preferences.getFilePreferences().getBackupDirectory(), -1); - List backups = BackupManagerGit.retrieveCommitDetails(commits, preferences.getFilePreferences().getBackupDirectory()).reversed(); + List commits = BackupManagerGit.retrieveCommits(originalPath, preferences.getFilePreferences().getBackupDirectory(), -1); + List backups = BackupManagerGit.retrieveCommitDetails(commits, originalPath, preferences.getFilePreferences().getBackupDirectory()).reversed(); if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { ObjectId commitId = backups.getFirst().getId(); BackupManagerGit.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory(), commitId); From bca3558813bfa500a7b697166e15eabe4f7a7709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 4 Dec 2024 19:41:14 +0100 Subject: [PATCH 61/84] Updated submodule abbrv.jabref.org --- buildres/abbrv.jabref.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildres/abbrv.jabref.org b/buildres/abbrv.jabref.org index d87037495de..0fdf99147a8 160000 --- a/buildres/abbrv.jabref.org +++ b/buildres/abbrv.jabref.org @@ -1 +1 @@ -Subproject commit d87037495de7213b896dbb6a20170387de170709 +Subproject commit 0fdf99147a8a5fc8ae7ccd79ad4e0029e736e4a3 From 7e63aa2af9217dddfda8b767b205ca1a35fab1b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Wed, 4 Dec 2024 19:56:56 +0100 Subject: [PATCH 62/84] Updated submodule csl-styles to latest commit --- src/main/resources/csl-styles | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/csl-styles b/src/main/resources/csl-styles index b413a778b81..49af15c4f5b 160000 --- a/src/main/resources/csl-styles +++ b/src/main/resources/csl-styles @@ -1 +1 @@ -Subproject commit b413a778b8170cf5aebbb9aeffec62cfd068e19e +Subproject commit 49af15c4f5bca025b6b18ca48c447016586f01e7 From 97caf3408fb51c28679bef02e222f68ced11faa0 Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Wed, 4 Dec 2024 22:35:10 +0100 Subject: [PATCH 63/84] Added support to review each change in a backup --- .../autosaveandbackup/BackupManagerGit.java | 46 ++++++++++++++++++- .../jabref/gui/dialogs/BackupUIManager.java | 24 ++++++---- 2 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index ec30593cc72..9dbcc0d60b4 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -429,6 +429,51 @@ public static boolean backupGitDiffers(Path dbFile, Path backupDir) throws IOExc return false; // No differences found } + public static Path getBackupFilePath(Path dbFile, Path backupDir) { + try { + String baseName = dbFile.getFileName().toString(); + String uuid = getOrGenerateFileUuid(dbFile); + String relativeFileName = baseName.replace(".bib", "") + "_" + uuid + ".bib"; + return backupDir.resolve(relativeFileName); + } catch ( + IOException e) { + throw new RuntimeException(e); + } + } + + public static void writeBackupFileToCommit(Path dbFile, Path backupDir, ObjectId objectId) { + try (Repository repository = openGitRepository(backupDir)) { + // Resolve the filename of dbFile in the repository + String baseName = dbFile.getFileName().toString(); + String uuid = getOrGenerateFileUuid(dbFile); // Generate or retrieve the UUID for this file + String relativeFilePath = baseName.replace(".bib", "") + "_" + uuid + ".bib"; + LOGGER.info("Relative file path TO RESTORE: {}", relativeFilePath); + String gitPath = backupDir.relativize(backupDir.resolve(relativeFilePath)).toString().replace("\\", "/"); + + LOGGER.info("Restoring file: {}", gitPath); + + // Load the content of the file from the specified commit + ObjectId fileObjectId = repository.resolve(objectId.getName() + ":" + gitPath); + if (fileObjectId == null) { // File not found in the commit + performBackupNoCommits(dbFile, backupDir); + } + + // Read the content of the file from the Git object + ObjectLoader loader = repository.open(fileObjectId); + String fileContent = new String(loader.getBytes(), StandardCharsets.UTF_8); + + Path backupFilePath = getBackupFilePath(dbFile, backupDir); + // Rewrite the original file at backupFilePath path + rewriteFile(backupFilePath, fileContent); + LOGGER.info("Restored content to: {}", dbFile); + } catch ( + IOException | + IllegalArgumentException | + GitAPIException e) { + LOGGER.error("Error while restoring the backup: {}", e.getMessage(), e); + } + } + private static Repository openGitRepository(Path backupDir) throws IOException { FileRepositoryBuilder builder = new FileRepositoryBuilder(); // Initialize Git repository from the backup directory @@ -628,7 +673,6 @@ public static void performBackupNoCommits(Path dbFile, Path backupDir) throws IO * @param backupDir the backup directory * @param bibDatabaseContext the BibDatabaseContext */ - private void shutdownGit(BibDatabaseContext bibDatabaseContext, Path backupDir, boolean createBackup) { // Unregister the listener and shut down the change filter if (changeFilter != null) { diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index c43e6a95a91..8c7333ebf83 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -29,8 +29,6 @@ import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.util.BackupFileType; -import org.jabref.logic.util.io.BackupFileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; @@ -72,7 +70,8 @@ public static Optional showRestoreBackupDialog(DialogService dialo BackupManagerGit.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory(), commitId); return Optional.empty(); } else if (action == BackupResolverDialog.REVIEW_BACKUP) { - return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); + ObjectId commitId = backups.getFirst().getId(); + return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager, commitId, commitId); } else if (action == BackupResolverDialog.COMPARE_OLDER_BACKUP) { var recordBackupChoice = showBackupChoiceDialog(dialogService, originalPath, preferences, backups); if (recordBackupChoice.isEmpty()) { @@ -86,7 +85,9 @@ public static Optional showRestoreBackupDialog(DialogService dialo } if (recordBackupChoice.get().action() == BackupChoiceDialog.REVIEW_BACKUP) { LOGGER.warn(recordBackupChoice.get().entry().getSize()); - return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); + ObjectId latestCommitId = backups.getFirst().getId(); + ObjectId commitId = recordBackupChoice.get().entry().getId(); + return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager, commitId, latestCommitId); } } } catch ( @@ -119,7 +120,9 @@ private static Optional showReviewBackupDialog( GuiPreferences preferences, FileUpdateMonitor fileUpdateMonitor, UndoManager undoManager, - StateManager stateManager) { + StateManager stateManager, + ObjectId commitIdToReview, + ObjectId latestCommitId) { try { ImportFormatPreferences importFormatPreferences = preferences.getImportFormatPreferences(); @@ -128,9 +131,13 @@ private static Optional showReviewBackupDialog( // This will be modified by using the `DatabaseChangesResolverDialog`. BibDatabaseContext originalDatabase = originalParserResult.getDatabaseContext(); - Path backupPath = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, preferences.getFilePreferences().getBackupDirectory()).orElseThrow(); - LOGGER.info("Ligne 127, BackupUIManager, Loading backup database from {}", backupPath); - BibDatabaseContext backupDatabase = OpenDatabase.loadDatabase(backupPath, importFormatPreferences, new DummyFileUpdateMonitor()).getDatabaseContext(); + Path backupPath = preferences.getFilePreferences().getBackupDirectory(); + + BackupManagerGit.writeBackupFileToCommit(originalPath, backupPath, commitIdToReview); + + Path backupFilePath = BackupManagerGit.getBackupFilePath(originalPath, backupPath); + + BibDatabaseContext backupDatabase = OpenDatabase.loadDatabase(backupFilePath, importFormatPreferences, new DummyFileUpdateMonitor()).getDatabaseContext(); DatabaseChangeResolverFactory changeResolverFactory = new DatabaseChangeResolverFactory(dialogService, originalDatabase, preferences); @@ -159,6 +166,7 @@ private static Optional showReviewBackupDialog( } // In case not all changes are resolved, start from scratch + BackupManagerGit.writeBackupFileToCommit(originalPath, backupPath, latestCommitId); return showRestoreBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager); }); } catch (IOException e) { From b1c006e69ba258748b2787d5d6b832179fdc36a3 Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Wed, 4 Dec 2024 23:03:04 +0100 Subject: [PATCH 64/84] Remove usage of BackupFileUtil in UI --- .../gui/backup/BackupResolverDialog.java | 38 +------------------ .../jabref/gui/dialogs/BackupUIManager.java | 18 +++++---- 2 files changed, 12 insertions(+), 44 deletions(-) diff --git a/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java b/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java index c7aad7811ba..aab94b961b3 100644 --- a/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java +++ b/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java @@ -1,23 +1,12 @@ 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.desktop.os.NativeDesktop; -import org.jabref.gui.frame.ExternalApplicationsPreferences; 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 latest backup"), ButtonBar.ButtonData.OK_DONE); @@ -25,38 +14,15 @@ public class BackupResolverDialog extends FXDialog { public static final ButtonType IGNORE_BACKUP = new ButtonType(Localization.lang("Ignore backup"), ButtonBar.ButtonData.CANCEL_CLOSE); public static final ButtonType COMPARE_OLDER_BACKUP = new ButtonType("Compare older backup", ButtonBar.ButtonData.LEFT); - private static final Logger LOGGER = LoggerFactory.getLogger(BackupResolverDialog.class); - - public BackupResolverDialog(Path originalPath, Path backupDir, ExternalApplicationsPreferences externalApplicationsPreferences) { + 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, COMPARE_OLDER_BACKUP); - Optional backupPathOpt = BackupFileUtil.getPathOfLatestExistingBackupFile(originalPath, BackupFileType.BACKUP, backupDir); - String backupFilename = backupPathOpt.map(Path::getFileName).map(Path::toString).orElse(Localization.lang("File not found")); - String content = Localization.lang("A backup file for '%0' was found at [%1]", originalPath.getFileName().toString(), backupFilename) + "\n" + + String content = Localization.lang("A backup file for '%0' was found.", originalPath.getFileName().toString()) + "\n" + Localization.lang("This could indicate that JabRef did not shut down cleanly last time the file was used.") + "\n\n" + Localization.lang("Do you want to recover the library from the backup file?"); 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 { - NativeDesktop.openFolderAndSelectFile(backupPathOpt.get(), externalApplicationsPreferences, null); - } catch (IOException ex) { - LOGGER.error("Could not open backup folder", ex); - } - } - } - }); - getDialogPane().setContent(contentLabel); } } diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 8c7333ebf83..652e152470b 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -21,7 +21,6 @@ import org.jabref.gui.collab.DatabaseChangeList; import org.jabref.gui.collab.DatabaseChangeResolverFactory; import org.jabref.gui.collab.DatabaseChangesResolverDialog; -import org.jabref.gui.frame.ExternalApplicationsPreferences; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.util.UiTaskExecutor; @@ -57,26 +56,31 @@ public static Optional showRestoreBackupDialog(DialogService dialo LOGGER.info("Show restore backup dialog"); var actionOpt = showBackupResolverDialog( dialogService, - preferences.getExternalApplicationsPreferences(), - originalPath, - preferences.getFilePreferences().getBackupDirectory()); + originalPath); return actionOpt.flatMap(action -> { try { + List commits = BackupManagerGit.retrieveCommits(originalPath, preferences.getFilePreferences().getBackupDirectory(), -1); List backups = BackupManagerGit.retrieveCommitDetails(commits, originalPath, preferences.getFilePreferences().getBackupDirectory()).reversed(); + if (action == BackupResolverDialog.RESTORE_FROM_BACKUP) { ObjectId commitId = backups.getFirst().getId(); + BackupManagerGit.restoreBackup(originalPath, preferences.getFilePreferences().getBackupDirectory(), commitId); + return Optional.empty(); } else if (action == BackupResolverDialog.REVIEW_BACKUP) { ObjectId commitId = backups.getFirst().getId(); + return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager, commitId, commitId); } else if (action == BackupResolverDialog.COMPARE_OLDER_BACKUP) { var recordBackupChoice = showBackupChoiceDialog(dialogService, originalPath, preferences, backups); + if (recordBackupChoice.isEmpty()) { return Optional.empty(); } + if (recordBackupChoice.get().action() == BackupChoiceDialog.RESTORE_BACKUP) { LOGGER.warn(recordBackupChoice.get().entry().getSize()); ObjectId commitId = recordBackupChoice.get().entry().getId(); @@ -99,11 +103,9 @@ public static Optional showRestoreBackupDialog(DialogService dialo } private static Optional showBackupResolverDialog(DialogService dialogService, - ExternalApplicationsPreferences externalApplicationsPreferences, - Path originalPath, - Path backupDir) { + Path originalPath) { return UiTaskExecutor.runInJavaFXThread( - () -> dialogService.showCustomDialogAndWait(new BackupResolverDialog(originalPath, backupDir, externalApplicationsPreferences))); + () -> dialogService.showCustomDialogAndWait(new BackupResolverDialog(originalPath))); } private static Optional showBackupChoiceDialog(DialogService dialogService, From aac491526667b667f942399d9375da91273ccd3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 5 Dec 2024 00:31:19 +0100 Subject: [PATCH 65/84] BackupResolverDialog.java doesn't use the old backup system anymore. --- src/main/java/org/jabref/gui/backup/BackupResolverDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java b/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java index aab94b961b3..750e94a9e4f 100644 --- a/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java +++ b/src/main/java/org/jabref/gui/backup/BackupResolverDialog.java @@ -20,7 +20,7 @@ public BackupResolverDialog(Path originalPath) { getDialogPane().setMinHeight(180); getDialogPane().getButtonTypes().setAll(RESTORE_FROM_BACKUP, REVIEW_BACKUP, IGNORE_BACKUP, COMPARE_OLDER_BACKUP); - String content = Localization.lang("A backup file for '%0' was found.", originalPath.getFileName().toString()) + "\n" + + String content = Localization.lang("A backup for '%0' was found.", originalPath.getFileName().toString()) + "\n" + Localization.lang("This could indicate that JabRef did not shut down cleanly last time the file was used.") + "\n\n" + Localization.lang("Do you want to recover the library from the backup file?"); setContentText(content); From a292db35e95af7b788b2cf10bae90107d97bd320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 5 Dec 2024 01:39:46 +0100 Subject: [PATCH 66/84] change in CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38e1c8b26c9..581b1a92a52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added +- Implemented BackupManagerGit to handle automatic backups of .bib files using Git, ensuring centralized, version-controlled backup management. [#2961] (https://github.com/JabRef/jabref/issues/2961#event-14646591632) +- Added automatic copying of .bib files to a jabref/backups directory every 19 seconds with comprehensive commit history. [#2961] +- Added support for unique file naming using UUIDs to prevent overwriting files with identical names in the backup directory. [#2961] +- Introduced UI functionality (only during opening) for saving, restoring, reviewing and discarding changes with accurate commit details retrieval. [#2961] +- Enabled a "Restore" button to recover specific backup versions. [#2961] - We added a "view as BibTeX" option before importing an entry from the citation relation tab. [#11826](https://github.com/JabRef/jabref/issues/11826) - We added support finding LaTeX-encoded special characters based on plain Unicode and vice versa. [#11542](https://github.com/JabRef/jabref/pull/11542) - When a search hits a file, the file icon of that entry is changed accordingly. [#11542](https://github.com/JabRef/jabref/pull/11542) @@ -47,6 +52,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Changed +- Refactored backup-related components to integrate with the new BackupManagerGit, replacing the older BackupManager logic. [#2961] - A search in "any" fields ignores the [groups](https://docs.jabref.org/finding-sorting-and-cleaning-entries/groups). [#7996](https://github.com/JabRef/jabref/issues/7996) - When a communication error with an [online service](https://docs.jabref.org/collect/import-using-online-bibliographic-database) occurs, JabRef displays the HTTP error. [#11223](https://github.com/JabRef/jabref/issues/11223) - The Pubmed/Medline Plain importer now imports the PMID field as well [#11488](https://github.com/JabRef/jabref/issues/11488) From 7ae97d1317d630167720a8dc0341ecd17bcf469c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 5 Dec 2024 11:54:56 +0100 Subject: [PATCH 67/84] Updated submodule csl-locales to latest commit --- src/main/resources/csl-locales | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/csl-locales b/src/main/resources/csl-locales index 8bc2af16f51..4753e3a9aca 160000 --- a/src/main/resources/csl-locales +++ b/src/main/resources/csl-locales @@ -1 +1 @@ -Subproject commit 8bc2af16f5180a8e4fb591c2be916650f75bb8f6 +Subproject commit 4753e3a9aca4b806ac0e3036ed727d47bf8f678e From 70b123bdf51a7c3f3bcdbe7754627ea6ff764b96 Mon Sep 17 00:00:00 2001 From: Khaoula AROUISSI <66998680+khola22@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:14:52 +0100 Subject: [PATCH 68/84] Update CHANGELOG.md Co-authored-by: Subhramit Basu Bhowmick --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cc22ecc56..87131f39990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added -- Implemented BackupManagerGit to handle automatic backups of .bib files using Git, ensuring centralized, version-controlled backup management. [#2961](https://github.com/JabRef/jabref/issues/2961) +- Implemented version-control based backup management of .bib files using Git. [#2961](https://github.com/JabRef/jabref/issues/2961) - Added automatic copying of .bib files to a jabref/backups directory every 19 seconds with comprehensive commit history. [#2961](https://github.com/JabRef/jabref/issues/2961) - Added support for unique file naming using UUIDs to prevent overwriting files with identical names in the backup directory. [#2961](https://github.com/JabRef/jabref/issues/2961) - Introduced UI functionality (only during opening) for saving, restoring, reviewing and discarding changes with accurate commit details retrieval. [#2961](https://github.com/JabRef/jabref/issues/2961) From 69ad0f335c2f932c981d7127dc49966ddaa2bb4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 5 Dec 2024 16:22:22 +0100 Subject: [PATCH 69/84] CHANGELOG.md changes --- CHANGELOG.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87131f39990..b017558a542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added -- Implemented version-control based backup management of .bib files using Git. [#2961](https://github.com/JabRef/jabref/issues/2961) -- Added automatic copying of .bib files to a jabref/backups directory every 19 seconds with comprehensive commit history. [#2961](https://github.com/JabRef/jabref/issues/2961) -- Added support for unique file naming using UUIDs to prevent overwriting files with identical names in the backup directory. [#2961](https://github.com/JabRef/jabref/issues/2961) -- Introduced UI functionality (only during opening) for saving, restoring, reviewing and discarding changes with accurate commit details retrieval. [#2961](https://github.com/JabRef/jabref/issues/2961) -- Enabled a "Restore" button to recover specific backup versions. [#2961](https://github.com/JabRef/jabref/issues/2961) +- Enhanced backup and restore functionality. [#2961](https://github.com/JabRef/jabref/issues/2961) - We added a Markdown export layout. [#12220](https://github.com/JabRef/jabref/pull/12220) - We added a "view as BibTeX" option before importing an entry from the citation relation tab. [#11826](https://github.com/JabRef/jabref/issues/11826) - We added support finding LaTeX-encoded special characters based on plain Unicode and vice versa. [#11542](https://github.com/JabRef/jabref/pull/11542) @@ -57,7 +53,6 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Changed -- Refactored backup-related components to integrate with the new BackupManagerGit, replacing the older BackupManager logic. [#2961](https://github.com/JabRef/jabref/issues/2961) - A search in "any" fields ignores the [groups](https://docs.jabref.org/finding-sorting-and-cleaning-entries/groups). [#7996](https://github.com/JabRef/jabref/issues/7996) - When a communication error with an [online service](https://docs.jabref.org/collect/import-using-online-bibliographic-database) occurs, JabRef displays the HTTP error. [#11223](https://github.com/JabRef/jabref/issues/11223) - The Pubmed/Medline Plain importer now imports the PMID field as well [#11488](https://github.com/JabRef/jabref/issues/11488) From a577ad9b34b7d5983e34afd2add204cec0c4c981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 5 Dec 2024 16:30:50 +0100 Subject: [PATCH 70/84] Resolve comment on LibraryTab.java and BackupManagerGit.java --- src/main/java/org/jabref/gui/LibraryTab.java | 2 -- .../autosaveandbackup/BackupManagerGit.java | 34 ++++++++----------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 6ebbceda379..99a2986be6c 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -1083,8 +1083,6 @@ public static LibraryTab createLibraryTab(BackgroundTask dataLoadi try { newTab.onDatabaseLoadingSucceed(result); } catch (Exception e) { - // We need to handle the exception. - // Handle the exception, e.g., log it or show an error dialog LOGGER.error("An error occurred while loading the database", e); } }) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 9dbcc0d60b4..aa20123883a 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -55,7 +55,7 @@ public class BackupManagerGit { private static Git git; private final BibDatabaseContext bibDatabaseContext; - private final CliPreferences preferences; + private final Path backupDirectory; private final ScheduledThreadPoolExecutor executor; private final CoarseChangeFilter changeFilter; private final BibEntryTypesManager entryTypesManager; @@ -63,7 +63,7 @@ public class BackupManagerGit { private boolean needsBackup = false; - BackupManagerGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { + BackupManagerGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, Path backupDir) throws IOException, GitAPIException { Path dbFile = bibDatabaseContext.getDatabasePath().orElseThrow(() -> new IllegalArgumentException("Database path is not provided.")); if (!Files.exists(dbFile)) { LOGGER.error("Database file does not exist: {}", dbFile); @@ -73,24 +73,24 @@ public class BackupManagerGit { this.bibDatabaseContext = bibDatabaseContext; LOGGER.info("Backup manager initialized for file: {}", bibDatabaseContext.getDatabasePath().orElseThrow()); this.entryTypesManager = entryTypesManager; - this.preferences = preferences; + this.backupDirectory = backupDir; this.executor = new ScheduledThreadPoolExecutor(2); this.libraryTab = libraryTab; changeFilter = new CoarseChangeFilter(bibDatabaseContext); changeFilter.registerListener(this); - LOGGER.info("Backup directory path: {}", preferences.getFilePreferences().getBackupDirectory()); + LOGGER.info("Backup directory path: {}", backupDirectory); - ensureGitInitialized(preferences.getFilePreferences().getBackupDirectory()); + ensureGitInitialized(backupDirectory); - File backupDirFile = preferences.getFilePreferences().getBackupDirectory().toFile(); + File backupDirFile = backupDirectory.toFile(); if (!backupDirFile.exists() && !backupDirFile.mkdirs()) { - LOGGER.error("Failed to create backup directory: {}", preferences.getFilePreferences().getBackupDirectory()); - throw new IOException("Unable to create backup directory: " + preferences.getFilePreferences().getBackupDirectory()); + LOGGER.error("Failed to create backup directory: {}", backupDirectory); + throw new IOException("Unable to create backup directory: " + backupDirectory); } - copyDatabaseFileToBackupDir(dbFile, preferences.getFilePreferences().getBackupDirectory()); + copyDatabaseFileToBackupDir(dbFile, backupDirectory); } /** @@ -232,7 +232,8 @@ private static void copyDatabaseFileToBackupDir(Path dbFile, Path backupDirPath) public static BackupManagerGit start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException { LOGGER.info("In methode Start"); - BackupManagerGit backupManagerGit = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, preferences); + Path backupDir = preferences.getFilePreferences().getBackupDirectory(); + BackupManagerGit backupManagerGit = new BackupManagerGit(libraryTab, bibDatabaseContext, entryTypesManager, backupDir); backupManagerGit.startBackupTask(preferences.getFilePreferences().getBackupDirectory(), bibDatabaseContext); runningInstances.add(backupManagerGit); return backupManagerGit; @@ -354,10 +355,7 @@ public static void restoreBackup(Path dbFile, Path backupDir, ObjectId objectId) // Rewrite the original file at dbFile path rewriteFile(dbFile, fileContent); LOGGER.info("Restored content to: {}", dbFile); - } catch ( - IOException | - IllegalArgumentException | - GitAPIException e) { + } catch (IOException | IllegalArgumentException | GitAPIException e) { LOGGER.error("Error while restoring the backup: {}", e.getMessage(), e); } } @@ -435,8 +433,7 @@ public static Path getBackupFilePath(Path dbFile, Path backupDir) { String uuid = getOrGenerateFileUuid(dbFile); String relativeFileName = baseName.replace(".bib", "") + "_" + uuid + ".bib"; return backupDir.resolve(relativeFileName); - } catch ( - IOException e) { + } catch (IOException e) { throw new RuntimeException(e); } } @@ -466,10 +463,7 @@ public static void writeBackupFileToCommit(Path dbFile, Path backupDir, ObjectId // Rewrite the original file at backupFilePath path rewriteFile(backupFilePath, fileContent); LOGGER.info("Restored content to: {}", dbFile); - } catch ( - IOException | - IllegalArgumentException | - GitAPIException e) { + } catch (IOException | IllegalArgumentException | GitAPIException e) { LOGGER.error("Error while restoring the backup: {}", e.getMessage(), e); } } From e55a831377cb666d334d840eeec21f89c2155acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 5 Dec 2024 16:36:42 +0100 Subject: [PATCH 71/84] Resolve comment on LibraryTab.java and BackupManagerGit.java --- .../gui/autosaveandbackup/BackupManagerGit.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index aa20123883a..b45276eb442 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -48,6 +48,7 @@ public class BackupManagerGit { static Set runningInstances = new HashSet(); + private static final String LINE_BREAK = System.lineSeparator(); private static final Logger LOGGER = LoggerFactory.getLogger(BackupManagerGit.class); private static final int DELAY_BETWEEN_BACKUP_ATTEMPTS_IN_SECONDS = 19; @@ -162,14 +163,14 @@ private static String normalizeBibTeX(String input) { return ""; } - // Diviser les lignes et traiter chaque ligne + // Split lines and process each line Stream lines = input.lines(); - // Normalisation des lignes + // Normalize lines String normalized = lines - .map(String::trim) // Supprimer les espaces en début et fin de ligne - .filter(line -> !line.isBlank()) // Supprimer les lignes vides - .collect(Collectors.joining("\n")); // Réassembler avec des sauts de ligne + .map(String::trim) // Remove leading and trailing spaces + .filter(line -> !line.isBlank()) // Remove blank lines + .collect(Collectors.joining(LINE_BREAK)); // Reassemble with line breaks return normalized; } From cf23e682ee5cf59ee7c253bc83ad09e7f3d4531f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 5 Dec 2024 16:43:03 +0100 Subject: [PATCH 72/84] Resolve comment on BackupUIManager.java --- src/main/java/org/jabref/gui/dialogs/BackupUIManager.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 652e152470b..58a124e611b 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -75,7 +75,7 @@ public static Optional showRestoreBackupDialog(DialogService dialo return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager, commitId, commitId); } else if (action == BackupResolverDialog.COMPARE_OLDER_BACKUP) { - var recordBackupChoice = showBackupChoiceDialog(dialogService, originalPath, preferences, backups); + var recordBackupChoice = showBackupChoiceDialog(dialogService, preferences, backups); if (recordBackupChoice.isEmpty()) { return Optional.empty(); @@ -94,8 +94,7 @@ public static Optional showRestoreBackupDialog(DialogService dialo return showReviewBackupDialog(dialogService, originalPath, preferences, fileUpdateMonitor, undoManager, stateManager, commitId, latestCommitId); } } - } catch ( - GitAPIException | IOException e) { + } catch (GitAPIException | IOException e) { throw new RuntimeException(e); } return Optional.empty(); @@ -109,7 +108,6 @@ private static Optional showBackupResolverDialog(DialogService dialo } private static Optional showBackupChoiceDialog(DialogService dialogService, - Path originalPath, GuiPreferences preferences, List backups) { return UiTaskExecutor.runInJavaFXThread( From 61a1987d0170f055c13df6b03488892010f34126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 5 Dec 2024 16:50:19 +0100 Subject: [PATCH 73/84] checkstyle --- src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java index 727fc394eff..f55ec43bd0d 100644 --- a/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java +++ b/src/main/java/org/jabref/gui/exporter/SaveDatabaseAction.java @@ -215,9 +215,7 @@ private boolean save(BibDatabaseContext bibDatabaseContext, SaveDatabaseMode mod return savePath.filter(path -> { try { return saveAs(path, mode); - } catch ( - GitAPIException | - IOException e) { + } catch (GitAPIException | IOException e) { LOGGER.error("A problem occurred when trying to save the file %s".formatted(path), e); throw new RuntimeException(e); } From b515e5176aeef8a0d6446cdecaac443e06c84d4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 5 Dec 2024 18:53:32 +0100 Subject: [PATCH 74/84] Working Tests --- .../autosaveandbackup/BackupManagerGit.java | 16 ++++--- .../BackupManagerGitTest.java | 42 ++++++++++++++----- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index b45276eb442..884eb953724 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -122,7 +122,7 @@ private static String appendUuidToFileName(String originalFileName, String uuid) * @return The UUID associated with the file. * @throws IOException If an error occurs while accessing or creating the UUID. */ - private static String getOrGenerateFileUuid(Path filePath) throws IOException { + protected static String getOrGenerateFileUuid(Path filePath) throws IOException { // Define a hidden metadata file to store the UUID Path metadataFile = filePath.resolveSibling("." + filePath.getFileName().toString() + ".uuid"); @@ -205,16 +205,12 @@ static void ensureGitInitialized(Path backupDir) throws IOException, GitAPIExcep } // Helper method to copy the database file to the backup directory - private static void copyDatabaseFileToBackupDir(Path dbFile, Path backupDirPath) throws IOException { + protected static void copyDatabaseFileToBackupDir(Path dbFile, Path backupDirPath) throws IOException { String fileUuid = getOrGenerateFileUuid(dbFile); String uniqueFileName = appendUuidToFileName(dbFile.getFileName().toString(), fileUuid); Path backupFilePath = backupDirPath.resolve(uniqueFileName); - if (!Files.exists(backupFilePath) || Files.mismatch(dbFile, backupFilePath) != -1) { - Files.copy(dbFile, backupFilePath, StandardCopyOption.REPLACE_EXISTING); - LOGGER.info("Database file uniquely copied to backup directory: {}", backupFilePath); - } else { - LOGGER.info("No changes detected; skipping backup for file: {}", uniqueFileName); - } + Files.copy(dbFile, backupFilePath, StandardCopyOption.REPLACE_EXISTING); + LOGGER.info("Database file uniquely copied to backup directory: {}", backupFilePath); } // A method @@ -641,7 +637,7 @@ public static void performBackupNoCommits(Path dbFile, Path backupDir) throws IO copyDatabaseFileToBackupDir(dbFile, backupDir); // Ensure the Git repository exists - LOGGER.info("Checking if backup differs for file: {}", dbFile); + LOGGER.info("Ensuring the .git is initialized"); ensureGitInitialized(backupDir); // Open the Git repository located in the backup directory @@ -653,9 +649,11 @@ public static void performBackupNoCommits(Path dbFile, Path backupDir) throws IO String repoFileName = baseName.replace(".bib", "") + "_" + uuid + ".bib"; // Stage the file for commit + LOGGER.info("Staging the file for commit"); git.add().addFilepattern(repoFileName).call(); // Commit the staged changes + LOGGER.info("Committing the file"); RevCommit commit = git.commit() .setMessage("Backup at " + Instant.now().toString()) .call(); diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index 9f5792634a4..54ed90cfec8 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; @@ -100,8 +101,8 @@ void tearDown() throws IOException { @Test void testInitializationCreatesBackupDirectory() throws IOException, GitAPIException { // Create BackupManagerGit - BackupManagerGit manager1 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, mockPreferences); - BackupManagerGit manager2 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext2, mockEntryTypesManager, mockPreferences); + BackupManagerGit manager1 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, tempDir); + BackupManagerGit manager2 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext2, mockEntryTypesManager, tempDir); // Check if the backup directory exists assertTrue(Files.exists(tempDir), " directory should be created wich contains .git and single copies og .bib"); assertTrue(Files.exists(tempDir1), "Backup directory should be created during initialization."); @@ -120,15 +121,21 @@ void testGitInitialization() throws IOException, GitAPIException { @Test void testBackupFileCopiedToDirectory() throws IOException, GitAPIException { + BackupManagerGit manager1 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, tempDir); + BackupManagerGit manager2 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext2, mockEntryTypesManager, tempDir); - BackupManagerGit manager1 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, mockPreferences); - BackupManagerGit manager2 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext2, mockEntryTypesManager, mockPreferences); + // Generate the expected backup file names + String uuid1 = BackupManagerGit.getOrGenerateFileUuid(mockDatabasePath1); + String uuid2 = BackupManagerGit.getOrGenerateFileUuid(mockDatabasePath2); + String backupFileName1 = mockDatabasePath1.getFileName().toString().replace(".bib", "") + "_" + uuid1 + ".bib"; + String backupFileName2 = mockDatabasePath2.getFileName().toString().replace(".bib", "") + "_" + uuid2 + ".bib"; // Verify the file is copied to the backup directory - Path backupFile1 = tempDir.resolve(this.mockDatabasePath1.getFileName()); - Path backupFile2 = tempDir.resolve(this.mockDatabasePath2.getFileName()); + Path backupFile1 = tempDir.resolve(backupFileName1); + Path backupFile2 = tempDir.resolve(backupFileName2); assertTrue(Files.exists(backupFile1), "Database file should be copied to the backup directory."); - } + assertTrue(Files.exists(backupFile2), "Database file should be copied to the backup directory."); + } @Test public void testStart() throws IOException, GitAPIException { @@ -143,15 +150,28 @@ void testPerformBackupCommitsChanges() throws IOException, GitAPIException { // Create a test file Path dbFile1 = tempDir.resolve("test1.bib"); - Files.writeString(dbFile1, "Initial content of test 1"); // Create BackupManagerGit and perform backup - BackupManagerGit manager = new BackupManagerGit(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, mockPreferences); - manager.performBackup(tempDir); + BackupManagerGit manager = new BackupManagerGit(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, tempDir); + Files.writeString(dbFile1, "Initial content of test 1"); + + BackupManagerGit.copyDatabaseFileToBackupDir(dbFile1, tempDir); + + // Generate the expected backup file name + String uuid1 = BackupManagerGit.getOrGenerateFileUuid(dbFile1); + String backupFileName1 = dbFile1.getFileName().toString().replace(".bib", "") + "_" + uuid1 + ".bib"; + Path backupFile1 = tempDir.resolve(backupFileName1); + + // Verify the file is copied to the backup directory + assertTrue(Files.exists(backupFile1), "Database file should be copied to the backup directory."); + + manager.performBackup(dbFile1, tempDir); // Verify that changes are committed try (Git git = Git.open(tempDir.toFile())) { - assertTrue(git.status().call().isClean(), "Git repository should have no uncommitted changes after backup."); + boolean hasUncommittedChanges = git.status().call().getUncommittedChanges().stream() + .anyMatch(file -> file.endsWith(".bib")); + assertFalse(hasUncommittedChanges, "Git repository should have no uncommitted .bib file changes after backup."); } } } From 8f06c8091aa3b7714a234dae2f2d5c510167862a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 5 Dec 2024 19:00:12 +0100 Subject: [PATCH 75/84] Add missing submodule configuration --- .gitmodules | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitmodules b/.gitmodules index 10944a3d5a0..0c5a1380408 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ url = https://github.com/citation-style-language/locales.git ignore = all shallow = true +[submodule "jabref"] + path = jabref + url = https://github.com/JabRef/jabref.git From d3b6dd3e9f49680632ba10a1a398bd99f865d783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 5 Dec 2024 19:12:50 +0100 Subject: [PATCH 76/84] Apply OpenRewrite recipes --- .../gui/autosaveandbackup/BackupManagerGit.java | 17 ++++++++--------- src/main/resources/csl-locales | 2 +- src/main/resources/csl-styles | 2 +- .../autosaveandbackup/BackupManagerGitTest.java | 15 ++++++++------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 884eb953724..115293072aa 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -46,7 +46,7 @@ public class BackupManagerGit { - static Set runningInstances = new HashSet(); + static Set runningInstances = new HashSet<>(); private static final String LINE_BREAK = System.lineSeparator(); private static final Logger LOGGER = LoggerFactory.getLogger(BackupManagerGit.class); @@ -292,14 +292,14 @@ protected void performBackup(Path dbfile, Path backupDir) throws IOException, Gi boolean needsCommit = backupGitDiffers(dbfile, backupDir); if (!needsBackup && !needsCommit) { - LOGGER.info("No changes detected, beacuse needsBackup is :" + needsBackup + " and needsCommit is :" + needsCommit); + LOGGER.info("No changes detected, beacuse needsBackup is :{} and needsCommit is :{}", needsBackup, needsCommit); return; } if (needsBackup) { - LOGGER.info("Backup needed, because needsBackup is :" + needsBackup); + LOGGER.info("Backup needed, because needsBackup is :{}", needsBackup); } else { - LOGGER.info("Backup needed, because needsCommit is :" + needsCommit); + LOGGER.info("Backup needed, because needsCommit is :{}", needsCommit); } // Stage the file for commit @@ -310,8 +310,7 @@ protected void performBackup(Path dbfile, Path backupDir) throws IOException, Gi RevCommit commit = git.commit() .setMessage("Backup at " + Instant.now().toString()) .call(); - LOGGER.info("Backup committed in :" + backupDir + " with commit ID: " + commit.getName() - + " for the file : {}", bibDatabaseContext.getDatabasePath().orElseThrow()); + LOGGER.info("Backup committed in :{} with commit ID: {} for the file : {}", backupDir, commit.getName(), bibDatabaseContext.getDatabasePath().orElseThrow()); } public synchronized void listen(BibDatabaseContextChangedEvent event) { @@ -604,9 +603,9 @@ public static List retrieveCommitDetails(List commits, P } // Convert size to KB or MB - sizeFormatted = (fileSize > 1024 * 1024) - ? String.format("%.2f MB", fileSize / (1024.0 * 1024.0)) - : String.format("%.2f KB", fileSize / 1024.0); + sizeFormatted = fileSize > 1024 * 1024 + ? "%.2f MB".formatted(fileSize / (1024.0 * 1024.0)) + : "%.2f KB".formatted(fileSize / 1024.0); } // Skip this commit if the file was not found diff --git a/src/main/resources/csl-locales b/src/main/resources/csl-locales index 96d704de2fc..4753e3a9aca 160000 --- a/src/main/resources/csl-locales +++ b/src/main/resources/csl-locales @@ -1 +1 @@ -Subproject commit 96d704de2fc7b930ae4a0ec4686a7143bb4a0d33 +Subproject commit 4753e3a9aca4b806ac0e3036ed727d47bf8f678e diff --git a/src/main/resources/csl-styles b/src/main/resources/csl-styles index 6b7b611908b..49af15c4f5b 160000 --- a/src/main/resources/csl-styles +++ b/src/main/resources/csl-styles @@ -1 +1 @@ -Subproject commit 6b7b611908b20c91f34110d1c9489fb3278e0ef5 +Subproject commit 49af15c4f5bca025b6b18ca48c447016586f01e7 diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index 54ed90cfec8..dbd9022dd67 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; import org.jabref.gui.LibraryTab; import org.jabref.logic.FilePreferences; @@ -66,8 +67,8 @@ public void setUp(@TempDir Path tempDir) throws IOException, GitAPIException { Files.writeString(mockDatabasePath1, "Mock content for testing 1"); // Create the file Files.writeString(mockDatabasePath2, "Mock content for testing 2"); // Create the file - when(mockDatabaseContext1.getDatabasePath()).thenReturn(java.util.Optional.of(mockDatabasePath1)); - when(mockDatabaseContext2.getDatabasePath()).thenReturn(java.util.Optional.of(mockDatabasePath2)); + when(mockDatabaseContext1.getDatabasePath()).thenReturn(Optional.of(mockDatabasePath1)); + when(mockDatabaseContext2.getDatabasePath()).thenReturn(Optional.of(mockDatabasePath2)); when(filePreferences.getBackupDirectory()).thenReturn(tempDir); @@ -99,7 +100,7 @@ void tearDown() throws IOException { } @Test - void testInitializationCreatesBackupDirectory() throws IOException, GitAPIException { + void initializationCreatesBackupDirectory() throws IOException, GitAPIException { // Create BackupManagerGit BackupManagerGit manager1 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, tempDir); BackupManagerGit manager2 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext2, mockEntryTypesManager, tempDir); @@ -110,7 +111,7 @@ void testInitializationCreatesBackupDirectory() throws IOException, GitAPIExcept } @Test - void testGitInitialization() throws IOException, GitAPIException { + void gitInitialization() throws IOException, GitAPIException { // Initialize Git BackupManagerGit.ensureGitInitialized(tempDir); @@ -120,7 +121,7 @@ void testGitInitialization() throws IOException, GitAPIException { } @Test - void testBackupFileCopiedToDirectory() throws IOException, GitAPIException { + void backupFileCopiedToDirectory() throws IOException, GitAPIException { BackupManagerGit manager1 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, tempDir); BackupManagerGit manager2 = new BackupManagerGit(mockLibraryTab, mockDatabaseContext2, mockEntryTypesManager, tempDir); @@ -138,13 +139,13 @@ void testBackupFileCopiedToDirectory() throws IOException, GitAPIException { } @Test - public void testStart() throws IOException, GitAPIException { + public void start() throws IOException, GitAPIException { BackupManagerGit startedManager = BackupManagerGit.start(mockLibraryTab, mockDatabaseContext1, mockEntryTypesManager, mockPreferences); assertNotNull(startedManager); } @Test - void testPerformBackupCommitsChanges() throws IOException, GitAPIException { + void performBackupCommitsChanges() throws IOException, GitAPIException { // Initialize Git BackupManagerGit.ensureGitInitialized(tempDir); From 2a05da51c6ed9fc1dcdea44c0311583346db2584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Thu, 5 Dec 2024 22:53:59 +0100 Subject: [PATCH 77/84] Correct Unit Tests --- src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java | 8 ++++---- .../jabref/logic/importer/fileformat/CffImporterTest.java | 8 ++++---- .../logic/util/io/CitationKeyBasedFileFinderTest.java | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java index 23c0f7e0eed..c8c57f250db 100644 --- a/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java +++ b/src/test/java/org/jabref/logic/bst/BstVMVisitorTest.java @@ -203,8 +203,8 @@ void visitIdentifier() { FUNCTION { test } { #1 'local.variable := #2 'variable := - "COMPARE_OLDER_BACKUP" 'local.label := - "COMPARE_OLDER_BACKUP-GLOBAL" 'label := + "TEST" 'local.label := + "TEST-GLOBAL" 'label := local.label local.variable label variable } @@ -215,9 +215,9 @@ void visitIdentifier() { vm.render(testEntries); assertEquals(2, vm.getStack().pop()); - assertEquals("COMPARE_OLDER_BACKUP-GLOBAL", vm.getStack().pop()); + assertEquals("TEST-GLOBAL", vm.getStack().pop()); assertEquals(1, vm.getStack().pop()); - assertEquals("COMPARE_OLDER_BACKUP", vm.getStack().pop()); + assertEquals("TEST", vm.getStack().pop()); assertEquals(0, vm.getStack().size()); } diff --git a/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java b/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java index bca58a77401..195d7bd713b 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/CffImporterTest.java @@ -175,7 +175,7 @@ void importEntriesPreferredCitation() throws IOException, URISyntaxException { BibEntry expectedPreferred = new BibEntry(StandardEntryType.InProceedings) .withCitationKey(citeKey) .withField(StandardField.AUTHOR, "Jonathan von Duke and Jim Kingston, Jr.") - .withField(StandardField.DOI, "10.0001/COMPARE_OLDER_BACKUP") + .withField(StandardField.DOI, "10.0001/TEST") .withField(StandardField.URL, "www.github.com"); assertEquals(mainEntry, expectedMain); @@ -198,13 +198,13 @@ void importEntriesReferences() throws IOException, URISyntaxException { .withCitationKey(citeKey1) .withField(StandardField.AUTHOR, "Jonathan von Duke and Jim Kingston, Jr.") .withField(StandardField.YEAR, "2007") - .withField(StandardField.DOI, "10.0001/COMPARE_OLDER_BACKUP") + .withField(StandardField.DOI, "10.0001/TEST") .withField(StandardField.URL, "www.example.com"); BibEntry expectedReference2 = new BibEntry(StandardEntryType.Manual) .withCitationKey(citeKey2) .withField(StandardField.AUTHOR, "Arthur Clark, Jr. and Luca von Diamond") - .withField(StandardField.DOI, "10.0002/COMPARE_OLDER_BACKUP") + .withField(StandardField.DOI, "10.0002/TEST") .withField(StandardField.URL, "www.facebook.com"); assertEquals(mainEntry, expectedMain); @@ -218,7 +218,7 @@ public BibEntry getPopulatedEntry() { .withField(StandardField.TITLE, "Test") .withField(StandardField.URL, "www.google.com") .withField(BiblatexSoftwareField.REPOSITORY, "www.github.com") - .withField(StandardField.DOI, "10.0000/COMPARE_OLDER_BACKUP") + .withField(StandardField.DOI, "10.0000/TEST") .withField(StandardField.DATE, "2000-07-02") .withField(StandardField.COMMENT, "Test entry.") .withField(StandardField.ABSTRACT, "Test abstract.") diff --git a/src/test/java/org/jabref/logic/util/io/CitationKeyBasedFileFinderTest.java b/src/test/java/org/jabref/logic/util/io/CitationKeyBasedFileFinderTest.java index 7133acaa60e..3f29211f4e0 100644 --- a/src/test/java/org/jabref/logic/util/io/CitationKeyBasedFileFinderTest.java +++ b/src/test/java/org/jabref/logic/util/io/CitationKeyBasedFileFinderTest.java @@ -47,8 +47,8 @@ void setUp(@TempDir Path temporaryFolder) throws IOException { Files.createFile(dir2003.resolve("Paper by HipKro03.pdf")); Path dirTest = Files.createDirectory(rootDir.resolve("test")); - Files.createFile(dirTest.resolve(".COMPARE_OLDER_BACKUP")); - Files.createFile(dirTest.resolve("COMPARE_OLDER_BACKUP[")); + Files.createFile(dirTest.resolve(".TEST")); + Files.createFile(dirTest.resolve("TEST[")); Files.createFile(dirTest.resolve("TE.ST")); Files.createFile(dirTest.resolve("foo.dat")); From dbe6f2b89a0fbafbc4f7f58ee8c232837b141ba6 Mon Sep 17 00:00:00 2001 From: Gillan0 Date: Fri, 6 Dec 2024 19:21:36 +0100 Subject: [PATCH 78/84] Added new UI text to JabRef_en.properties --- src/main/resources/l10n/JabRef_en.properties | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 32bc777fcc6..f552dad398b 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2689,9 +2689,17 @@ Keep\ existing\ entry=Keep existing entry No\ entries\ corresponding\ to\ given\ query=No entries corresponding to given query Review\ backup=Review\ backup -A\ backup\ file\ for\ '%0'\ was\ found\ at\ [%1]=A backup file for '%0' was found at [%1] +A\ backup\ for\ '%0'\ was\ found.=A backup for '%0' was found. Do\ you\ want\ to\ recover\ the\ library\ from\ the\ backup\ file?=Do you want to recover the library from the backup file? This\ could\ indicate\ that\ JabRef\ did\ not\ shut\ down\ cleanly\ last\ time\ the\ file\ was\ used.=This could indicate that JabRef did not shut down cleanly last time the file was used. +Choose\ backup\ file=Choose backup file +Date\ of\ Backup=Date of Backup +Do\ you\ want\ to\ recover\ the\ library\ from\ a\ backup\ file?=Do you want to recover the library from a backup file? +It\ looks\ like\ JabRef\ did\ not\ shut\ down\ cleanly\ last\ time\ the\ file\ was\ used.=It looks like JabRef did not shut down cleanly last time the file was used. +Number\ of\ Entries=Number of Entries +Restore\ from\ latest\ backup=Restore from latest backup +Review\ latest\ backup=Review latest backup +Size\ of\ Backup=Size of Backup Use\ the\ field\ FJournal\ to\ store\ the\ full\ journal\ name\ for\ (un)abbreviations\ in\ the\ entry=Use the field FJournal to store the full journal name for (un)abbreviations in the entry From e0653994d0853c70b52677001b2d4b9a0f87985e Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Sat, 7 Dec 2024 21:14:59 +0100 Subject: [PATCH 79/84] Resolve comment on BackupManagerGitTest.java and --- buildres/abbrv.jabref.org | 2 +- src/main/java/org/jabref/gui/LibraryTab.java | 2 +- .../gui/autosaveandbackup/BackupManagerGit.java | 16 ++++++++++------ src/main/resources/csl-locales | 2 +- src/main/resources/csl-styles | 2 +- .../autosaveandbackup/BackupManagerGitTest.java | 14 +------------- 6 files changed, 15 insertions(+), 23 deletions(-) diff --git a/buildres/abbrv.jabref.org b/buildres/abbrv.jabref.org index 0fdf99147a8..d87037495de 160000 --- a/buildres/abbrv.jabref.org +++ b/buildres/abbrv.jabref.org @@ -1 +1 @@ -Subproject commit 0fdf99147a8a5fc8ae7ccd79ad4e0029e736e4a3 +Subproject commit d87037495de7213b896dbb6a20170387de170709 diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 99a2986be6c..6ab7334c880 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -374,7 +374,7 @@ public void installAutosaveManagerAndBackupManager() throws GitAPIException, IOE autosaveManager.registerListener(new AutosaveUiManager(this, dialogService, preferences, entryTypesManager)); } if (isDatabaseReadyForBackup(bibDatabaseContext) && preferences.getFilePreferences().shouldCreateBackup()) { - BackupManagerGit.start(this, bibDatabaseContext, Injector.instantiateModelOrService(BibEntryTypesManager.class), preferences); + BackupManagerGit.start(this, bibDatabaseContext, entryTypesManager, preferences); } } diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java index 115293072aa..d86a8425559 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManagerGit.java @@ -9,11 +9,14 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.Locale; +import java.util.ResourceBundle; import java.util.Set; import java.util.UUID; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -148,7 +151,10 @@ protected static String getOrGenerateFileUuid(Path filePath) throws IOException public static void rewriteFile(Path dbFile, String content) throws IOException { // Ensure the file exists before rewriting if (!Files.exists(dbFile)) { - throw new FileNotFoundException("The file at path " + dbFile + " does not exist."); + Locale currentLocale = Locale.getDefault(); + ResourceBundle messages = ResourceBundle.getBundle("messages", currentLocale); + String errorMessage = MessageFormat.format(messages.getString("file.not.found"), dbFile.toString()); + throw new FileNotFoundException(errorMessage); } // Write the new content to the file (overwrite mode) @@ -266,7 +272,7 @@ void startBackupTask(Path backupDir, BibDatabaseContext bibDatabaseContext) { () -> { try { Path dbFile = bibDatabaseContext.getDatabasePath().orElseThrow(() -> new IllegalArgumentException("Database path is not provided.")); - copyDatabaseFileToBackupDir(dbFile, backupDir); + // copyDatabaseFileToBackupDir(dbFile, backupDir); performBackup(dbFile, backupDir); } catch (IOException | GitAPIException e) { LOGGER.error("Error during backup", e); @@ -633,15 +639,13 @@ public static void performBackupNoCommits(Path dbFile, Path backupDir) throws IO LOGGER.info("No commits found in the repository. We need a first commit."); // Ensure the specific database file is copied to the backup directory - copyDatabaseFileToBackupDir(dbFile, backupDir); + // no need of copying again !! + // copyDatabaseFileToBackupDir(dbFile, backupDir); // Ensure the Git repository exists LOGGER.info("Ensuring the .git is initialized"); ensureGitInitialized(backupDir); - // Open the Git repository located in the backup directory - Repository repository = openGitRepository(backupDir); - // Get the file name of the database file String baseName = dbFile.getFileName().toString(); String uuid = getOrGenerateFileUuid(dbFile); // Generate or retrieve the UUID for this file diff --git a/src/main/resources/csl-locales b/src/main/resources/csl-locales index 4753e3a9aca..8bc2af16f51 160000 --- a/src/main/resources/csl-locales +++ b/src/main/resources/csl-locales @@ -1 +1 @@ -Subproject commit 4753e3a9aca4b806ac0e3036ed727d47bf8f678e +Subproject commit 8bc2af16f5180a8e4fb591c2be916650f75bb8f6 diff --git a/src/main/resources/csl-styles b/src/main/resources/csl-styles index 49af15c4f5b..b413a778b81 160000 --- a/src/main/resources/csl-styles +++ b/src/main/resources/csl-styles @@ -1 +1 @@ -Subproject commit 49af15c4f5bca025b6b18ca48c447016586f01e7 +Subproject commit b413a778b8170cf5aebbb9aeffec62cfd068e19e diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index dbd9022dd67..d6d20e47c20 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -16,7 +16,6 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -43,6 +42,7 @@ public class BackupManagerGitTest { @BeforeEach public void setUp(@TempDir Path tempDir) throws IOException, GitAPIException { mockLibraryTab = mock(LibraryTab.class); + mockDatabaseContext1 = mock(BibDatabaseContext.class); mockDatabaseContext2 = mock(BibDatabaseContext.class); mockEntryTypesManager = mock(BibEntryTypesManager.class); @@ -87,18 +87,6 @@ public void setUp(@TempDir Path tempDir) throws IOException, GitAPIException { when(mockDatabaseContext2.getMetaData()).thenReturn(mockMetaData2); } - @AfterEach - void tearDown() throws IOException { - // Delete the temporary directory - Files.walk(tempDir) - .map(Path::toFile) - .forEach(file -> { - if (!file.delete()) { - file.deleteOnExit(); - } - }); - } - @Test void initializationCreatesBackupDirectory() throws IOException, GitAPIException { // Create BackupManagerGit From da79d5d71511ce4f4d55a813a0991dbf8c93f59a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Fri, 13 Dec 2024 22:48:46 +0100 Subject: [PATCH 80/84] Resolve comment about BackupUIManager.java --- src/main/java/org/jabref/gui/dialogs/BackupUIManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java index 58a124e611b..21760488bb8 100644 --- a/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java +++ b/src/main/java/org/jabref/gui/dialogs/BackupUIManager.java @@ -110,8 +110,9 @@ private static Optional showBackupResolverDialog(DialogService dialo private static Optional showBackupChoiceDialog(DialogService dialogService, GuiPreferences preferences, List backups) { + Path backupDirectory = preferences.getFilePreferences().getBackupDirectory(); return UiTaskExecutor.runInJavaFXThread( - () -> dialogService.showCustomDialogAndWait(new BackupChoiceDialog(preferences.getFilePreferences().getBackupDirectory(), backups))); + () -> dialogService.showCustomDialogAndWait(new BackupChoiceDialog(backupDirectory, backups))); } private static Optional showReviewBackupDialog( From a638da59bc6f693fdedf38ba524949213acc053c Mon Sep 17 00:00:00 2001 From: Nawal CHAHBOUNE Date: Sun, 15 Dec 2024 12:18:39 +0100 Subject: [PATCH 81/84] resolve initialization of bibEntries --- .../BackupManagerGitTest.java | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java index bbf8a5f687c..73415b31584 100644 --- a/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java +++ b/src/test/java/org/jabref/gui/autosaveandbackup/BackupManagerGitTest.java @@ -14,6 +14,8 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.metadata.MetaData; import org.eclipse.jgit.api.Git; @@ -45,12 +47,46 @@ public class BackupManagerGitTest { public void setUp(@TempDir Path tempDir) throws IOException, GitAPIException { // creating Entries + BibEntry entry1 = new BibEntry(StandardEntryType.Article) + .withField(StandardField.AUTHOR, "Garcia, Maria and Lee, David") + .withField(StandardField.JOURNAL, "International Review of Physics") + .withField(StandardField.NUMBER, "6") + .withField(StandardField.PAGES, "789--810") + .withField(StandardField.TITLE, "Quantum Entanglement in Superconductors") + .withField(StandardField.VOLUME, "28") + .withField(StandardField.ISSUE, "3") + .withField(StandardField.YEAR, "2021") + .withCitationKey("Garcia_2021"); + BibEntry entry2 = new BibEntry(StandardEntryType.Book) + .withField(StandardField.AUTHOR, "Smith, John") + .withField(StandardField.TITLE, "Advanced Quantum Mechanics") + .withField(StandardField.PUBLISHER, "Physics Press") + .withField(StandardField.YEAR, "2019") + .withField(StandardField.ISBN, "978-3-16-148410-0") + .withCitationKey("Smith_2019"); + + BibEntry entry3 = new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "Doe, Jane and Brown, Alice") + .withField(StandardField.TITLE, "Machine Learning in Quantum Computing") + .withField(StandardField.BOOKTITLE, "Proceedings of the International Conference on Quantum Computing") + .withField(StandardField.YEAR, "2020") + .withField(StandardField.PAGES, "123-130") + .withCitationKey("Doe_2020"); + + BibEntry entry4 = new BibEntry(StandardEntryType.Thesis) + .withField(StandardField.AUTHOR, "Johnson, Emily") + .withField(StandardField.TITLE, "Quantum Algorithms for Data Analysis") + .withField(StandardField.SCHOOL, "University of Quantum Studies") + .withField(StandardField.YEAR, "2022") + .withField(StandardField.TYPE, "PhD Thesis") + .withCitationKey("Johnson_2022"); + List entries1 = new ArrayList<>(); - entries1.add(new BibEntry("this")); - entries1.add(new BibEntry("is")); + entries1.add(entry1); + entries1.add(entry2); List entries2 = new ArrayList<>(); - entries2.add(new BibEntry("BackupManagerGitTest")); - entries2.add(new BibEntry("test")); + entries2.add(entry3); + entries2.add(entry4); // Initializing BibDatabases BibDatabase bibDatabase1 = new BibDatabase(entries1); From fcdd63b8ead576714566f8a78b5bea52584a25c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Tue, 7 Jan 2025 10:07:25 +0100 Subject: [PATCH 82/84] reverse .gitmodules --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 0c5a1380408..10944a3d5a0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,3 @@ url = https://github.com/citation-style-language/locales.git ignore = all shallow = true -[submodule "jabref"] - path = jabref - url = https://github.com/JabRef/jabref.git From 9be1cd2ab2bba73a124b0dac2acffdd24f33f1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Sat, 11 Jan 2025 12:50:04 +0100 Subject: [PATCH 83/84] cst-style --- src/main/resources/csl-styles | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/csl-styles b/src/main/resources/csl-styles index b413a778b81..49af15c4f5b 160000 --- a/src/main/resources/csl-styles +++ b/src/main/resources/csl-styles @@ -1 +1 @@ -Subproject commit b413a778b8170cf5aebbb9aeffec62cfd068e19e +Subproject commit 49af15c4f5bca025b6b18ca48c447016586f01e7 From 6c70924d711a49ca48144b0c94b3da522c009f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CKhaoula?= <“khaoula.arouissi@imt-atlantique.net”> Date: Sat, 11 Jan 2025 12:57:31 +0100 Subject: [PATCH 84/84] abbrv --- buildres/abbrv.jabref.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildres/abbrv.jabref.org b/buildres/abbrv.jabref.org index d87037495de..0fdf99147a8 160000 --- a/buildres/abbrv.jabref.org +++ b/buildres/abbrv.jabref.org @@ -1 +1 @@ -Subproject commit d87037495de7213b896dbb6a20170387de170709 +Subproject commit 0fdf99147a8a5fc8ae7ccd79ad4e0029e736e4a3