diff --git a/CHANGELOG.md b/CHANGELOG.md index 406e02ed7fe..b7db5173292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - `log.txt` does not contain entries for non-found files during PDF indexing. [#9678](https://github.com/JabRef/jabref/pull/9678) - We improved the Medline importer to correctly import ISO dates for `revised`. [#9536](https://github.com/JabRef/jabref/issues/9536) - To avoid cluttering of the directory, We always delete the `.sav` file upon successful write. [#9675](https://github.com/JabRef/jabref/pull/9675) - +- We improved the unlinking/deletion of multiple linked files of an entry using the Delete key [#9473](https://github.com/JabRef/jabref/issues/9473) ### Fixed diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java index f18975dad88..1e3daafffe7 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java @@ -385,7 +385,7 @@ public boolean delete() { Optional file = linkedFile.findIn(databaseContext, preferences.getFilePreferences()); if (file.isEmpty()) { - LOGGER.warn("Could not find file " + linkedFile.getLink()); + LOGGER.warn("Could not find file {}", linkedFile.getLink()); return true; } @@ -407,7 +407,7 @@ public boolean delete() { return true; } catch (IOException ex) { dialogService.showErrorDialogAndWait(Localization.lang("Cannot delete file"), Localization.lang("File permission error")); - LOGGER.warn("File permission error while deleting: " + linkedFile, ex); + LOGGER.warn("File permission error while deleting: {}", linkedFile, ex); } } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java index e93c3952858..b59d1a3164d 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java @@ -1,6 +1,5 @@ package org.jabref.gui.fieldeditors; -import java.util.List; import java.util.Optional; import javafx.beans.binding.Bindings; @@ -41,6 +40,7 @@ import org.jabref.gui.icon.IconTheme; import org.jabref.gui.importer.GrobidOptInDialogHelper; import org.jabref.gui.keyboard.KeyBinding; +import org.jabref.gui.linkedfile.DeleteFileAction; import org.jabref.gui.util.BindingsHelper; import org.jabref.gui.util.TaskExecutor; import org.jabref.gui.util.ViewModelListCellFactory; @@ -218,10 +218,8 @@ private void setUpKeyBindings() { if (keyBinding.isPresent()) { switch (keyBinding.get()) { case DELETE_ENTRY: - List toBeDeleted = List.copyOf(listView.getSelectionModel().getSelectedItems()); - for (LinkedFileViewModel selectedItem : toBeDeleted) { - viewModel.deleteFile(selectedItem); - } + new DeleteFileAction(dialogService, preferencesService, databaseContext, + viewModel, listView).execute(); event.consume(); break; default: diff --git a/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java b/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java new file mode 100644 index 00000000000..a1f9ef3bb15 --- /dev/null +++ b/src/main/java/org/jabref/gui/linkedfile/DeleteFileAction.java @@ -0,0 +1,138 @@ +package org.jabref.gui.linkedfile; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; + +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ListView; + +import org.jabref.gui.DialogService; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.fieldeditors.LinkedFileViewModel; +import org.jabref.gui.fieldeditors.LinkedFilesEditorViewModel; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.LinkedFile; +import org.jabref.preferences.PreferencesService; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DeleteFileAction extends SimpleCommand { + + private static final Logger LOGGER = LoggerFactory.getLogger(DeleteFileAction.class); + + private final DialogService dialogService; + private final PreferencesService preferences; + private final BibDatabaseContext databaseContext; + private final LinkedFilesEditorViewModel viewModel; + private final ListView listView; + + public DeleteFileAction(DialogService dialogService, + PreferencesService preferences, + BibDatabaseContext databaseContext, + LinkedFilesEditorViewModel viewModel, + ListView listView) { + this.dialogService = dialogService; + this.preferences = preferences; + this.databaseContext = databaseContext; + this.viewModel = viewModel; + this.listView = listView; + } + + @Override + public void execute() { + List toBeDeleted = List.copyOf(listView.getSelectionModel().getSelectedItems()); + + if (toBeDeleted.isEmpty()) { + dialogService.notify(Localization.lang("This operation requires selected linked files.")); + return; + } + + String dialogTitle; + String dialogContent; + + if (toBeDeleted.size() != 1) { + dialogTitle = Localization.lang("Delete %0 files", toBeDeleted.size()); + dialogContent = Localization.lang("Delete %0 files permanently from disk, or just remove the files from the entry? " + + "Pressing Delete will delete the files permanently from disk.", toBeDeleted.size()); + } else { + Optional file = toBeDeleted.get(0).getFile().findIn(databaseContext, preferences.getFilePreferences()); + + if (file.isPresent()) { + dialogTitle = Localization.lang("Delete '%0'", file.get().getFileName().toString()); + dialogContent = Localization.lang("Delete '%0' permanently from disk, or just remove the file from the entry? " + + "Pressing Delete will delete the file permanently from disk.", file.get().toString()); + } else { + dialogService.notify(Localization.lang("Error accessing file '%0'.", toBeDeleted.get(0).getFile().getLink())); + return; + } + } + + ButtonType removeFromEntry = new ButtonType(Localization.lang("Remove from entry"), ButtonBar.ButtonData.YES); + ButtonType deleteFromEntry = new ButtonType(Localization.lang("Delete from disk")); + Optional buttonType = dialogService.showCustomButtonDialogAndWait(Alert.AlertType.INFORMATION, + dialogTitle, dialogContent, removeFromEntry, deleteFromEntry, ButtonType.CANCEL); + + if (buttonType.isPresent()) { + if (buttonType.get().equals(removeFromEntry)) { + deleteFiles(toBeDeleted, false); + } + + if (buttonType.get().equals(deleteFromEntry)) { + deleteFiles(toBeDeleted, true); + } + } + } + + /** + * Deletes the files from the entry and optionally from disk. + * + * @param toBeDeleted the files to be deleted + * @param deleteFromDisk if true, the files are deleted from disk, otherwise they are only removed from the entry + */ + private void deleteFiles(List toBeDeleted, boolean deleteFromDisk) { + for (LinkedFileViewModel fileViewModel : toBeDeleted) { + if (fileViewModel.getFile().isOnlineLink()) { + viewModel.removeFileLink(fileViewModel); + } else { + if (deleteFromDisk) { + deleteFileFromDisk(fileViewModel); + } + viewModel.getFiles().remove(fileViewModel); + } + } + } + + /** + * Deletes the file from disk without asking the user for confirmation. + * + * @param fileViewModel the file to be deleted + */ + public void deleteFileFromDisk(LinkedFileViewModel fileViewModel) { + LinkedFile linkedFile = fileViewModel.getFile(); + + Optional file = linkedFile.findIn(databaseContext, preferences.getFilePreferences()); + + if (file.isEmpty()) { + LOGGER.warn("Could not find file {}", linkedFile.getLink()); + } + + if (file.isPresent()) { + try { + Files.delete(file.get()); + } catch ( + IOException ex) { + dialogService.showErrorDialogAndWait(Localization.lang("Cannot delete file"), Localization.lang("File permission error")); + LOGGER.warn("File permission error while deleting: {}", linkedFile, ex); + } + } else { + dialogService.notify(Localization.lang("Error accessing file '%0'.", linkedFile.getLink())); + } + } +} diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 035b291a4fd..c772cf06f87 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -2539,3 +2539,8 @@ Please\ select\ a\ valid\ main\ directory\ under=Please select a valid main dire Search\ from\ history...=Search from history... your\ search\ history\ is\ empty=your search history is empty Clear\ history =Clear history + +Delete\ %0\ files=Delete %0 files +Delete\ %0\ files\ permanently\ from\ disk,\ or\ just\ remove\ the\ files\ from\ the\ entry?\ Pressing\ Delete\ will\ delete\ the\ files\ permanently\ from\ disk.=Delete %0 files permanently from disk, or just remove the files from the entry? Pressing Delete will delete the files permanently from disk. +Error\ accessing\ file\ '%0'.=Error accessing file '%0'. +This\ operation\ requires\ selected\ linked\ files.=This operation requires selected linked files.