Skip to content

Fix #13269 : support multi-file import across different formats #13271

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We added a new `jabkit` command `pseudonymize` to pseudonymize the library. [#13109](https://github.com/JabRef/jabref/issues/13109)
- We added functionality to focus running instance when trying to start a second instance. [#13129](https://github.com/JabRef/jabref/issues/13129)
- We added a new setting in the 'Entry Editor' preferences to hide the 'File Annotations' tab when no annotations are available. [#13143](https://github.com/JabRef/jabref/issues/13143)
- We added support for multi-file import across different formats. [#13269](https://github.com/JabRef/jabref/issues/13269)

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ public Optional<Path> showDirectorySelectionDialog(DirectoryDialogConfiguration
public List<Path> showFileOpenDialogAndGetMultipleFiles(FileDialogConfiguration fileDialogConfiguration) {
FileChooser chooser = getConfiguredFileChooser(fileDialogConfiguration);
List<File> files = chooser.showOpenMultipleDialog(mainWindow);
Optional.ofNullable(chooser.getSelectedExtensionFilter()).ifPresent(fileDialogConfiguration::setSelectedExtensionFilter);
return files != null ? files.stream().map(File::toPath).toList() : List.of();
}

Expand Down
56 changes: 40 additions & 16 deletions jabgui/src/main/java/org/jabref/gui/importer/ImportCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import javafx.stage.FileChooser;

import org.jabref.gui.DialogService;
import org.jabref.gui.LibraryTab;
import org.jabref.gui.LibraryTabContainer;
import org.jabref.gui.StateManager;
import org.jabref.gui.actions.SimpleCommand;
Expand Down Expand Up @@ -89,27 +90,50 @@ public void execute() {
.addExtensionFilter(FileFilterConverter.importerToExtensionFilter(importers))
.withInitialDirectory(preferences.getImporterPreferences().getImportWorkingDirectory())
.build();
dialogService.showFileOpenDialog(fileDialogConfiguration)
.ifPresent(path -> importSingleFile(path, importers, fileDialogConfiguration.getSelectedExtensionFilter()));
}

private void importSingleFile(Path file, SortedSet<Importer> importers, FileChooser.ExtensionFilter selectedExtensionFilter) {
if (!Files.exists(file)) {
dialogService.showErrorDialogAndWait(Localization.lang("Import"),
Localization.lang("File not found") + ": '" + file.getFileName() + "'.");
List<Path> selectedFiles = dialogService.showFileOpenDialogAndGetMultipleFiles(fileDialogConfiguration);

if (selectedFiles.isEmpty()) {
return; // User cancelled or no files selected
}

importMultipleFiles(selectedFiles, importers, fileDialogConfiguration.getSelectedExtensionFilter());
}

return;
private void importMultipleFiles(List<Path> files, SortedSet<Importer> importers, FileChooser.ExtensionFilter selectedExtensionFilter) {
for (Path file : files) {
if (!Files.exists(file)) {
dialogService.showErrorDialogAndWait(Localization.lang("Import"),
Localization.lang("File not found") + ": '" + file.getFileName() + "'.");
return;
}
}

if (selectedExtensionFilter == FileFilterConverter.ANY_FILE || "Available import formats".equals(selectedExtensionFilter.getDescription())) {
selectedExtensionFilter = FileFilterConverter.determineExtensionFilter(file);
BackgroundTask<ParserResult> task;
Optional<Importer> format;

boolean isGeneralFilter = selectedExtensionFilter == FileFilterConverter.ANY_FILE
|| "Available import formats".equals(selectedExtensionFilter.getDescription());

if (!isGeneralFilter) {
// User picked a specific format
format = FileFilterConverter.getImporter(selectedExtensionFilter, importers);
} else if (files.size() == 1) {
// Infer if only one file and no specific filter
selectedExtensionFilter = FileFilterConverter.determineExtensionFilter(files.getFirst());
format = FileFilterConverter.getImporter(selectedExtensionFilter, importers);
} else {
format = Optional.empty();
}

Optional<Importer> format = FileFilterConverter.getImporter(selectedExtensionFilter, importers);
BackgroundTask<ParserResult> task = BackgroundTask.wrap(
() -> doImport(List.of(file), format.orElse(null)));
task = BackgroundTask.wrap(() -> doImport(files, format.orElse(null)));

LibraryTab tab = tabContainer.getCurrentLibraryTab();

if (importMethod == ImportMethod.AS_NEW) {
// If there is no open library tab, we fall back to importing as new
// This prevents a crash in case the user selects "Import into current library"
// while no library is currently open.
if (importMethod == ImportMethod.AS_NEW || tab == null) {
Copy link
Member

Choose a reason for hiding this comment

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

Please add a comment why the second condition works, as otherwise just by reading code this seems like a logic proxy/hacky way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"Import into current library" can still be enabled if there are no libraries open. Is that intentional ? If not, then comment should mention a bug report ?

task.onSuccess(parserResult -> {
tabContainer.addTab(parserResult.getDatabaseContext(), true);
dialogService.notify(Localization.lang("%0 entry(s) imported", parserResult.getDatabase().getEntries().size()));
Expand All @@ -120,13 +144,13 @@ private void importSingleFile(Path file, SortedSet<Importer> importers, FileChoo
})
.executeWith(taskExecutor);
} else {
ImportEntriesDialog dialog = new ImportEntriesDialog(tabContainer.getCurrentLibraryTab().getBibDatabaseContext(), task);
ImportEntriesDialog dialog = new ImportEntriesDialog(tab.getBibDatabaseContext(), task);
dialog.setTitle(Localization.lang("Import"));
dialogService.showCustomDialogAndWait(dialog);
}

// Set last working dir for import
preferences.getImporterPreferences().setImportWorkingDirectory(file.getParent());
preferences.getImporterPreferences().setImportWorkingDirectory(files.getLast().getParent());
}

/**
Expand Down
Loading