From 18f87e919412995bffac314cc6dd8b0b1cdf3d79 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 13 Sep 2026 23:59:19 +0200 Subject: [PATCH 01/13] Persist group membership in sidecars and make file renames opt-in Explicit group membership was lost across restarts because the `groups` field had no Hayagriva key. Sidecar renames now follow "Auto rename files if entry changes", with a library-level override in the General properties tab that also applies to .bib libraries. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01H7bhV1r2H2XZvjHrYQkiJt --- CHANGELOG.md | 1 + docs/requirements/directory-library.md | 10 +++++--- .../AutoRenameFileOnEntryChange.java | 4 ++- .../actions/OpenDirectoryLibraryAction.java | 8 ++++-- .../general/GeneralPropertiesView.java | 15 +++++++++++ .../general/GeneralPropertiesViewModel.java | 7 ++++++ .../general/GeneralProperties.fxml | 4 +++ .../directorylibrary/SidecarWriteBack.java | 2 +- .../logic/exporter/MetaDataSerializer.java | 2 ++ .../importer/fileformat/HayagrivaMapping.java | 8 +++--- .../logic/importer/util/MetaDataParser.java | 2 ++ .../org/jabref/model/metadata/MetaData.java | 25 ++++++++++++++++--- .../main/resources/l10n/JabRef_en.properties | 3 +++ .../exporter/HayagrivaEntryWriterTest.java | 22 ++++++++++++++++ .../exporter/MetaDataSerializerTest.java | 7 ++++++ .../importer/util/MetaDataParserTest.java | 8 ++++++ 16 files changed, 114 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55ecba5a5f95..61af1aba8f03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added OCR language selection to the OCR preferences, allowing users to configure which Tesseract languages to use. [#16618](https://github.com/JabRef/jabref/issues/16618) - We added a fetcher for the Bielefeld Academic Search Engine (BASE). [#15016](https://github.com/JabRef/jabref/issues/15016) - We added a Git section to the library properties for automatic commit, pull and push. [#12630](https://github.com/JabRef/jabref/issues/12630) +- We added a library-specific override of the "Auto rename files if entry changes" preference to the library properties. [#17111](https://github.com/JabRef/jabref/pull/17111) - We added connection URL pasting to the shared database login dialog. [#16800](https://github.com/JabRef/jabref/pull/16800) - We added a Dnb Fetcher that uses SRU interface and wired to ISBN Fetcher. [#17070](https://github.com/JabRef/jabref/pull/17070) - We added read-only access to shared SQL libraries in `jabkit`: every input file argument accepts a PostgreSQL URL. [#12948](https://github.com/JabRef/jabref/issues/12948) diff --git a/docs/requirements/directory-library.md b/docs/requirements/directory-library.md index 92a86318b104..1c5066aa3e0a 100644 --- a/docs/requirements/directory-library.md +++ b/docs/requirements/directory-library.md @@ -82,11 +82,13 @@ entries or subgroups can be added to them, they cannot be dragged or edited. Needs: impl ## The sidecar and its PDF follow the configured filename pattern -`req~directory-library.pattern-rename~1` +`req~directory-library.pattern-rename~2` -When write-back touches a single-entry sidecar, the sidecar and its equally named PDF are -renamed together to the base name the configured filename pattern (Linked files preferences) -generates for the entry, keeping the pair in sync. Multi-entry files have no single generating +When write-back touches a single-entry sidecar and auto-renaming is enabled, the sidecar and +its equally named PDF are renamed together to the base name the configured filename pattern +(Linked files preferences) generates for the entry, keeping the pair in sync. Auto-renaming +follows the global "Auto rename files if entry changes" preference unless the library +properties override it (General tab; the same override applies to `.bib` libraries). Multi-entry files have no single generating entry and keep their name; occupied target names and pattern failures leave the current name untouched. Entry file links and the catalog follow the rename; the watcher does not re-import the renamed files. diff --git a/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChange.java b/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChange.java index 98af6a04f795..33752c6685b1 100644 --- a/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChange.java +++ b/jabgui/src/main/java/org/jabref/gui/externalfiles/AutoRenameFileOnEntryChange.java @@ -14,17 +14,19 @@ public class AutoRenameFileOnEntryChange { private static final Logger LOGGER = LoggerFactory.getLogger(AutoRenameFileOnEntryChange.class); + private final BibDatabaseContext bibDatabaseContext; private final FilePreferences filePreferences; private final RenamePdfCleanup renamePdfCleanup; public AutoRenameFileOnEntryChange(BibDatabaseContext bibDatabaseContext, FilePreferences filePreferences) { + this.bibDatabaseContext = bibDatabaseContext; this.filePreferences = filePreferences; renamePdfCleanup = new RenamePdfCleanup(false, () -> bibDatabaseContext, filePreferences); } @Subscribe public void listen(FieldChangedEvent event) { - if (!filePreferences.shouldAutoRenameFilesOnChange() + if (!bibDatabaseContext.getMetaData().getAutoRenameFilesOnChange().orElse(filePreferences.shouldAutoRenameFilesOnChange()) || filePreferences.getFileNamePattern().isEmpty() || filePreferences.getFileNamePattern() == null) { return; diff --git a/jabgui/src/main/java/org/jabref/gui/importer/actions/OpenDirectoryLibraryAction.java b/jabgui/src/main/java/org/jabref/gui/importer/actions/OpenDirectoryLibraryAction.java index aa4eaea0ff1d..7165b2e50604 100644 --- a/jabgui/src/main/java/org/jabref/gui/importer/actions/OpenDirectoryLibraryAction.java +++ b/jabgui/src/main/java/org/jabref/gui/importer/actions/OpenDirectoryLibraryAction.java @@ -160,8 +160,12 @@ private void showLibraryTab(DirectoryLibraryScanner.ScanResult scanResult, PdfEn libraryTab.updateTabTitle(false); BibDatabaseContext databaseContext = scanResult.databaseContext(); - Function> fileNameGenerator = entry -> FileUtil.createFileNameFromPattern( - databaseContext.getDatabase(), entry, preferences.getFilePreferences().getFileNamePattern()); + // Evaluated per write, so a changed preference (global or library) takes effect immediately + // [impl->req~directory-library.pattern-rename~2] + Function> fileNameGenerator = entry -> + databaseContext.getMetaData().getAutoRenameFilesOnChange().orElse(preferences.getFilePreferences().shouldAutoRenameFilesOnChange()) + ? FileUtil.createFileNameFromPattern(databaseContext.getDatabase(), entry, preferences.getFilePreferences().getFileNamePattern()) + : Optional.empty(); GuiGitConflictResolverStrategy conflictResolver = new GuiGitConflictResolverStrategy( new GitConflictResolverDialog(dialogService, preferences, stateManager)); DirectoryLibrarySynchronizer synchronizer = new DirectoryLibrarySynchronizer( diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java index e745030452ff..2d22941b6b21 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesView.java @@ -2,9 +2,11 @@ import java.nio.charset.Charset; import java.nio.file.Path; +import java.util.Optional; import java.util.function.UnaryOperator; import javafx.application.Platform; +import javafx.collections.FXCollections; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.control.ComboBox; @@ -37,6 +39,7 @@ public class GeneralPropertiesView extends AbstractPropertiesTabView> autoRenameFilesOnChange; @FXML private Button libSpecificFileDirSwitchId; @FXML private Button userSpecificFileDirSwitchId; @FXML private Button laTexSpecificFileDirSwitchId; @@ -101,6 +104,14 @@ public void initialize() { change -> change.getControlNewText().length() <= 1 ? change : null; keywordSeparator.setTextFormatter(new TextFormatter<>(singleCharacterFilter)); + boolean globalAutoRename = preferences.getFilePreferences().shouldAutoRenameFilesOnChange(); + new ViewModelListCellFactory>() + .withText(choice -> choice.map(GeneralPropertiesView::yesOrNo) + .orElse(Localization.lang("Use global preference (%0)", yesOrNo(globalAutoRename)))) + .install(autoRenameFilesOnChange); + autoRenameFilesOnChange.setItems(FXCollections.observableArrayList(Optional.empty(), Optional.of(true), Optional.of(false))); + autoRenameFilesOnChange.valueProperty().bindBidirectional(viewModel.autoRenameFilesOnChangeProperty()); + userSpecificFileDirectoryTooltip.setText(Localization.lang("User-specific file directory: %0", preferences.getFilePreferences().getUserAndHost())); userSpecificFileDirectory.setTooltip(userSpecificFileDirectoryTooltip); @@ -179,4 +190,8 @@ void userSpecificFileDirPathSwitch() { void laTexSpecificFileDirPathSwitch() { viewModel.togglePath(viewModel.laTexFileDirectoryProperty()); } + + private static String yesOrNo(boolean value) { + return value ? Localization.lang("Yes") : Localization.lang("No"); + } } diff --git a/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java b/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java index 31e42d327ff4..05ed0a05d4da 100644 --- a/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java +++ b/jabgui/src/main/java/org/jabref/gui/libraryproperties/general/GeneralPropertiesViewModel.java @@ -47,6 +47,7 @@ public class GeneralPropertiesViewModel implements PropertiesTabViewModel { private final StringProperty userSpecificFileDirectoryProperty = new SimpleStringProperty(""); private final StringProperty laTexFileDirectoryProperty = new SimpleStringProperty(""); private final StringProperty keywordSeparatorProperty = new SimpleStringProperty(""); + private final ObjectProperty> autoRenameFilesOnChangeProperty = new SimpleObjectProperty<>(Optional.empty()); private final Validator librarySpecificFileDirectoryValidator; private final Validator userSpecificFileDirectoryValidator; @@ -91,6 +92,7 @@ public void setValues(MetaData metaData) { userSpecificFileDirectoryProperty.setValue(metaData.getUserFileDirectory(preferences.getFilePreferences().getUserAndHost()).orElse("").trim()); laTexFileDirectoryProperty.setValue(metaData.getLatexFileDirectory(preferences.getFilePreferences().getUserAndHost()).map(Path::toString).orElse("")); keywordSeparatorProperty.setValue(metaData.getKeywordSeparator().map(Object::toString).orElse("")); + autoRenameFilesOnChangeProperty.setValue(metaData.getAutoRenameFilesOnChange()); } @Override @@ -120,6 +122,7 @@ public void storeSettings(MetaData metaData) { } storeKeywordSeparator(metaData); + autoRenameFilesOnChangeProperty.getValue().ifPresentOrElse(metaData::setAutoRenameFilesOnChange, metaData::clearAutoRenameFilesOnChange); } /// The separator and the group definitions the migration rewrites are both metadata, so the @@ -225,6 +228,10 @@ public StringProperty keywordSeparatorProperty() { return this.keywordSeparatorProperty; } + public ObjectProperty> autoRenameFilesOnChangeProperty() { + return this.autoRenameFilesOnChangeProperty; + } + private Path getBrowseDirectory(String configuredDir) { Optional libPath = this.databaseContext.getDatabasePath(); Path workingDir = preferences.getFilePreferences().getWorkingDirectory(); diff --git a/jabgui/src/main/resources/org/jabref/gui/libraryproperties/general/GeneralProperties.fxml b/jabgui/src/main/resources/org/jabref/gui/libraryproperties/general/GeneralProperties.fxml index ab0665cee773..390a93dc31a5 100644 --- a/jabgui/src/main/resources/org/jabref/gui/libraryproperties/general/GeneralProperties.fxml +++ b/jabgui/src/main/resources/org/jabref/gui/libraryproperties/general/GeneralProperties.fxml @@ -135,6 +135,10 @@ GridPane.columnIndex="0" GridPane.rowIndex="7"/> +