Skip to content

Commit 1a1cc24

Browse files
authored
Fix #13269 : support multi-file import across different formats (#13271)
* fix: multi-file imports * remove comment * add null check before accessing tab database context * fix NPE when importing into current library if no library tab is open * added support for multi-file import through the 'Import into current/new library' dialog * remove extra blank line * update FileDialogConfiguration with selected extension filter after multi-file selection * fix importer format detection * add comment to explain tab nullcheck
1 parent 02620fa commit 1a1cc24

File tree

3 files changed

+42
-16
lines changed

3 files changed

+42
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
3636
- We added a new `jabkit` command `pseudonymize` to pseudonymize the library. [#13109](https://github.com/JabRef/jabref/issues/13109)
3737
- We added functionality to focus running instance when trying to start a second instance. [#13129](https://github.com/JabRef/jabref/issues/13129)
3838
- 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)
39+
- We added support for multi-file import across different formats. [#13269](https://github.com/JabRef/jabref/issues/13269)
3940

4041
### Changed
4142

jabgui/src/main/java/org/jabref/gui/JabRefDialogService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ public Optional<Path> showDirectorySelectionDialog(DirectoryDialogConfiguration
462462
public List<Path> showFileOpenDialogAndGetMultipleFiles(FileDialogConfiguration fileDialogConfiguration) {
463463
FileChooser chooser = getConfiguredFileChooser(fileDialogConfiguration);
464464
List<File> files = chooser.showOpenMultipleDialog(mainWindow);
465+
Optional.ofNullable(chooser.getSelectedExtensionFilter()).ifPresent(fileDialogConfiguration::setSelectedExtensionFilter);
465466
return files != null ? files.stream().map(File::toPath).toList() : List.of();
466467
}
467468

jabgui/src/main/java/org/jabref/gui/importer/ImportCommand.java

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import javafx.stage.FileChooser;
1212

1313
import org.jabref.gui.DialogService;
14+
import org.jabref.gui.LibraryTab;
1415
import org.jabref.gui.LibraryTabContainer;
1516
import org.jabref.gui.StateManager;
1617
import org.jabref.gui.actions.SimpleCommand;
@@ -89,27 +90,50 @@ public void execute() {
8990
.addExtensionFilter(FileFilterConverter.importerToExtensionFilter(importers))
9091
.withInitialDirectory(preferences.getImporterPreferences().getImportWorkingDirectory())
9192
.build();
92-
dialogService.showFileOpenDialog(fileDialogConfiguration)
93-
.ifPresent(path -> importSingleFile(path, importers, fileDialogConfiguration.getSelectedExtensionFilter()));
94-
}
9593

96-
private void importSingleFile(Path file, SortedSet<Importer> importers, FileChooser.ExtensionFilter selectedExtensionFilter) {
97-
if (!Files.exists(file)) {
98-
dialogService.showErrorDialogAndWait(Localization.lang("Import"),
99-
Localization.lang("File not found") + ": '" + file.getFileName() + "'.");
94+
List<Path> selectedFiles = dialogService.showFileOpenDialogAndGetMultipleFiles(fileDialogConfiguration);
95+
96+
if (selectedFiles.isEmpty()) {
97+
return; // User cancelled or no files selected
98+
}
99+
100+
importMultipleFiles(selectedFiles, importers, fileDialogConfiguration.getSelectedExtensionFilter());
101+
}
100102

101-
return;
103+
private void importMultipleFiles(List<Path> files, SortedSet<Importer> importers, FileChooser.ExtensionFilter selectedExtensionFilter) {
104+
for (Path file : files) {
105+
if (!Files.exists(file)) {
106+
dialogService.showErrorDialogAndWait(Localization.lang("Import"),
107+
Localization.lang("File not found") + ": '" + file.getFileName() + "'.");
108+
return;
109+
}
102110
}
103111

104-
if (selectedExtensionFilter == FileFilterConverter.ANY_FILE || "Available import formats".equals(selectedExtensionFilter.getDescription())) {
105-
selectedExtensionFilter = FileFilterConverter.determineExtensionFilter(file);
112+
BackgroundTask<ParserResult> task;
113+
Optional<Importer> format;
114+
115+
boolean isGeneralFilter = selectedExtensionFilter == FileFilterConverter.ANY_FILE
116+
|| "Available import formats".equals(selectedExtensionFilter.getDescription());
117+
118+
if (!isGeneralFilter) {
119+
// User picked a specific format
120+
format = FileFilterConverter.getImporter(selectedExtensionFilter, importers);
121+
} else if (files.size() == 1) {
122+
// Infer if only one file and no specific filter
123+
selectedExtensionFilter = FileFilterConverter.determineExtensionFilter(files.getFirst());
124+
format = FileFilterConverter.getImporter(selectedExtensionFilter, importers);
125+
} else {
126+
format = Optional.empty();
106127
}
107128

108-
Optional<Importer> format = FileFilterConverter.getImporter(selectedExtensionFilter, importers);
109-
BackgroundTask<ParserResult> task = BackgroundTask.wrap(
110-
() -> doImport(List.of(file), format.orElse(null)));
129+
task = BackgroundTask.wrap(() -> doImport(files, format.orElse(null)));
130+
131+
LibraryTab tab = tabContainer.getCurrentLibraryTab();
111132

112-
if (importMethod == ImportMethod.AS_NEW) {
133+
// If there is no open library tab, we fall back to importing as new
134+
// This prevents a crash in case the user selects "Import into current library"
135+
// while no library is currently open.
136+
if (importMethod == ImportMethod.AS_NEW || tab == null) {
113137
task.onSuccess(parserResult -> {
114138
tabContainer.addTab(parserResult.getDatabaseContext(), true);
115139
dialogService.notify(Localization.lang("%0 entry(s) imported", parserResult.getDatabase().getEntries().size()));
@@ -120,13 +144,13 @@ private void importSingleFile(Path file, SortedSet<Importer> importers, FileChoo
120144
})
121145
.executeWith(taskExecutor);
122146
} else {
123-
ImportEntriesDialog dialog = new ImportEntriesDialog(tabContainer.getCurrentLibraryTab().getBibDatabaseContext(), task);
147+
ImportEntriesDialog dialog = new ImportEntriesDialog(tab.getBibDatabaseContext(), task);
124148
dialog.setTitle(Localization.lang("Import"));
125149
dialogService.showCustomDialogAndWait(dialog);
126150
}
127151

128152
// Set last working dir for import
129-
preferences.getImporterPreferences().setImportWorkingDirectory(file.getParent());
153+
preferences.getImporterPreferences().setImportWorkingDirectory(files.getLast().getParent());
130154
}
131155

132156
/**

0 commit comments

Comments
 (0)