Skip to content

Commit

Permalink
first git implementation, not tested
Browse files Browse the repository at this point in the history
  • Loading branch information
“Khaoula committed Nov 6, 2024
1 parent 9ab37a2 commit cff3427
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 100 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ dependencies {
exclude group: 'org.jetbrains.kotlin'
}

implementation 'org.eclipse.jgit:org.eclipse.jgit:5.13.0.202109080827-r'

implementation platform('ai.djl:bom:0.30.0')
implementation 'ai.djl:api'
Expand Down
162 changes: 76 additions & 86 deletions src/main/java/org/jabref/gui/autosaveandbackup/BackUpManagerJGit.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package org.jabref.gui.autosaveandbackup;

import java.io.File;
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 java.util.concurrent.TimeUnit;

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.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.jabref.gui.LibraryTab;
import org.jabref.logic.preferences.CliPreferences;
import org.jabref.logic.util.CoarseChangeFilter;
Expand All @@ -23,7 +26,6 @@ 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<BackupManager> runningInstances = new HashSet<>();
Expand All @@ -34,13 +36,11 @@ public class BackUpManagerJGit {
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<Path> backupFilesQueue = new LinkedBlockingQueue<>();
private boolean needsBackup = false;

public BackUpManagerJGit(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;
Expand All @@ -49,93 +49,83 @@ public BackUpManagerJGit(LibraryTab libraryTab, BibDatabaseContext bibDatabaseCo

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 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;

public static BackupManager start(LibraryTab libraryTab, BibDatabaseContext bibDatabaseContext, BibEntryTypesManager entryTypesManager, CliPreferences preferences) throws IOException, GitAPIException {
BackupManager backupManager = new BackupManager(libraryTab, bibDatabaseContext, entryTypesManager, preferences);
backupManager.startBackupTask(preferences.getFilePreferences().getBackupDirectory());
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));
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 <code>false</code>, See also {@link #discardBackup(Path)}.
*
* @param originalPath Path to the file a backup should be checked for. Example: jabref.bib.
*
* @return <code>true</code> if backup file exists AND differs from originalPath. <code>false</code> is the
* "default" return value in the good case. In case a discarded file exists, <code>false</code> is returned, too.
* In the case of an exception <code>true</code> is returned to ensure that the user checks the output.
*/
public static boolean backupGitDiffers(Path originalPath, Path backupDir) {
//à implementer

private void startBackupTask(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;
}
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);

// 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 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
* */
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 shutdown(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);
}
}
}

}
19 changes: 5 additions & 14 deletions src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,7 @@ 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()
Expand All @@ -258,26 +256,20 @@ 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)
.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<BibEntry> 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"
Expand Down Expand Up @@ -343,7 +335,7 @@ public synchronized void listen(@SuppressWarnings("unused") BibDatabaseContextCh
}

private void startBackupTask(Path backupDir) {
fillQueue(backupDir);//remplie backupFilesQueue les files .sav de le meme bibl
fillQueue(backupDir);

executor.scheduleAtFixedRate(
// We need to determine the backup path on each action, because we use the timestamp in the filename
Expand All @@ -352,8 +344,7 @@ 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;
Expand All @@ -364,7 +355,7 @@ private void fillQueue(Path backupDir) {
try {
List<Path> 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
.filter(p -> p.getFileName().toString().startsWith(prefix))
.sorted().toList();
backupFilesQueue.addAll(allSavFiles);
} catch (IOException e) {
Expand Down

0 comments on commit cff3427

Please sign in to comment.