Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore auto rename feature 2 #12604

Draft
wants to merge 17 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@

### Added


Check failure on line 14 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / Markdown

Multiple consecutive blank lines

CHANGELOG.md:14 MD012/no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 2] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md012.md

Check failure on line 15 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / Markdown

Multiple consecutive blank lines

CHANGELOG.md:15 MD012/no-multiple-blanks Multiple consecutive blank lines [Expected: 1; Actual: 3] https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md012.md
- We added automatic file renaming functionality: when entry data is changed, associated filenames are updated automatically without requiring manual clicks on the "Generate" button. [#12604](https://github.com/JabRef/jabref/pull/12604)

- We added <kbd>F5</kbd> as a shortcut key for fetching data and <kbd>Alt+F</kbd> as a shortcut for looking up data using DOI. [#11802](https://github.com/JabRef/jabref/issues/11802)

- We added a feature to rename the subgroup, with the keybinding (<kbd>F2</kbd>) for quick access. [#11896](https://github.com/JabRef/jabref/issues/11896)
- We added a new functionality that displays a drop-down list of matching suggestions when typing a citation key pattern. [#12502](https://github.com/JabRef/jabref/issues/12502)
- We added a new CLI that supports txt, csv, and console-based output for consistency in BibTeX entries. [#11984](https://github.com/JabRef/jabref/issues/11984)
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/jabref/gui/LibraryTab.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@
import org.jabref.gui.util.OptionalObjectProperty;
import org.jabref.gui.util.UiTaskExecutor;
import org.jabref.logic.ai.AiService;
import org.jabref.logic.citationkeypattern.AutomaticCitationKeyGenerator;
import org.jabref.logic.citationstyle.CitationStyleCache;
import org.jabref.logic.externalfiles.AutomaticFileRenamer;
import org.jabref.logic.importer.FetcherClientException;
import org.jabref.logic.importer.FetcherException;
import org.jabref.logic.importer.FetcherServerException;
Expand Down Expand Up @@ -251,6 +253,19 @@ private void initializeComponentsAndListeners(boolean isDummyContext) {

this.getDatabase().registerListener(new UpdateTimestampListener(preferences));

// Register automatic citation key generator
AutomaticCitationKeyGenerator citationKeyGenerator = new AutomaticCitationKeyGenerator(
bibDatabaseContext,
preferences.getCitationKeyPatternPreferences(),
preferences.getFilePreferences());
this.getDatabase().registerListener(citationKeyGenerator);

// Register automatic file renamer
AutomaticFileRenamer fileRenamer = new AutomaticFileRenamer(
bibDatabaseContext,
preferences.getFilePreferences());
this.getDatabase().registerListener(fileRenamer);

this.entryEditor = createEntryEditor();

aiService.setupDatabase(bibDatabaseContext);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/jabref/gui/openoffice/Bootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ private static void insertBasicFactories(XSet xSet, XImplementationLoader xImpLo
* </pre>
*
* @return an array of default commandline options
* @see #bootstrap(String[])
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here lacks one parameter it should be @see #bootstrap(String[], Path) otherwise the project doesn't compile

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't see why openoffice gui files need to be touched here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side track: Git Butler is a neat git client for submitting unrelated pull requests while keeping all changes locally merged. - However, this for advanced users, because one will get feedback on the pull requests and need to handle them.

* @see #bootstrap(String[], Path)
* @since LibreOffice 5.1
*/
public static String[] getDefaultOptions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@

<Label styleClass="sectionHeader" text="%Fulltext Index"/>
<CheckBox fx:id="fulltextIndex" text="%Automatically index all linked files for fulltext search"/>

<Label styleClass="sectionHeader" text="%Linked file name conventions"/>
<GridPane hgap="4.0" vgap="4.0">
<columnConstraints>
Expand All @@ -76,7 +75,7 @@
<TextField fx:id="fileDirectoryPattern" GridPane.columnIndex="1" GridPane.rowIndex="1"
prefWidth="300" minWidth="300" maxWidth="300"/>
</GridPane>

<CheckBox fx:id="autoRenameFiles" text="%Auto rename files if entry changes"/>
<Label styleClass="sectionHeader" text="%Attached files"/>
<CheckBox fx:id="confirmLinkedFileDelete" text="%Show confirmation dialog when deleting attached files"/>
<CheckBox fx:id="moveToTrash" text="%Move deleted files to trash (instead of deleting them)"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class LinkedFilesTab extends AbstractPreferenceTabView<LinkedFilesTabView
@FXML private TextField fileDirectoryPattern;
@FXML private CheckBox confirmLinkedFileDelete;
@FXML private CheckBox moveToTrash;
@FXML private CheckBox autoRenameFiles;

private final ControlsFxVisualizer validationVisualizer = new ControlsFxVisualizer();

Expand Down Expand Up @@ -75,6 +76,7 @@ public void initialize() {
fileNamePattern.itemsProperty().bind(viewModel.defaultFileNamePatternsProperty());
fileDirectoryPattern.textProperty().bindBidirectional(viewModel.fileDirectoryPatternProperty());
confirmLinkedFileDelete.selectedProperty().bindBidirectional(viewModel.confirmLinkedFileDeleteProperty());
autoRenameFiles.selectedProperty().bindBidirectional(viewModel.autoRenameFilesProperty());

ActionFactory actionFactory = new ActionFactory();
actionFactory.configureIconButton(StandardActions.HELP_REGEX_SEARCH, new HelpAction(HelpFile.REGEX_SEARCH, dialogService, preferences.getExternalApplicationsPreferences()), autolinkRegexHelp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ public class LinkedFilesTabViewModel implements PreferenceTabViewModel {
private final BooleanProperty autolinkUseRegexProperty = new SimpleBooleanProperty();
private final StringProperty autolinkRegexKeyProperty = new SimpleStringProperty("");
private final ListProperty<String> defaultFileNamePatternsProperty =
new SimpleListProperty<>(FXCollections.observableArrayList(FilePreferences.DEFAULT_FILENAME_PATTERNS));
new SimpleListProperty<>(FXCollections.observableArrayList(FilePreferences.DEFAULT_FILENAME_PATTERNS));
private final BooleanProperty fulltextIndex = new SimpleBooleanProperty();
private final StringProperty fileNamePatternProperty = new SimpleStringProperty();
private final StringProperty fileDirectoryPatternProperty = new SimpleStringProperty();
private final BooleanProperty confirmLinkedFileDeleteProperty = new SimpleBooleanProperty();
private final BooleanProperty moveToTrashProperty = new SimpleBooleanProperty();
private final BooleanProperty autoRenameFilesProperty = new SimpleBooleanProperty();

private final Validator mainFileDirValidator;

Expand Down Expand Up @@ -84,6 +85,7 @@ public void setValues() {
fileDirectoryPatternProperty.setValue(filePreferences.getFileDirectoryPattern());
confirmLinkedFileDeleteProperty.setValue(filePreferences.confirmDeleteLinkedFile());
moveToTrashProperty.setValue(filePreferences.moveToTrash());
autoRenameFilesProperty.setValue(filePreferences.shouldAutoRenameFilesOnEntryChange());

// Autolink preferences
switch (autoLinkPreferences.getCitationKeyDependency()) {
Expand Down Expand Up @@ -116,6 +118,7 @@ public void storeSettings() {
autoLinkPreferences.setRegularExpression(autolinkRegexKeyProperty.getValue());
filePreferences.confirmDeleteLinkedFile(confirmLinkedFileDeleteProperty.getValue());
filePreferences.moveToTrash(moveToTrashProperty.getValue());
filePreferences.setAutoRenameFilesOnEntryChange(autoRenameFilesProperty.getValue());
}

ValidationStatus mainFileDirValidationStatus() {
Expand Down Expand Up @@ -192,5 +195,8 @@ public BooleanProperty confirmLinkedFileDeleteProperty() {
public BooleanProperty moveToTrashProperty() {
return this.moveToTrashProperty;
}
}

public BooleanProperty autoRenameFilesProperty() {
return this.autoRenameFilesProperty;
}
}
18 changes: 16 additions & 2 deletions src/main/java/org/jabref/logic/FilePreferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class FilePreferences {
private final BooleanProperty confirmDeleteLinkedFile = new SimpleBooleanProperty();
private final BooleanProperty moveToTrash = new SimpleBooleanProperty();
private final BooleanProperty shouldKeepDownloadUrl = new SimpleBooleanProperty();

private final BooleanProperty autoRenameFilesOnEntryChange = new SimpleBooleanProperty();
public FilePreferences(String userAndHost,
String mainFileDirectory,
boolean storeFilesRelativeToBibFile,
Expand All @@ -45,7 +45,8 @@ public FilePreferences(String userAndHost,
Path backupDirectory,
boolean confirmDeleteLinkedFile,
boolean moveToTrash,
boolean shouldKeepDownloadUrl) {
boolean shouldKeepDownloadUrl,
boolean autoRenameFilesOnEntryChange) {
this.userAndHost.setValue(userAndHost);
this.mainFileDirectory.setValue(mainFileDirectory);
this.storeFilesRelativeToBibFile.setValue(storeFilesRelativeToBibFile);
Expand All @@ -59,6 +60,7 @@ public FilePreferences(String userAndHost,
this.confirmDeleteLinkedFile.setValue(confirmDeleteLinkedFile);
this.moveToTrash.setValue(moveToTrash);
this.shouldKeepDownloadUrl.setValue(shouldKeepDownloadUrl);
this.autoRenameFilesOnEntryChange.setValue(autoRenameFilesOnEntryChange);
}

public String getUserAndHost() {
Expand Down Expand Up @@ -213,6 +215,18 @@ public BooleanProperty shouldKeepDownloadUrlProperty() {
return shouldKeepDownloadUrl;
}

public boolean shouldAutoRenameFilesOnEntryChange() {
return autoRenameFilesOnEntryChange.get();
}

public BooleanProperty autoRenameFilesOnEntryChangeProperty() {
return autoRenameFilesOnEntryChange;
}

public void setAutoRenameFilesOnEntryChange(boolean autoRenameFilesOnEntryChange) {
this.autoRenameFilesOnEntryChange.set(autoRenameFilesOnEntryChange);
}

public void setKeepDownloadUrl(boolean shouldKeepDownloadUrl) {
this.shouldKeepDownloadUrl.set(shouldKeepDownloadUrl);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.jabref.logic.citationkeypattern;

import org.jabref.logic.FilePreferences;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.event.FieldChangedEvent;
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.StandardField;

import com.google.common.eventbus.Subscribe;

public class AutomaticCitationKeyGenerator {
private final CitationKeyGenerator keyGenerator;
private final BibDatabaseContext databaseContext;
private final FilePreferences filePreferences;

public AutomaticCitationKeyGenerator(BibDatabaseContext databaseContext,
CitationKeyPatternPreferences citationKeyPatternPreferences,
FilePreferences filePreferences) {
this.databaseContext = databaseContext;
this.keyGenerator = new CitationKeyGenerator(databaseContext, citationKeyPatternPreferences);
this.filePreferences = filePreferences;
}

/**
* Listens for field changes and regenerates the citation key if a relevant field changed.
*/
@Subscribe
public void listen(FieldChangedEvent event) {
BibEntry entry = event.getBibEntry();
Field field = event.getField();

// Check if the changed field affects the citation key generation
// Typically includes: author, year, title, etc.
if (isRelevantField(field)) {
// Regenerate the citation key
keyGenerator.generateAndSetKey(entry);
}
}

/**
* Determines if a field is relevant for citation key generation.
*
* @param field The field to check
* @return true if the field affects citation key generation
*/
private boolean isRelevantField(Field field) {
// Determine which fields affect the citation key based on the pattern
return field == StandardField.AUTHOR ||
field == StandardField.YEAR ||
field == StandardField.TITLE;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.jabref.logic.externalfiles;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;

import org.jabref.logic.FilePreferences;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.LinkedFile;
import org.jabref.model.entry.event.FieldChangedEvent;

import com.google.common.eventbus.Subscribe;

public class AutomaticFileRenamer {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use org.jabref.logic.cleanup.RenamePdfCleanup#cleanup instead of self-written class. If this is not possible, comment why.


private final BibDatabaseContext databaseContext;
private final FilePreferences filePreferences;
private final AtomicBoolean isCurrentlyRenamingFile = new AtomicBoolean(false);

public AutomaticFileRenamer(BibDatabaseContext databaseContext, FilePreferences filePreferences) {
this.databaseContext = databaseContext;
this.filePreferences = filePreferences;
}

@Subscribe
public void listen(FieldChangedEvent event) {
if (!filePreferences.shouldAutoRenameFilesOnEntryChange()) {
return;
}

if (isCurrentlyRenamingFile.get()) {
return;
}

BibEntry entry = event.getBibEntry();

if (entry.getFiles().isEmpty()) {
return;
}

new Thread(() -> {
try {
Thread.sleep(500); // avoid renaming the file too frequently
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use background task infrasturcture


if (!isCurrentlyRenamingFile.compareAndSet(false, true)) {
return;
} // make sure only one thread is renaming the file at a time - Thread Safety

try {
if (entry.getCitationKey().isEmpty()) {
return;
} // make sure the entry has a citation key

List<LinkedFile> updatedFiles = new ArrayList<>();
boolean anyFileRenamed = false;

// handling every file in the entry
for (LinkedFile linkedFile : entry.getFiles()) {
if (linkedFile.isOnlineLink()) {
updatedFiles.add(linkedFile);
continue;
}

Optional<Path> filePath = linkedFile.findIn(databaseContext, filePreferences);
if (filePath.isEmpty()) {
updatedFiles.add(linkedFile);
continue;
}

LinkedFileHandler fileHandler = new LinkedFileHandler(linkedFile, entry, databaseContext, filePreferences);
try {
boolean renamed = fileHandler.renameToSuggestedName();
if (renamed) {
LinkedFile updatedFile = fileHandler.refreshFileLink();
updatedFiles.add(updatedFile);
anyFileRenamed = true;
} else {
updatedFiles.add(linkedFile);
}
} catch (Exception e) {
updatedFiles.add(linkedFile);
}
}

if (anyFileRenamed) {
entry.setFiles(updatedFiles);
}
} finally {
isCurrentlyRenamingFile.set(false); // Make sure the flag is set to false after the renamer is done
}
} catch (Exception e) {
// DO NOTHING FOR NOW
}
}).start();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,20 @@ public Optional<Path> findExistingFile(LinkedFile linkedFile, BibEntry entry, St
}
return matchedByDiffCase;
}

public LinkedFile refreshFileLink() {
// Calculate the correct relative path based on the current file path and database context
Optional<Path> filePath = linkedFile.findIn(databaseContext, filePreferences);
if (filePath.isPresent()) {
String relativeLink = FileUtil.relativize(filePath.get(), databaseContext, filePreferences).toString();
return new LinkedFile(
linkedFile.getDescription(),
relativeLink,
linkedFile.getFileType()
);
} else {
// If the file cannot be found, return the original link
return linkedFile;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ public class JabRefCliPreferences implements CliPreferences {
public static final String BACKUP_DIRECTORY = "backupDirectory";
public static final String CREATE_BACKUP = "createBackup";


public static final String KEYWORD_SEPARATOR = "groupKeywordSeparator";

public static final String MEMORY_STICK_MODE = "memoryStickMode";
Expand Down Expand Up @@ -234,6 +235,7 @@ public class JabRefCliPreferences implements CliPreferences {
public static final String AVOID_OVERWRITING_KEY = "avoidOverwritingKey";
public static final String AUTOLINK_EXACT_KEY_ONLY = "autolinkExactKeyOnly";
public static final String AUTOLINK_FILES_ENABLED = "autoLinkFilesEnabled";
public static final String AUTO_RENAME_FILES_ON_ENTRY_CHANGE = "autoRenameFilesOnEntryChange";

public static final String GENERATE_KEYS_BEFORE_SAVING = "generateKeysBeforeSaving";
public static final String KEY_GEN_ALWAYS_ADD_LETTER = "keyGenAlwaysAddLetter";
Expand Down Expand Up @@ -266,6 +268,7 @@ public class JabRefCliPreferences implements CliPreferences {
public static final String VALIDATE_IN_ENTRY_EDITOR = "validateInEntryEditor";
public static final String SHOW_SCITE_TAB = "showSciteTab";


/**
* The OpenOffice/LibreOffice connection preferences are: OO_PATH main directory for OO/LO installation, used to detect location on Win/macOS when using manual connect OO_EXECUTABLE_PATH path to soffice-file OO_JARS_PATH directory that contains juh.jar, jurt.jar, ridl.jar, unoil.jar OO_SYNC_WHEN_CITING true if the reference list is updated when adding a new citation OO_SHOW_PANEL true if the OO panel is shown on startup OO_USE_ALL_OPEN_DATABASES true if all databases should be used when citing OO_BIBLIOGRAPHY_STYLE_FILE path to the used style file OO_EXTERNAL_STYLE_FILES list with paths to external style files STYLES_*_* size and position of "Select style" dialog
*/
Expand Down Expand Up @@ -380,7 +383,6 @@ public class JabRefCliPreferences implements CliPreferences {

private static final Logger LOGGER = LoggerFactory.getLogger(JabRefCliPreferences.class);
private static final Preferences PREFS_NODE = Preferences.userRoot().node("/org/jabref");

// The only instance of this class:
private static JabRefCliPreferences singleton;
/**
Expand Down Expand Up @@ -1554,7 +1556,8 @@ public FilePreferences getFilePreferences() {
getBoolean(CONFIRM_LINKED_FILE_DELETE),
// We make use of the fallback, because we need AWT being initialized, which is not the case at the constructor JabRefPreferences()
getBoolean(TRASH_INSTEAD_OF_DELETE, moveToTrashSupported()),
getBoolean(KEEP_DOWNLOAD_URL));
getBoolean(KEEP_DOWNLOAD_URL),
getBoolean(AUTO_RENAME_FILES_ON_ENTRY_CHANGE, false));

EasyBind.listen(getInternalPreferences().getUserAndHostProperty(), (obs, oldValue, newValue) -> filePreferences.getUserAndHostProperty().setValue(newValue));
EasyBind.listen(filePreferences.mainFileDirectoryProperty(), (obs, oldValue, newValue) -> put(MAIN_FILE_DIRECTORY, newValue));
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2870,3 +2870,4 @@ Author\ related=Author related
Editor\ related=Editor related
Title\ related=Title related
Entry\ fields=Entry fields
Auto\ rename\ files\ if\ entry\ changes=Auto rename files if entry changes
Loading
Loading