From 636f579b3d17464ebed8d071bd05cce3b02de026 Mon Sep 17 00:00:00 2001 From: Ritij Paudel <43158841+paudelritij@users.noreply.github.com> Date: Tue, 10 Jun 2025 02:33:10 +0930 Subject: [PATCH] Add auto-renaming of linked files on entry data change - Implemented a preference option under Linked files -> Linked file name conventions to enable auto-renaming of files when entry data changes (default: false). - Added functionality to listen for entry change events and rename files if the preference is enabled and the file name matches the defined pattern. - Ensured that no action is taken if the pattern is empty, as the file name would not match. - Considered scenarios where an entry has multiple files attached and handled them appropriately. - Added test cases to verify the new functionality. --- CHANGELOG.md | 1 + jabgui/build.gradle.kts | 4 + .../main/java/org/jabref/gui/LibraryTab.java | 2 + .../AutoRenameFileOnEntryChange.java | 66 ++++++++ .../linkedfiles/LinkedFilesTab.java | 2 + .../linkedfiles/LinkedFilesTabViewModel.java | 7 + .../linkedfiles/LinkedFilesTab.fxml | 1 + .../AutoRenameFileOnEntryChangeTest.java | 150 ++++++++++++++++++ .../org/jabref/logic/FilePreferences.java | 15 ++ .../preferences/JabRefCliPreferences.java | 4 + .../main/resources/l10n/JabRef_en.properties | 1 + 11 files changed, 253 insertions(+) create mode 100644 jabgui/src/main/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChange.java create mode 100644 jabgui/src/test/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChangeTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 234387fd283..59faf5e5b2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv ### Added - We introduced a settings parameters to manage citations' relations local storage time-to-live with a default value set to 30 days. [#11189](https://github.com/JabRef/jabref/issues/11189) +- We introduced an option in Preferences under (under Linked files -> Linked file name conventions) to automatically rename linked files when an entry data changes. [#11316](https://github.com/JabRef/jabref/issues/11316) ### Changed diff --git a/jabgui/build.gradle.kts b/jabgui/build.gradle.kts index 9f4edb3b1d6..6955eb805fb 100644 --- a/jabgui/build.gradle.kts +++ b/jabgui/build.gradle.kts @@ -131,6 +131,10 @@ dependencies { testImplementation("io.github.classgraph:classgraph:4.8.179") testImplementation("org.testfx:testfx-core:4.0.16-alpha") testImplementation("org.testfx:testfx-junit5:4.0.16-alpha") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.12.2") + testImplementation("org.junit.jupiter:junit-jupiter:5.12.2") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.12.2") + testImplementation("org.junit.platform:junit-platform-launcher:1.12.2") testImplementation("org.mockito:mockito-core:5.18.0") { exclude(group = "net.bytebuddy", module = "byte-buddy") diff --git a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java index 2afe8e908c1..a924b054fe0 100644 --- a/jabgui/src/main/java/org/jabref/gui/LibraryTab.java +++ b/jabgui/src/main/java/org/jabref/gui/LibraryTab.java @@ -43,6 +43,7 @@ import org.jabref.gui.collab.DatabaseChangeMonitor; import org.jabref.gui.dialogs.AutosaveUiManager; import org.jabref.gui.exporter.SaveDatabaseAction; +import org.jabref.gui.externalfiles.AutoRenameFileOnEntryChange; import org.jabref.gui.externalfiles.ImportHandler; import org.jabref.gui.fieldeditors.LinkedFileViewModel; import org.jabref.gui.importer.actions.OpenDatabaseAction; @@ -203,6 +204,7 @@ private void initializeComponentsAndListeners(boolean isDummyContext) { bibDatabaseContext.getDatabase().registerListener(this); bibDatabaseContext.getMetaData().registerListener(this); + new AutoRenameFileOnEntryChange(bibDatabaseContext, preferences); this.selectedGroupsProperty = new SimpleListProperty<>(stateManager.getSelectedGroups(bibDatabaseContext)); this.tableModel = new MainTableDataModel(getBibDatabaseContext(), preferences, taskExecutor, getIndexManager(), selectedGroupsProperty(), searchQueryProperty, resultSizeProperty()); diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChange.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChange.java new file mode 100644 index 00000000000..c4db6722cd2 --- /dev/null +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChange.java @@ -0,0 +1,66 @@ +package org.jabref.gui.externalfiles; + +import java.util.HashSet; +import java.util.Set; + +import org.jabref.gui.preferences.GuiPreferences; +import org.jabref.logic.FilePreferences; +import org.jabref.logic.citationkeypattern.CitationKeyGenerator; +import org.jabref.logic.cleanup.RenamePdfCleanup; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.event.FieldChangedEvent; + +import com.google.common.eventbus.Subscribe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.jabref.logic.citationkeypattern.BracketedPattern.expandBrackets; + +public class AutoRenameFileOnEntryChange { + private static final Logger LOGGER = LoggerFactory.getLogger(AutoRenameFileOnEntryChange.class); + + private final GuiPreferences preferences; + private final BibDatabaseContext bibDatabaseContext; + private final RenamePdfCleanup renamePdfCleanup; + + public AutoRenameFileOnEntryChange(BibDatabaseContext bibDatabaseContext, GuiPreferences preferences) { + this.bibDatabaseContext = bibDatabaseContext; + this.preferences = preferences; + bibDatabaseContext.getDatabase().registerListener(this); + renamePdfCleanup = new RenamePdfCleanup(false, () -> bibDatabaseContext, preferences.getFilePreferences()); + } + + @Subscribe + public void listen(FieldChangedEvent event) { + FilePreferences filePreferences = preferences.getFilePreferences(); + + if (!filePreferences.shouldAutoRenameFilesOnChange() + || filePreferences.getFileNamePattern().isEmpty() + || filePreferences.getFileNamePattern() == null + || !relatesToFilePattern(filePreferences.getFileNamePattern(), event)) { + return; + } + + BibEntry entry = event.getBibEntry(); + if (entry.getFiles().isEmpty()) { + return; + } + new CitationKeyGenerator(bibDatabaseContext, preferences.getCitationKeyPatternPreferences()).generateAndSetKey(entry); + renamePdfCleanup.cleanup(entry); + + LOGGER.info("Field changed for entry {}: {}", entry.getCitationKey().orElse("defaultCitationKey"), event.getField().getName()); + } + + private boolean relatesToFilePattern(String fileNamePattern, FieldChangedEvent event) { + Set extractedFields = new HashSet<>(); + + expandBrackets(fileNamePattern, bracketContent -> { + extractedFields.add(bracketContent); + return bracketContent; + }); + + return extractedFields.contains("bibtexkey") + || extractedFields.contains(event.getField().getName()); + } +} diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.java b/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.java index f67aba3c420..f4683d4a202 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.java @@ -36,6 +36,7 @@ public class LinkedFilesTab extends AbstractPreferenceTabView fileNamePattern; @FXML private TextField fileDirectoryPattern; @@ -73,6 +74,7 @@ public void initialize() { autolinkRegexKey.textProperty().bindBidirectional(viewModel.autolinkRegexKeyProperty()); autolinkRegexKey.disableProperty().bind(autolinkUseRegex.selectedProperty().not()); fulltextIndex.selectedProperty().bindBidirectional(viewModel.fulltextIndexProperty()); + autoRenameFilesOnChange.selectedProperty().bindBidirectional(viewModel.autoRenameFilesOnChangeProperty()); fileNamePattern.valueProperty().bindBidirectional(viewModel.fileNamePatternProperty()); fileNamePattern.itemsProperty().bind(viewModel.defaultFileNamePatternsProperty()); fileDirectoryPattern.textProperty().bindBidirectional(viewModel.fileDirectoryPatternProperty()); diff --git a/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTabViewModel.java b/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTabViewModel.java index 3b83c2c7f0b..313274508f6 100644 --- a/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTabViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/preferences/linkedfiles/LinkedFilesTabViewModel.java @@ -37,6 +37,7 @@ public class LinkedFilesTabViewModel implements PreferenceTabViewModel { private final ListProperty defaultFileNamePatternsProperty = new SimpleListProperty<>(FXCollections.observableArrayList(FilePreferences.DEFAULT_FILENAME_PATTERNS)); private final BooleanProperty fulltextIndex = new SimpleBooleanProperty(); + private final BooleanProperty autoRenameFilesOnChangeProperty = new SimpleBooleanProperty(); private final StringProperty fileNamePatternProperty = new SimpleStringProperty(); private final StringProperty fileDirectoryPatternProperty = new SimpleStringProperty(); private final BooleanProperty confirmLinkedFileDeleteProperty = new SimpleBooleanProperty(); @@ -82,6 +83,7 @@ public void setValues() { useMainFileDirectoryProperty.setValue(!filePreferences.shouldStoreFilesRelativeToBibFile()); useBibLocationAsPrimaryProperty.setValue(filePreferences.shouldStoreFilesRelativeToBibFile()); fulltextIndex.setValue(filePreferences.shouldFulltextIndexLinkedFiles()); + autoRenameFilesOnChangeProperty.setValue(filePreferences.shouldAutoRenameFilesOnChange()); fileNamePatternProperty.setValue(filePreferences.getFileNamePattern()); fileDirectoryPatternProperty.setValue(filePreferences.getFileDirectoryPattern()); confirmLinkedFileDeleteProperty.setValue(filePreferences.confirmDeleteLinkedFile()); @@ -104,6 +106,7 @@ public void storeSettings() { // External files preferences / Attached files preferences / File preferences filePreferences.setMainFileDirectory(mainFileDirectoryProperty.getValue()); filePreferences.setStoreFilesRelativeToBibFile(useBibLocationAsPrimaryProperty.getValue()); + filePreferences.setAutoRenameFilesOnChange(autoRenameFilesOnChangeProperty.getValue()); filePreferences.setFileNamePattern(fileNamePatternProperty.getValue()); filePreferences.setFileDirectoryPattern(fileDirectoryPatternProperty.getValue()); filePreferences.setFulltextIndexLinkedFiles(fulltextIndex.getValue()); @@ -179,6 +182,10 @@ public ListProperty defaultFileNamePatternsProperty() { return defaultFileNamePatternsProperty; } + public BooleanProperty autoRenameFilesOnChangeProperty() { + return autoRenameFilesOnChangeProperty; + } + public StringProperty fileNamePatternProperty() { return fileNamePatternProperty; } diff --git a/jabgui/src/main/resources/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.fxml b/jabgui/src/main/resources/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.fxml index f71481fbe9b..947d1bdb7ae 100644 --- a/jabgui/src/main/resources/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.fxml +++ b/jabgui/src/main/resources/org/jabref/gui/preferences/linkedfiles/LinkedFilesTab.fxml @@ -65,6 +65,7 @@