From f18bf1f24310a13c682d240b93f6d0bda6ff560b Mon Sep 17 00:00:00 2001 From: Christoph Date: Sat, 3 Feb 2024 19:06:19 +0100 Subject: [PATCH 01/14] Fix secrents presence chcek --- .github/workflows/deployment-arm64.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deployment-arm64.yml b/.github/workflows/deployment-arm64.yml index dad4aaa49d8..666ac8e14cd 100644 --- a/.github/workflows/deployment-arm64.yml +++ b/.github/workflows/deployment-arm64.yml @@ -48,9 +48,13 @@ jobs: id: checksecrets shell: bash run: | - [ -n "$BUILDJABREFPRIVATEKEY" ] || exit 1 + if [ "$BUILDJABREFPRIVATEKEY" == "" ]; then + echo "secretspresent=NO" >> $GITHUB_OUTPUT + else + echo "secretspresent=YES" >> $GITHUB_OUTPUT + fi env: - BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} + BUILDJABREFPRIVATEKEY: ${{ secrets.buildJabRefPrivateKey }} - name: Fetch all history for all tags and branches uses: actions/checkout@v4 with: From 46412dc4add67d69a5cdbd5dbece6b287c7e59a9 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sat, 3 Feb 2024 19:48:19 +0100 Subject: [PATCH 02/14] Update deployment-arm64.yml fix outputs --- .github/workflows/deployment-arm64.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/deployment-arm64.yml b/.github/workflows/deployment-arm64.yml index 666ac8e14cd..260272637ac 100644 --- a/.github/workflows/deployment-arm64.yml +++ b/.github/workflows/deployment-arm64.yml @@ -42,6 +42,10 @@ jobs: displayName: macOS (Arm64) suffix: '_arm64' runs-on: ${{ matrix.os }} + outputs: + major: ${{ steps.gitversion.outputs.Major }} + minor: ${{ steps.gitversion.outputs.Minor }} + branchname: ${{ steps.gitversion.outputs.branchName }} name: Create installer and portable version for ${{ matrix.displayName }} steps: - name: Check secrets presence From c261185e88420ece8512f29e5d5398b59c8d3346 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sat, 3 Feb 2024 20:08:22 +0100 Subject: [PATCH 03/14] Fix closing of JabRef in case of issues with index writer (#10840) * Fix closing of JabRef in case of issues with index writer Uncaught exception occurred in Thread[#60,JavaFX Application Thread,5,main] java.lang.NullPointerException: Cannot invoke "org.apache.lucene.index.IndexWriter.close()" because "this.indexWriter" is null at org.jabref@100.0.0/org.jabref.logic.pdf.search.PdfIndexer.close(PdfIndexer.java:299) at org.jabref@100.0.0/org.jabref.logic.pdf.search.PdfIndexerManager.shutdownIndexer(PdfIndexerManager.java:74) at org.jabref@100.0.0/org.jabref.gui.LibraryTab.onClosed(LibraryTab.java:792) * NPEs are caught at onClosed * Handle absent index writer * Try to handle null directory * Discard changes to src/main/java/org/jabref/gui/LibraryTab.java * Use java.io.tempDir * Add comments and annotations --- .../jabref/logic/pdf/search/PdfIndexer.java | 77 +++++++++++++++---- .../jabref/logic/pdf/search/PdfSearcher.java | 9 ++- .../model/database/BibDatabaseContext.java | 2 + 3 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/jabref/logic/pdf/search/PdfIndexer.java b/src/main/java/org/jabref/logic/pdf/search/PdfIndexer.java index d28f33b6507..7b66b1c62ca 100644 --- a/src/main/java/org/jabref/logic/pdf/search/PdfIndexer.java +++ b/src/main/java/org/jabref/logic/pdf/search/PdfIndexer.java @@ -1,6 +1,7 @@ package org.jabref.logic.pdf.search; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; @@ -34,6 +35,8 @@ import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.Directory; import org.apache.lucene.store.NIOFSDirectory; +import org.jooq.lambda.Unchecked; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,16 +48,34 @@ public class PdfIndexer { private static final Logger LOGGER = LoggerFactory.getLogger(PdfIndexer.class); @VisibleForTesting + @Nullable // null might happen if lock is held by another JabRef instance IndexWriter indexWriter; private final BibDatabaseContext databaseContext; + private final FilePreferences filePreferences; + + @Nullable private final Directory indexDirectory; + private IndexReader reader; private PdfIndexer(BibDatabaseContext databaseContext, Directory indexDirectory, FilePreferences filePreferences) { this.databaseContext = databaseContext; - this.indexDirectory = indexDirectory; + if (indexDirectory == null) { + // FIXME: This should never happen, but was reported at https://github.com/JabRef/jabref/issues/10781. + String tmpDir = System.getProperty("java.io.tmpdir"); + LOGGER.info("Index directory must not be null. Falling back to {}", tmpDir); + Directory tmpIndexDirectory = null; + try { + tmpIndexDirectory = new NIOFSDirectory(Path.of(tmpDir)); + } catch (IOException e) { + LOGGER.info("Could not use {}. Indexing unavailable.", tmpDir, e); + } + this.indexDirectory = tmpIndexDirectory; + } else { + this.indexDirectory = indexDirectory; + } this.filePreferences = filePreferences; } @@ -69,7 +90,6 @@ public static PdfIndexer of(BibDatabaseContext databaseContext, Path indexDirect /** * Method is public, because DatabaseSearcherWithBibFilesTest resides in another package */ - @VisibleForTesting public static PdfIndexer of(BibDatabaseContext databaseContext, FilePreferences filePreferences) throws IOException { return new PdfIndexer(databaseContext, new NIOFSDirectory(databaseContext.getFulltextIndexPath()), filePreferences); } @@ -79,6 +99,10 @@ public static PdfIndexer of(BibDatabaseContext databaseContext, FilePreferences * Any previous state of the Lucene search is deleted. */ public void createIndex() { + if (indexDirectory == null) { + LOGGER.info("Index directory must not be null. Returning."); + return; + } LOGGER.debug("Creating new index for directory {}.", indexDirectory); initializeIndexWriterAndReader(IndexWriterConfig.OpenMode.CREATE); } @@ -86,7 +110,7 @@ public void createIndex() { /** * Needs to be accessed by {@link PdfSearcher} */ - IndexWriter getIndexWriter() { + Optional getIndexWriter() { LOGGER.trace("Getting the index writer"); if (indexWriter == null) { LOGGER.trace("Initializing the index writer"); @@ -94,10 +118,14 @@ IndexWriter getIndexWriter() { } else { LOGGER.trace("Using existing index writer"); } - return indexWriter; + return Optional.ofNullable(indexWriter); } private void initializeIndexWriterAndReader(IndexWriterConfig.OpenMode mode) { + if (indexDirectory == null) { + LOGGER.info("Index directory must not be null. Returning."); + return; + } try { indexWriter = new IndexWriter( indexDirectory, @@ -105,6 +133,12 @@ private void initializeIndexWriterAndReader(IndexWriterConfig.OpenMode mode) { new EnglishStemAnalyzer()).setOpenMode(mode)); } catch (IOException e) { LOGGER.error("Could not initialize the IndexWriter", e); + // FIXME: This can also happen if another instance of JabRef is launched in parallel. + // We could implement a read-only access to the index in this case. + // This requires a major rewrite of the code, though. + // Accessing the index using a permanent writer object is (much) faster than always + // closing and opening the writer and reader on demand. + return; } try { reader = DirectoryReader.open(indexWriter); @@ -171,8 +205,8 @@ public void addToIndex(BibEntry entry, Collection linkedFiles, boole private void doCommit() { try { - getIndexWriter().commit(); - } catch (IOException e) { + getIndexWriter().ifPresent(Unchecked.consumer(writer -> writer.commit())); + } catch (UncheckedIOException e) { LOGGER.warn("Could not commit changes to the index.", e); } } @@ -184,9 +218,11 @@ private void doCommit() { */ public void removeFromIndex(String linkedFilePath) { try { - getIndexWriter().deleteDocuments(new Term(SearchFieldConstants.PATH, linkedFilePath)); - getIndexWriter().commit(); - } catch (IOException e) { + getIndexWriter().ifPresent(Unchecked.consumer(writer -> { + writer.deleteDocuments(new Term(SearchFieldConstants.PATH, linkedFilePath)); + writer.commit(); + })); + } catch (UncheckedIOException e) { LOGGER.debug("Could not remove document {} from the index.", linkedFilePath, e); } } @@ -261,14 +297,16 @@ private void addToIndex(BibEntry entry, LinkedFile linkedFile, boolean shouldCom // If no document was found, add the new one Optional> pages = new DocumentReader(entry, filePreferences).readLinkedPdf(this.databaseContext, linkedFile); if (pages.isPresent()) { - getIndexWriter().addDocuments(pages.get()); - if (shouldCommit) { - getIndexWriter().commit(); - } + getIndexWriter().ifPresent(Unchecked.consumer(writer -> { + writer.addDocuments(pages.get()); + if (shouldCommit) { + writer.commit(); + } + })); } else { LOGGER.debug("No content found in file {}", linkedFile.getLink()); } - } catch (IOException e) { + } catch (UncheckedIOException | IOException e) { LOGGER.warn("Could not add document {} to the index.", linkedFile.getLink(), e); } } @@ -280,7 +318,12 @@ private void addToIndex(BibEntry entry, LinkedFile linkedFile, boolean shouldCom */ public Set getListOfFilePaths() { Set paths = new HashSet<>(); - try (IndexReader reader = DirectoryReader.open(getIndexWriter())) { + Optional optionalIndexWriter = getIndexWriter(); + if (optionalIndexWriter.isEmpty()) { + LOGGER.debug("IndexWriter is empty. Returning empty list."); + return paths; + } + try (IndexReader reader = DirectoryReader.open(optionalIndexWriter.get())) { IndexSearcher searcher = new IndexSearcher(reader); MatchAllDocsQuery query = new MatchAllDocsQuery(); TopDocs allDocs = searcher.search(query, Integer.MAX_VALUE); @@ -296,6 +339,10 @@ public Set getListOfFilePaths() { } public void close() throws IOException { + if (indexWriter == null) { + LOGGER.debug("IndexWriter is null."); + return; + } indexWriter.close(); } } diff --git a/src/main/java/org/jabref/logic/pdf/search/PdfSearcher.java b/src/main/java/org/jabref/logic/pdf/search/PdfSearcher.java index 3906341ecb5..40acc97f8af 100644 --- a/src/main/java/org/jabref/logic/pdf/search/PdfSearcher.java +++ b/src/main/java/org/jabref/logic/pdf/search/PdfSearcher.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import org.jabref.gui.LibraryTab; import org.jabref.model.pdf.search.EnglishStemAnalyzer; @@ -13,6 +14,7 @@ import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; import org.apache.lucene.queryparser.classic.MultiFieldQueryParser; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.search.IndexSearcher; @@ -57,7 +59,12 @@ public PdfSearchResults search(final String searchString, final int maxHits) thr List resultDocs = new ArrayList<>(); // We need to point the DirectoryReader to the indexer, because we get errors otherwise // Hint from https://stackoverflow.com/a/63673753/873282. - try (IndexReader reader = DirectoryReader.open(indexer.getIndexWriter())) { + Optional optionalIndexWriter = indexer.getIndexWriter(); + if (optionalIndexWriter.isEmpty()) { + LOGGER.info("No index writer present, returning empty result set."); + return new PdfSearchResults(); + } + try (IndexReader reader = DirectoryReader.open(optionalIndexWriter.get())) { Query query = new MultiFieldQueryParser(PDF_FIELDS, englishStemAnalyzer).parse(searchString); IndexSearcher searcher = new IndexSearcher(reader); TopDocs results = searcher.search(query, maxHits); diff --git a/src/main/java/org/jabref/model/database/BibDatabaseContext.java b/src/main/java/org/jabref/model/database/BibDatabaseContext.java index 7f4fce2159a..3496fde8194 100644 --- a/src/main/java/org/jabref/model/database/BibDatabaseContext.java +++ b/src/main/java/org/jabref/model/database/BibDatabaseContext.java @@ -22,6 +22,7 @@ import org.jabref.model.study.Study; import org.jabref.preferences.FilePreferences; +import org.jspecify.annotations.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -241,6 +242,7 @@ public List getEntries() { /** * @return The path to store the lucene index files. One directory for each library. */ + @NonNull public Path getFulltextIndexPath() { Path appData = OS.getNativeDesktop().getFulltextIndexBaseDirectory(); Path indexPath; From 1c47af42c2e89fcc10cc61b3ae3e0b7d7d73a331 Mon Sep 17 00:00:00 2001 From: u7282852 <141634726+u7282852@users.noreply.github.com> Date: Mon, 5 Feb 2024 07:13:44 +1100 Subject: [PATCH 04/14] Jump to entry from cli (#10578) * Add command line skeleton for jump to entry key option * Add basic logic linking new command line option with gui * Remove small bug that opens the wrong library with multiple entries * Add small feedback changes * Fixed cli by renaming all method to jumpToEntry * Fixed bug that focused libraries of recently opened files over command line arguments * Add CHANGELOG.md entry and localization entries * Update CHANGELOG.md Co-authored-by: Oliver Kopp * Update CITEKEY arg name to CITATIONKEY Co-authored-by: Oliver Kopp * Add comment explaining a previous change * Introduced UiCommands to replace clumsy gui interface * Fixed merge error * Fixed inconsistencies adding library, converted BackgroundProgress to record * Implemented selection logic * wip * Fixed missing call to executor service * Fixed waiting for loading Co-authored-by: Oliver Kopp * Refactored DialogService * Added fail message * Reworded database to library * Rewrite * Fix variable name * Add search-first-in-focused-tab * Add note on architecture smell * Add @implNote * Add some @Nullable annotation * Fix localization * Fix handling of port changes of the remote listener * Add logging of remote messages * Output path of backup file to log * Adding some debug * Re-add output at multiple instances * Working * Additional logger statement * Add more LOGGER.trace * Convert change listener to binding * Add some debug messages * Add more LOGGERs * Large libraries work! * Revert "Convert change listener to binding" This reverts commit a39742f54896a84e44f00ca03f7feb1fe73ab527. * Some more traces * Add more logging statements * Extract method * Fix jump on loading of a file * Use streams instead of for loop --------- Co-authored-by: Oliver Kopp Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> --- CHANGELOG.md | 1 + .../org/jabref/cli/ArgumentProcessor.java | 60 ++++--- src/main/java/org/jabref/cli/JabRefCLI.java | 18 ++- src/main/java/org/jabref/cli/Launcher.java | 19 ++- .../java/org/jabref/gui/DialogService.java | 14 +- .../org/jabref/gui/JabRefDialogService.java | 15 +- src/main/java/org/jabref/gui/JabRefFrame.java | 147 +++++++++++++++--- src/main/java/org/jabref/gui/JabRefGUI.java | 22 ++- src/main/java/org/jabref/gui/LibraryTab.java | 30 +++- .../org/jabref/gui/LibraryTabContainer.java | 5 + .../java/org/jabref/gui/MainApplication.java | 13 +- ...alog.java => ProcessingLibraryDialog.java} | 18 ++- .../gui/autosaveandbackup/BackupManager.java | 6 +- .../CitationRelationsTab.java | 2 +- .../LinkedEntriesEditorViewModel.java | 2 +- .../importer/actions/OpenDatabaseAction.java | 6 +- .../general/GeneralTabViewModel.java | 4 +- .../jabref/gui/remote/CLIMessageHandler.java | 14 +- .../gui/shared/SharedDatabaseUIManager.java | 33 ++-- .../org/jabref/gui/util/BackgroundTask.java | 38 ++--- .../jabref/gui/util/DefaultTaskExecutor.java | 2 +- src/main/java/org/jabref/logic/UiCommand.java | 13 ++ .../org/jabref/logic/remote/Protocol.java | 8 +- .../logic/remote/client/RemoteClient.java | 4 +- .../remote/server/RemoteListenerServer.java | 1 - .../server/RemoteListenerServerThread.java | 2 +- .../remote/server/RemoteMessageHandler.java | 1 - src/main/resources/l10n/JabRef_en.properties | 59 +++---- 28 files changed, 383 insertions(+), 174 deletions(-) rename src/main/java/org/jabref/gui/{WaitForSaveFinishedDialog.java => ProcessingLibraryDialog.java} (58%) create mode 100644 src/main/java/org/jabref/logic/UiCommand.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c83a4906cc2..a7119a590bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added a fetcher for [ISIDORE](https://isidore.science/), simply paste in the link into the text field or the last 6 digits in the link that identify that paper. [#10423](https://github.com/JabRef/jabref/issues/10423) - When importing entries form the "Citation relations" tab, the field [cites](https://docs.jabref.org/advanced/entryeditor/entrylinks) is now filled according to the relationship between the entries. [#10572](https://github.com/JabRef/jabref/pull/10752) - We added a new group icon column to the main table showing the icons of the entry's groups. [#10801](https://github.com/JabRef/jabref/pull/10801) +- We added ability to jump to an entry in the command line using `-j CITATIONKEY`. [koppor#540](https://github.com/koppor/jabref/issues/540) - We added a new boolean to the style files for Openoffice/Libreoffice integration to switch between ZERO_WIDTH_SPACE (default) and no space. [#10843](https://github.com/JabRef/jabref/pull/10843) ### Changed diff --git a/src/main/java/org/jabref/cli/ArgumentProcessor.java b/src/main/java/org/jabref/cli/ArgumentProcessor.java index ccd49977000..35fd85483a5 100644 --- a/src/main/java/org/jabref/cli/ArgumentProcessor.java +++ b/src/main/java/org/jabref/cli/ArgumentProcessor.java @@ -16,6 +16,7 @@ import org.jabref.gui.externalfiles.AutoSetFileLinksUtil; import org.jabref.gui.undo.NamedCompound; import org.jabref.logic.JabRefException; +import org.jabref.logic.UiCommand; import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.citationkeypattern.CitationKeyGenerator; import org.jabref.logic.exporter.AtomicFileWriter; @@ -64,28 +65,31 @@ import org.slf4j.LoggerFactory; public class ArgumentProcessor { - private static final Logger LOGGER = LoggerFactory.getLogger(ArgumentProcessor.class); - private final JabRefCLI cli; - // Written once by processArguments() - private List parserResults = List.of(); + public enum Mode { INITIAL_START, REMOTE_START } + + private final JabRefCLI cli; private final Mode startupMode; + private final PreferencesService preferencesService; private final FileUpdateMonitor fileUpdateMonitor; private final BibEntryTypesManager entryTypesManager; - private boolean noGUINeeded; + + private boolean guiNeeded; + private final List uiCommands = new ArrayList<>(); /** * First call the constructor, then call {@link #processArguments()}. - * Afterward, you can access the {@link #getParserResults()} and other getters. + * Afterward, you can access the {@link #getUiCommands()}. */ public ArgumentProcessor(String[] args, Mode startupMode, PreferencesService preferencesService, FileUpdateMonitor fileUpdateMonitor, - BibEntryTypesManager entryTypesManager) throws org.apache.commons.cli.ParseException { + BibEntryTypesManager entryTypesManager) + throws org.apache.commons.cli.ParseException { this.cli = new JabRefCLI(args); this.startupMode = startupMode; this.preferencesService = preferencesService; @@ -121,6 +125,7 @@ private Optional importBibtexToOpenBase(String argument, ImportFor } private Optional importFile(String argument) { + LOGGER.debug("Importing file {}", argument); String[] data = argument.split(","); String address = data[0]; @@ -185,22 +190,21 @@ private Optional importFile(Path file, String importFormat) { } } - public List getParserResults() { - return parserResults; - } - public void processArguments() { + uiCommands.clear(); + if ((startupMode == Mode.INITIAL_START) && cli.isShowVersion()) { cli.displayVersion(); } if ((startupMode == Mode.INITIAL_START) && cli.isHelp()) { JabRefCLI.printUsage(preferencesService); - noGUINeeded = true; - this.parserResults = Collections.emptyList(); + guiNeeded = false; return; } + guiNeeded = true; + // Check if we should reset all preferences to default values: if (cli.isPreferencesReset()) { resetPreferences(cli.getPreferencesReset()); @@ -211,7 +215,6 @@ public void processArguments() { importPreferences(); } - // List to put imported/loaded database(s) in. List loaded = importAndOpenFiles(); if (!cli.isBlank() && cli.isFetcherEngine()) { @@ -221,7 +224,6 @@ public void processArguments() { if (cli.isExportMatches()) { if (!loaded.isEmpty()) { if (!exportMatches(loaded)) { - this.parserResults = Collections.emptyList(); return; } } else { @@ -277,7 +279,17 @@ public void processArguments() { doAuxImport(loaded); } - this.parserResults = loaded; + if (cli.isBlank()) { + uiCommands.add(new UiCommand.BlankWorkspace()); + } + + if (!cli.isBlank() && cli.isJumpToKey()) { + uiCommands.add(new UiCommand.JumpToEntryKey(cli.getJumpToKey())); + } + + if (!cli.isBlank() && !loaded.isEmpty()) { + uiCommands.add(new UiCommand.OpenDatabases(loaded)); + } } private void writeMetadataToPdf(List loaded, @@ -460,7 +472,7 @@ private boolean exportMatches(List loaded) { default -> { System.err.println(Localization.lang("Output file missing").concat(". \n \t ") .concat(Localization.lang("Usage")).concat(": ") + JabRefCLI.getExportMatchesSyntax()); - noGUINeeded = true; + guiNeeded = false; return false; } } @@ -510,6 +522,9 @@ private void doAuxImport(List loaded) { } } + /** + * @return List of opened files (could be .bib, but also other formats). May also contain error results. + */ private List importAndOpenFiles() { List loaded = new ArrayList<>(); List toImport = new ArrayList<>(); @@ -527,6 +542,7 @@ private List importAndOpenFiles() { Path.of(aLeftOver), preferencesService.getImportFormatPreferences(), fileUpdateMonitor); + // In contrast to org.jabref.gui.LibraryTab.onDatabaseLoadingSucceed, we do not execute OpenDatabaseAction.performPostOpenActions(result, dialogService); } catch (IOException ex) { pr = ParserResult.fromError(ex); LOGGER.error("Error opening file '{}'", aLeftOver, ex); @@ -773,15 +789,11 @@ private Optional fetch(String fetchCommand) { } } - public boolean isBlank() { - return cli.isBlank(); - } - public boolean shouldShutDown() { - return cli.isDisableGui() || cli.isShowVersion() || noGUINeeded; + return cli.isDisableGui() || cli.isShowVersion() || !guiNeeded; } - public enum Mode { - INITIAL_START, REMOTE_START + public List getUiCommands() { + return uiCommands; } } diff --git a/src/main/java/org/jabref/cli/JabRefCLI.java b/src/main/java/org/jabref/cli/JabRefCLI.java index 9b37022f23a..746c9529608 100644 --- a/src/main/java/org/jabref/cli/JabRefCLI.java +++ b/src/main/java/org/jabref/cli/JabRefCLI.java @@ -169,6 +169,14 @@ public String getWriteMetadatatoPdf() { cl.hasOption("embeddBibfileInPdf") ? cl.getOptionValue("embeddBibfileInPdf") : null; } + public String getJumpToKey() { + return cl.getOptionValue("jumpToKey"); + } + + public boolean isJumpToKey() { + return cl.hasOption("jumpToKey"); + } + private static Options getOptions() { Options options = new Options(); @@ -275,7 +283,7 @@ private static Options getOptions() { options.addOption(Option .builder() .longOpt("embeddBibfileInPdf") - .desc("%s: '%s'".formatted(Localization.lang("Embed BibTeXEntry in PDF."), "-w pathToMyOwnPaper.pdf")) + .desc("%s: '%s'".formatted(Localization.lang("Embed BibTeX as attached file in PDF."), "-w pathToMyOwnPaper.pdf")) .hasArg() .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") .build()); @@ -288,6 +296,14 @@ private static Options getOptions() { .argName("CITEKEY1[,CITEKEY2][,CITEKEYn] | PDF1[,PDF2][,PDFn] | all") .build()); + options.addOption(Option + .builder("j") + .longOpt("jumpToKey") + .desc("%s: '%s'".formatted(Localization.lang("Jump to the entry of the given citation key."), "-j key")) + .hasArg() + .argName("CITATIONKEY") + .build()); + return options; } diff --git a/src/main/java/org/jabref/cli/Launcher.java b/src/main/java/org/jabref/cli/Launcher.java index 964f2dc13dc..5205bf15cf1 100644 --- a/src/main/java/org/jabref/cli/Launcher.java +++ b/src/main/java/org/jabref/cli/Launcher.java @@ -6,11 +6,14 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Comparator; +import java.util.List; import java.util.Map; import org.jabref.gui.Globals; import org.jabref.gui.MainApplication; +import org.jabref.logic.UiCommand; import org.jabref.logic.journals.JournalAbbreviationLoader; import org.jabref.logic.journals.predatory.PredatoryJournalListLoader; import org.jabref.logic.l10n.Localization; @@ -101,7 +104,8 @@ public static void main(String[] args) { System.exit(0); } - MainApplication.main(argumentProcessor.getParserResults(), argumentProcessor.isBlank(), preferences, fileUpdateMonitor, ARGUMENTS); + List uiCommands = new ArrayList<>(argumentProcessor.getUiCommands()); + MainApplication.main(uiCommands, preferences, fileUpdateMonitor, ARGUMENTS); } catch (ParseException e) { LOGGER.error("Problem parsing arguments", e); JabRefCLI.printUsage(preferences); @@ -147,20 +151,31 @@ private static void initializeLogger() { LOGGER = LoggerFactory.getLogger(MainApplication.class); } + /** + * @return true if JabRef should continue starting up, false if it should quit. + */ private static boolean handleMultipleAppInstances(String[] args, RemotePreferences remotePreferences) { + LOGGER.trace("Checking for remote handling..."); if (remotePreferences.useRemoteServer()) { // Try to contact already running JabRef RemoteClient remoteClient = new RemoteClient(remotePreferences.getPort()); if (remoteClient.ping()) { + LOGGER.debug("Pinging other instance succeeded."); // We are not alone, there is already a server out there, send command line // arguments to other instance + LOGGER.debug("Passing arguments passed on to running JabRef..."); if (remoteClient.sendCommandLineArguments(args)) { // So we assume it's all taken care of, and quit. - LOGGER.info(Localization.lang("Arguments passed on to running JabRef instance. Shutting down.")); + LOGGER.debug("Arguments passed on to running JabRef instance."); + System.out.println(Localization.lang("Arguments passed on to running JabRef instance. Shutting down.")); return false; } else { LOGGER.warn("Could not communicate with other running JabRef instance."); + // We do not launch a new instance in presence of an error + return false; } + } else { + LOGGER.debug("Could not ping JabRef instance."); } } return true; diff --git a/src/main/java/org/jabref/gui/DialogService.java b/src/main/java/org/jabref/gui/DialogService.java index c152bdcb343..8734637975c 100644 --- a/src/main/java/org/jabref/gui/DialogService.java +++ b/src/main/java/org/jabref/gui/DialogService.java @@ -204,7 +204,8 @@ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String Optional showCustomDialogAndWait(javafx.scene.control.Dialog dialog); /** - * Constructs and shows a canceable {@link ProgressDialog}. Clicking cancel will cancel the underlying service and close the dialog + * Constructs and shows a cancelable {@link ProgressDialog}. + * Clicking cancel will cancel the underlying service and close the dialog * * @param title title of the dialog * @param content message to show above the progress bar @@ -212,6 +213,17 @@ Optional showCustomButtonDialogAndWait(Alert.AlertType type, String */ void showProgressDialog(String title, String content, Task task); + /** + * Constructs and shows a cancelable {@link ProgressDialog}. + * Clicking cancel will cancel the underlying service and close the dialog, + * otherwise will wait for the task to finish. + * + * @param title title of the dialog + * @param content message to show above the progress bar + * @param task The {@link Task} which executes the work and for which to show the dialog + */ + void showProgressDialogAndWait(String title, String content, Task task); + /** * Constructs and shows a dialog showing the progress of running background tasks. * Clicking cancel will cancel the underlying service and close the dialog. diff --git a/src/main/java/org/jabref/gui/JabRefDialogService.java b/src/main/java/org/jabref/gui/JabRefDialogService.java index 6b595654d31..9198dfb0f30 100644 --- a/src/main/java/org/jabref/gui/JabRefDialogService.java +++ b/src/main/java/org/jabref/gui/JabRefDialogService.java @@ -297,8 +297,7 @@ public Optional showPasswordDialogAndWait(String title, String header, S return dialog.showAndWait(); } - @Override - public void showProgressDialog(String title, String content, Task task) { + private ProgressDialog createProgressDialog(String title, String content, Task task) { ProgressDialog progressDialog = new ProgressDialog(task); progressDialog.setHeaderText(null); progressDialog.setTitle(title); @@ -314,9 +313,21 @@ public void showProgressDialog(String title, String content, Task task) { progressDialog.close(); }); progressDialog.initOwner(mainWindow); + return progressDialog; + } + + @Override + public void showProgressDialog(String title, String content, Task task) { + ProgressDialog progressDialog = createProgressDialog(title, content, task); progressDialog.show(); } + @Override + public void showProgressDialogAndWait(String title, String content, Task task) { + ProgressDialog progressDialog = createProgressDialog(title, content, task); + progressDialog.showAndWait(); + } + @Override public Optional showBackgroundProgressDialogAndWait(String title, String content, StateManager stateManager) { TaskProgressView> taskProgressView = new TaskProgressView<>(); diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 154b3a3edfa..355a301d40c 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -5,10 +5,12 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.TimerTask; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -16,7 +18,10 @@ import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.StringBinding; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableBooleanValue; import javafx.collections.transformation.FilteredList; import javafx.event.Event; import javafx.scene.Node; @@ -56,6 +61,7 @@ import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.UiCommand; import org.jabref.logic.importer.ImportCleanup; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.l10n.Localization; @@ -111,6 +117,8 @@ public class JabRefFrame extends BorderPane implements LibraryTabContainer { private TabPane tabbedPane; private Subscription dividerSubscription; + private BooleanBinding loadingBinding; + private Subscription loadingSubscription; private final TaskExecutor taskExecutor; @@ -398,8 +406,8 @@ public boolean close() { storeLastOpenedFiles(openedLibraries, focusedLibraries); // store only if successfully having closed the libraries - WaitForSaveFinishedDialog waitForSaveFinishedDialog = new WaitForSaveFinishedDialog(dialogService); - waitForSaveFinishedDialog.showAndWait(getLibraryTabs()); + ProcessingLibraryDialog processingLibraryDialog = new ProcessingLibraryDialog(dialogService); + processingLibraryDialog.showAndWait(getLibraryTabs()); return true; } @@ -638,6 +646,7 @@ private ContextMenu createTabContextMenuFor(LibraryTab tab, KeyBindingRepository } public void addTab(LibraryTab libraryTab, boolean raisePanel) { + assert libraryTab != null; tabbedPane.getTabs().add(libraryTab); if (raisePanel) { tabbedPane.getSelectionModel().select(libraryTab); @@ -652,10 +661,10 @@ public void addTab(LibraryTab libraryTab, boolean raisePanel) { /** * Opens a new tab with existing data. * Asynchronous loading is done at {@link LibraryTab#createLibraryTab}. + * Similar method: {@link OpenDatabaseAction#openTheFile(Path)} */ public void addTab(BibDatabaseContext databaseContext, boolean raisePanel) { Objects.requireNonNull(databaseContext); - LibraryTab libraryTab = LibraryTab.createLibraryTab( databaseContext, this, @@ -666,21 +675,20 @@ public void addTab(BibDatabaseContext databaseContext, boolean raisePanel) { entryTypesManager, undoManager, taskExecutor); - addTab(libraryTab, raisePanel); } /** - * Might be called when a user asks JabRef at the command line + * Should be called when a user asks JabRef at the command line * i) to import a file or * ii) to open a .bib file */ public void addTab(ParserResult parserResult, boolean raisePanel) { if (parserResult.toOpenTab()) { - // Add the entries to the open tab. + LOGGER.trace("Adding the entries to the open tab."); LibraryTab libraryTab = getCurrentLibraryTab(); if (libraryTab == null) { - // There is no open tab to add to, so we create a new tab: + LOGGER.debug("No open tab found to add entries to. Creating a new tab."); addTab(parserResult.getDatabaseContext(), raisePanel); } else { addImportedEntries(libraryTab, parserResult); @@ -696,6 +704,8 @@ public void addTab(ParserResult parserResult, boolean raisePanel) { if (libraryTab.isPresent()) { tabbedPane.getSelectionModel().select(libraryTab.get()); } else { + // On this place, a tab is added after loading using the command line + // This takes a different execution path than loading a library using the GUI addTab(parserResult.getDatabaseContext(), raisePanel); } } @@ -802,17 +812,10 @@ public void refresh() { getLibraryTabs().forEach(tab -> tab.getMainTable().getTableModel().resetFieldFormatter()); } - void openDatabases(List parserResults, boolean isBlank) { + void openDatabases(List parserResults) { final List failed = new ArrayList<>(); final List toOpenTab = new ArrayList<>(); - // If the option is enabled, open the last edited libraries, if any. - if (!isBlank && prefs.getWorkspacePreferences().shouldOpenLastEdited()) { - openLastEditedDatabases(); - } - - // From here on, the libraries provided by command line arguments are treated - // Remove invalid databases List invalidDatabases = parserResults.stream() .filter(ParserResult::isInvalid) @@ -853,7 +856,6 @@ void openDatabases(List parserResults, boolean isBlank) { DatabaseNotSupportedException | InvalidDBMSConnectionPropertiesException | NotASharedDatabaseException e) { - LOGGER.error("Connection error", e); dialogService.showErrorDialogAndWait( Localization.lang("Connection error"), @@ -881,7 +883,6 @@ void openDatabases(List parserResults, boolean isBlank) { String message = Localization.lang("Error opening file '%0'", pr.getPath().map(Path::toString).orElse("(File name unknown)")) + "\n" + pr.getErrorMessage(); - dialogService.showErrorDialogAndWait(Localization.lang("Error opening file"), message); } @@ -907,7 +908,7 @@ void openDatabases(List parserResults, boolean isBlank) { LOGGER.debug("Finished adding panels"); } - private void openLastEditedDatabases() { + public void openLastEditedDatabases() { List lastFiles = prefs.getGuiPreferences().getLastFilesOpened(); if (lastFiles.isEmpty()) { return; @@ -924,6 +925,116 @@ public Stage getMainStage() { return mainStage; } + /** + * Handles commands submitted by the command line or by the remote host to be executed in the ui + * Needs to run in a certain order. E.g. databases have to be loaded before selecting an entry. + * + * @param uiCommands to be handled + */ + public void handleUiCommands(List uiCommands) { + LOGGER.debug("Handling UI commands {}", uiCommands); + if (uiCommands.isEmpty()) { + return; + } + + // Handle blank workspace + boolean blank = uiCommands.stream().anyMatch(UiCommand.BlankWorkspace.class::isInstance); + + // Handle OpenDatabases + if (!blank) { + uiCommands.stream() + .filter(UiCommand.OpenDatabases.class::isInstance) + .map(UiCommand.OpenDatabases.class::cast) + .forEach(command -> openDatabases(command.parserResults())); + } + + // Handle jumpToEntry + uiCommands.stream() + .filter(UiCommand.JumpToEntryKey.class::isInstance) + .map(UiCommand.JumpToEntryKey.class::cast) + .map(UiCommand.JumpToEntryKey::citationKey) + .filter(Objects::nonNull) + .findAny().ifPresent(entryKey -> { + LOGGER.debug("Jump to entry {} requested", entryKey); + // tabs must be present and contents async loaded for an entry to be selected + waitForLoadingFinished(() -> jumpToEntry(entryKey)); + }); + } + + private void jumpToEntry(String entryKey) { + // check current library tab first + LibraryTab currentLibraryTab = getCurrentLibraryTab(); + List sortedTabs = getLibraryTabs().stream() + .sorted(Comparator.comparing(tab -> tab != currentLibraryTab)) + .toList(); + for (LibraryTab libraryTab : sortedTabs) { + Optional bibEntry = libraryTab.getDatabase() + .getEntries().stream() + .filter(entry -> entry.getCitationKey().orElse("") + .equals(entryKey)) + .findAny(); + if (bibEntry.isPresent()) { + LOGGER.debug("Found entry {} in library tab {}", entryKey, libraryTab); + libraryTab.clearAndSelect(bibEntry.get()); + showLibraryTab(libraryTab); + break; + } + } + + LOGGER.trace("End of loop"); + + if (stateManager.getSelectedEntries().isEmpty()) { + dialogService.notify(Localization.lang("Citation key '%0' to select not found in open libraries.", entryKey)); + } + } + + private void waitForLoadingFinished(Runnable runnable) { + LOGGER.trace("Waiting for all tabs being loaded"); + + CompletableFuture future = new CompletableFuture<>(); + + List loadings = getLibraryTabs().stream().map(LibraryTab::getLoading) + .collect(Collectors.toList()); + + // Create a listener for each observable + ChangeListener listener = (observable, oldValue, newValue) -> { + assert newValue = false; + if (observable != null) { + loadings.remove(observable); + } + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Count of loading tabs: {}", loadings.size()); + LOGGER.trace("Count of loading tabs really true: {}", loadings.stream().filter(ObservableBooleanValue::get).count()); + } + for (ObservableBooleanValue obs : loadings) { + if (obs.get()) { + // Exit the listener if any of the observables is still true + return; + } + } + // All observables are false, complete the future + LOGGER.trace("Future completed"); + future.complete(null); + }; + + for (ObservableBooleanValue obs : loadings) { + obs.addListener(listener); + } + + LOGGER.trace("Fire once"); + // Due to concurrency, it might be that the observables are already false, so we trigger one evaluation + listener.changed(null, null, false); + LOGGER.trace("Waiting for state changes..."); + + future.thenRun(() -> { + LOGGER.debug("All tabs loaded. Jumping to entry."); + for (ObservableBooleanValue obs : loadings) { + obs.removeListener(listener); + } + runnable.run(); + }); + } + /** * The action concerned with closing the window. */ diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index 51ae5efd0cf..9fab7c0c3de 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -14,7 +14,7 @@ import org.jabref.gui.icon.IconTheme; import org.jabref.gui.keyboard.TextInputKeyBindings; import org.jabref.gui.theme.ThemeManager; -import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.UiCommand; import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.ProxyRegisterer; import org.jabref.logic.util.WebViewStore; @@ -42,18 +42,15 @@ public class JabRefGUI { private final Stage mainStage; private final PreferencesService preferencesService; - private final List parserResults; - private final boolean isBlank; + private final List uiCommands; private boolean correctedWindowPos; public JabRefGUI(Stage mainStage, - List parserResults, - boolean isBlank, + List uiCommands, PreferencesService preferencesService, FileUpdateMonitor fileUpdateMonitor) { this.mainStage = mainStage; - this.parserResults = parserResults; - this.isBlank = isBlank; + this.uiCommands = uiCommands; this.preferencesService = preferencesService; this.correctedWindowPos = false; @@ -157,11 +154,20 @@ private void openWindow() { mainStage.setTitle(JabRefFrame.FRAME_TITLE); mainStage.getIcons().addAll(IconTheme.getLogoSetFX()); mainStage.setScene(scene); + mainStage.setOnShowing(this::onShowing); mainStage.setOnCloseRequest(this::onCloseRequest); mainStage.setOnHiding(this::onHiding); mainStage.show(); - Platform.runLater(() -> mainFrame.openDatabases(parserResults, isBlank)); + Platform.runLater(() -> mainFrame.handleUiCommands(uiCommands)); + } + + public void onShowing(WindowEvent event) { + // Open last edited databases + if (uiCommands.stream().noneMatch(UiCommand.BlankWorkspace.class::isInstance) + && preferencesService.getWorkspacePreferences().shouldOpenLastEdited()) { + mainFrame.openLastEditedDatabases(); + } } public void onCloseRequest(WindowEvent event) { diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 66b1c4588ec..0567113d38b 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -15,6 +15,7 @@ import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ObservableBooleanValue; import javafx.collections.ListChangeListener; import javafx.event.Event; import javafx.geometry.Orientation; @@ -95,7 +96,6 @@ * Represents the ui area where the notifier pane, the library table and the entry editor are shown. */ public class LibraryTab extends Tab { - /** * Defines the different modes that the tab can operate in */ @@ -121,7 +121,14 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } private PanelMode mode = PanelMode.MAIN_TABLE; private SplitPane splitPane; private DatabaseNotification databaseNotificationPane; - private boolean saving; + + // Indicates whether the tab is loading data using a dataloading task + // The constructors take care to the right true/false assignment during start. + private SimpleBooleanProperty loading = new SimpleBooleanProperty(false); + + // initally, the dialog is loading, not saving + private boolean saving = false; + private PersonNameSuggestionProvider searchAutoCompleter; // Used to track whether the base has changed since last save. @@ -218,6 +225,7 @@ private static void addSharedDbInformation(StringBuilder text, BibDatabaseContex } private void setDataLoadingTask(BackgroundTask dataLoadingTask) { + this.loading.set(true); this.dataLoadingTask = dataLoadingTask; } @@ -234,7 +242,6 @@ private Node createLoadingAnimationLayout() { private void onDatabaseLoadingStarted() { Node loadingLayout = createLoadingAnimationLayout(); getMainTable().placeholderProperty().setValue(loadingLayout); - tabContainer.addTab(this, true); } private void onDatabaseLoadingSucceed(ParserResult result) { @@ -251,10 +258,14 @@ private void onDatabaseLoadingSucceed(ParserResult result) { } } + LOGGER.trace("loading.set(false);"); + loading.set(false); dataLoadingTask = null; } private void onDatabaseLoadingFailed(Exception ex) { + loading.set(false); + String title = Localization.lang("Connection error"); String content = "%s\n\n%s".formatted(ex.getMessage(), Localization.lang("A local copy will be opened.")); @@ -722,6 +733,7 @@ private boolean confirmClose() { // Database could not have been changed, since it is still loading if (dataLoadingTask != null) { dataLoadingTask.cancel(); + loading.setValue(false); return true; } @@ -832,6 +844,10 @@ public void setSaving(boolean saving) { this.saving = saving; } + public ObservableBooleanValue getLoading() { + return loading; + } + public CountingUndoManager getUndoManager() { return undoManager; } @@ -1067,4 +1083,12 @@ public void notify(Node graphic, String text, List actions, Duration dur public DatabaseNotification getNotificationPane() { return databaseNotificationPane; } + + @Override + public String toString() { + return "LibraryTab{" + + "bibDatabaseContext=" + bibDatabaseContext + + ", showing=" + showing + + '}'; + } } diff --git a/src/main/java/org/jabref/gui/LibraryTabContainer.java b/src/main/java/org/jabref/gui/LibraryTabContainer.java index a290676bd3a..25c1ac468e1 100644 --- a/src/main/java/org/jabref/gui/LibraryTabContainer.java +++ b/src/main/java/org/jabref/gui/LibraryTabContainer.java @@ -4,9 +4,14 @@ import org.jabref.model.database.BibDatabaseContext; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked public interface LibraryTabContainer { List getLibraryTabs(); + @Nullable LibraryTab getCurrentLibraryTab(); void showLibraryTab(LibraryTab libraryTab); diff --git a/src/main/java/org/jabref/gui/MainApplication.java b/src/main/java/org/jabref/gui/MainApplication.java index 76a33eb6f24..1eaa0671e50 100644 --- a/src/main/java/org/jabref/gui/MainApplication.java +++ b/src/main/java/org/jabref/gui/MainApplication.java @@ -6,7 +6,7 @@ import javafx.stage.Stage; import org.jabref.gui.openoffice.OOBibBaseConnect; -import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.UiCommand; import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.JabRefPreferences; @@ -19,18 +19,15 @@ public class MainApplication extends Application { private static final Logger LOGGER = LoggerFactory.getLogger(MainApplication.class); - private static List parserResults; - private static boolean isBlank; + private static List uiCommands; private static JabRefPreferences preferences; private static FileUpdateMonitor fileUpdateMonitor; - public static void main(List parserResults, - boolean blank, + public static void main(List uiCommands, JabRefPreferences preferences, FileUpdateMonitor fileUpdateMonitor, String[] args) { - MainApplication.parserResults = parserResults; - MainApplication.isBlank = blank; + MainApplication.uiCommands = uiCommands; MainApplication.preferences = preferences; MainApplication.fileUpdateMonitor = fileUpdateMonitor; launch(args); @@ -40,7 +37,7 @@ public static void main(List parserResults, public void start(Stage mainStage) { FallbackExceptionHandler.installExceptionHandler(); Globals.startBackgroundTasks(); - new JabRefGUI(mainStage, parserResults, isBlank, preferences, fileUpdateMonitor); + new JabRefGUI(mainStage, uiCommands, preferences, fileUpdateMonitor); } @Override diff --git a/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java b/src/main/java/org/jabref/gui/ProcessingLibraryDialog.java similarity index 58% rename from src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java rename to src/main/java/org/jabref/gui/ProcessingLibraryDialog.java index 43f784669de..52ff8512d2c 100644 --- a/src/main/java/org/jabref/gui/WaitForSaveFinishedDialog.java +++ b/src/main/java/org/jabref/gui/ProcessingLibraryDialog.java @@ -4,25 +4,26 @@ import javafx.concurrent.Task; +import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.l10n.Localization; /** - * Dialog shown when closing of application needs to wait for a save operation to finish. + * Dialog shown when closing of application needs to wait for a save operation to finish. */ -public class WaitForSaveFinishedDialog { +public class ProcessingLibraryDialog { private final DialogService dialogService; - public WaitForSaveFinishedDialog(DialogService dialogService) { + public ProcessingLibraryDialog(DialogService dialogService) { this.dialogService = dialogService; } - public void showAndWait(List LibraryTabs) { - if (LibraryTabs.stream().anyMatch(LibraryTab::isSaving)) { + public void showAndWait(List libraryTabs) { + if (libraryTabs.stream().anyMatch(tab -> tab.isSaving())) { Task waitForSaveFinished = new Task<>() { @Override protected Void call() throws Exception { - while (LibraryTabs.stream().anyMatch(LibraryTab::isSaving)) { + while (libraryTabs.stream().anyMatch(tab -> tab.isSaving())) { if (isCancelled()) { return null; } else { @@ -33,9 +34,10 @@ protected Void call() throws Exception { } }; - dialogService.showProgressDialog( + DefaultTaskExecutor.runInJavaFXThread(waitForSaveFinished); + dialogService.showProgressDialogAndWait( Localization.lang("Please wait..."), - Localization.lang("Waiting for save operation to finish") + "...", + Localization.lang("Waiting for save operation to finish..."), waitForSaveFinished ); } diff --git a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java index dfb89701663..afce97681e6 100644 --- a/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java +++ b/src/main/java/org/jabref/gui/autosaveandbackup/BackupManager.java @@ -178,7 +178,11 @@ public static boolean backupFileDiffers(Path originalPath, Path backupDir) { return false; } try { - return Files.mismatch(originalPath, latestBackupPath) != -1L; + boolean result = Files.mismatch(originalPath, latestBackupPath) != -1L; + if (result) { + LOGGER.info("Backup file {} differs from current file {}", latestBackupPath, originalPath); + } + return result; } catch (IOException e) { LOGGER.debug("Could not compare original file and backup file.", e); // User has to investigate in this case diff --git a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java index 6be605c61c1..b3027a6f9b5 100644 --- a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java @@ -203,7 +203,7 @@ private void styleFetchedListView(CheckListView listView) if (entry.isLocal()) { Button jumpTo = IconTheme.JabRefIcons.LINK.asButton(); - jumpTo.setTooltip(new Tooltip(Localization.lang("Jump to entry in database"))); + jumpTo.setTooltip(new Tooltip(Localization.lang("Jump to entry in library"))); jumpTo.getStyleClass().add("addEntryButton"); jumpTo.setOnMouseClicked(event -> { libraryTab.showAndEdit(entry.entry()); diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java index 1b46b1d6c6b..28048defa97 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java @@ -54,7 +54,7 @@ public ParsedEntryLink fromString(String key) { } public void jumpToEntry(ParsedEntryLink parsedEntryLink) { - // TODO: Implement jump to entry + // TODO: Implement jump to entry - The implementation can be based on JabRefFrame.jumpToEntry // TODO: Add toolitp for tag: Localization.lang("Jump to entry") // This feature was removed while converting the linked entries editor to JavaFX // Right now there is no nice way to re-implement it as we have no good interface to control the focus of the main table diff --git a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java index af295f03a19..f607f90a4ed 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -169,9 +169,10 @@ public void openFiles(List filesToOpen) { openTheFile(theFile); fileHistory.newFile(theFile); }); - } else if (toRaise != null) { + } else if (toRaise != null && tabContainer.getCurrentLibraryTab() == null) { // If no files are remaining to open, this could mean that a file was // already open. If so, we may have to raise the correct tab: + // If there is already a library focused, do not show this library tabContainer.showLibraryTab(toRaise); } } @@ -179,6 +180,8 @@ public void openFiles(List filesToOpen) { /** * This is the real file opening. Should be called via {@link #openFile(Path)} * + * Similar method: {@link org.jabref.gui.JabRefFrame#addTab(org.jabref.model.database.BibDatabaseContext, boolean)}. + * * @param file the file, may be NOT null, but may not be existing */ private void openTheFile(Path file) { @@ -201,6 +204,7 @@ private void openTheFile(Path file) { undoManager, taskExecutor); backgroundTask.onFinished(() -> trackOpenNewDatabase(newTab)); + tabContainer.addTab(newTab, true); } private ParserResult loadDatabase(Path file) throws Exception { diff --git a/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java b/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java index 906ad83975c..a9a834071d8 100644 --- a/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java @@ -260,12 +260,14 @@ public void storeSettings() { } }); + // stop in all cases, because the port might have changed + Globals.REMOTE_LISTENER.stop(); + if (remoteServerProperty.getValue()) { remotePreferences.setUseRemoteServer(true); Globals.REMOTE_LISTENER.openAndStart(new CLIMessageHandler(preferences, fileUpdateMonitor, entryTypesManager), remotePreferences.getPort()); } else { remotePreferences.setUseRemoteServer(false); - Globals.REMOTE_LISTENER.stop(); } trustStoreManager.flush(); diff --git a/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java b/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java index 272d84fd600..ed794039d97 100644 --- a/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java +++ b/src/main/java/org/jabref/gui/remote/CLIMessageHandler.java @@ -1,12 +1,11 @@ package org.jabref.gui.remote; -import java.util.List; +import java.util.Arrays; import javafx.application.Platform; import org.jabref.cli.ArgumentProcessor; import org.jabref.gui.JabRefGUI; -import org.jabref.logic.importer.ParserResult; import org.jabref.logic.remote.server.RemoteMessageHandler; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.util.FileUpdateMonitor; @@ -32,6 +31,7 @@ public CLIMessageHandler(PreferencesService preferencesService, FileUpdateMonito @Override public void handleCommandLineArguments(String[] message) { try { + LOGGER.info("Processing message {}", Arrays.stream(message).toList()); ArgumentProcessor argumentProcessor = new ArgumentProcessor( message, ArgumentProcessor.Mode.REMOTE_START, @@ -39,15 +39,7 @@ public void handleCommandLineArguments(String[] message) { fileUpdateMonitor, entryTypesManager); argumentProcessor.processArguments(); - List loaded = argumentProcessor.getParserResults(); - for (int i = 0; i < loaded.size(); i++) { - ParserResult pr = loaded.get(i); - boolean focusPanel = i == 0; - Platform.runLater(() -> - // Need to run this on the JavaFX thread - JabRefGUI.getMainFrame().addTab(pr, focusPanel) - ); - } + Platform.runLater(() -> JabRefGUI.getMainFrame().handleUiCommands(argumentProcessor.getUiCommands())); } catch (ParseException e) { LOGGER.error("Error when parsing CLI args", e); } diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java index e8462259b7a..158082f4934 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java @@ -156,14 +156,7 @@ public void listen(SharedEntriesNotPresentEvent event) { public LibraryTab openNewSharedDatabaseTab(DBMSConnectionProperties dbmsConnectionProperties) throws SQLException, DatabaseNotSupportedException, InvalidDBMSConnectionPropertiesException { - BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(); - bibDatabaseContext.setMode(preferencesService.getLibraryPreferences().getDefaultBibDatabaseMode()); - DBMSSynchronizer synchronizer = new DBMSSynchronizer( - bibDatabaseContext, - preferencesService.getBibEntryPreferences().getKeywordSeparator(), - preferencesService.getCitationKeyPatternPreferences().getKeyPattern(), - fileUpdateMonitor); - bibDatabaseContext.convertToSharedDatabase(synchronizer); + BibDatabaseContext bibDatabaseContext = getBibDatabaseContextForSharedDatabase(); dbmsSynchronizer = bibDatabaseContext.getDBMSSynchronizer(); dbmsSynchronizer.openSharedDatabase(new DBMSConnection(dbmsConnectionProperties)); @@ -197,14 +190,7 @@ public void openSharedDatabaseFromParserResult(ParserResult parserResult) String sharedDatabaseID = sharedDatabaseIDOptional.get(); DBMSConnectionProperties dbmsConnectionProperties = new DBMSConnectionProperties(new SharedDatabasePreferences(sharedDatabaseID)); - BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(); - bibDatabaseContext.setMode(preferencesService.getLibraryPreferences().getDefaultBibDatabaseMode()); - DBMSSynchronizer synchronizer = new DBMSSynchronizer( - bibDatabaseContext, - preferencesService.getBibEntryPreferences().getKeywordSeparator(), - preferencesService.getCitationKeyPatternPreferences().getKeyPattern(), - fileUpdateMonitor); - bibDatabaseContext.convertToSharedDatabase(synchronizer); + BibDatabaseContext bibDatabaseContext = getBibDatabaseContextForSharedDatabase(); bibDatabaseContext.getDatabase().setSharedDatabaseID(sharedDatabaseID); bibDatabaseContext.setDatabasePath(parserResult.getDatabaseContext().getDatabasePath().orElse(null)); @@ -212,7 +198,20 @@ public void openSharedDatabaseFromParserResult(ParserResult parserResult) dbmsSynchronizer = bibDatabaseContext.getDBMSSynchronizer(); dbmsSynchronizer.openSharedDatabase(new DBMSConnection(dbmsConnectionProperties)); dbmsSynchronizer.registerListener(this); - parserResult.setDatabaseContext(bibDatabaseContext); dialogService.notify(Localization.lang("Connection to %0 server established.", dbmsConnectionProperties.getType().toString())); + + parserResult.setDatabaseContext(bibDatabaseContext); + } + + private BibDatabaseContext getBibDatabaseContextForSharedDatabase() { + BibDatabaseContext bibDatabaseContext = new BibDatabaseContext(); + bibDatabaseContext.setMode(preferencesService.getLibraryPreferences().getDefaultBibDatabaseMode()); + DBMSSynchronizer synchronizer = new DBMSSynchronizer( + bibDatabaseContext, + preferencesService.getBibEntryPreferences().getKeywordSeparator(), + preferencesService.getCitationKeyPatternPreferences().getKeyPattern(), + fileUpdateMonitor); + bibDatabaseContext.convertToSharedDatabase(synchronizer); + return bibDatabaseContext; } } diff --git a/src/main/java/org/jabref/gui/util/BackgroundTask.java b/src/main/java/org/jabref/gui/util/BackgroundTask.java index a0eda270279..0262e54cb61 100644 --- a/src/main/java/org/jabref/gui/util/BackgroundTask.java +++ b/src/main/java/org/jabref/gui/util/BackgroundTask.java @@ -31,6 +31,10 @@ * and so makes testing harder. * We take the opportunity and implement a fluid interface. * + * TODO: Think of migrating to RxJava; + * CompletableFuture do not seem to support everything. + * If this is not possible, add an @implNote why. + * * @param type of the return value of the task */ public abstract class BackgroundTask { @@ -120,11 +124,11 @@ public DoubleProperty workDonePercentageProperty() { return workDonePercentage; } - public BackgroundProgress getProgress() { + protected BackgroundProgress getProgress() { return progress.get(); } - public ObjectProperty progressProperty() { + protected ObjectProperty progressProperty() { return progress; } @@ -275,30 +279,16 @@ public static Node getIcon(Task task) { return BackgroundTask.iconMap.getOrDefault(task.getTitle(), null); } - static class BackgroundProgress { - - private final double workDone; - private final double max; - - public BackgroundProgress(double workDone, double max) { - this.workDone = workDone; - this.max = max; - } - - public double getWorkDone() { - return workDone; - } - - public double getMax() { - return max; - } + protected record BackgroundProgress( + double workDone, + double max) { public double getWorkDonePercentage() { - if (max == 0) { - return 0; - } else { - return workDone / max; + if (max == 0) { + return 0; + } else { + return workDone / max; + } } } - } } diff --git a/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java b/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java index 8240a5b209f..eda0fcf71d8 100644 --- a/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java +++ b/src/main/java/org/jabref/gui/util/DefaultTaskExecutor.java @@ -145,7 +145,7 @@ private Task getJavaFXTask(BackgroundTask task) { { this.updateMessage(task.messageProperty().get()); this.updateTitle(task.titleProperty().get()); - BindingsHelper.subscribeFuture(task.progressProperty(), progress -> updateProgress(progress.getWorkDone(), progress.getMax())); + BindingsHelper.subscribeFuture(task.progressProperty(), progress -> updateProgress(progress.workDone(), progress.max())); BindingsHelper.subscribeFuture(task.messageProperty(), this::updateMessage); BindingsHelper.subscribeFuture(task.titleProperty(), this::updateTitle); BindingsHelper.subscribeFuture(task.isCanceledProperty(), cancelled -> { diff --git a/src/main/java/org/jabref/logic/UiCommand.java b/src/main/java/org/jabref/logic/UiCommand.java new file mode 100644 index 00000000000..e409d65b0be --- /dev/null +++ b/src/main/java/org/jabref/logic/UiCommand.java @@ -0,0 +1,13 @@ +package org.jabref.logic; + +import java.util.List; + +import org.jabref.logic.importer.ParserResult; + +public sealed interface UiCommand { + record BlankWorkspace() implements UiCommand { } + + record JumpToEntryKey(String citationKey) implements UiCommand { } + + record OpenDatabases(List parserResults) implements UiCommand { } +} diff --git a/src/main/java/org/jabref/logic/remote/Protocol.java b/src/main/java/org/jabref/logic/remote/Protocol.java index eb610e17ec7..d8f2b147ef5 100644 --- a/src/main/java/org/jabref/logic/remote/Protocol.java +++ b/src/main/java/org/jabref/logic/remote/Protocol.java @@ -92,13 +92,17 @@ public void close() { try { out.close(); } catch (IOException e) { - LOGGER.warn("Output stream not closed", e); + // On the server side, the socket is automatically closed, thus we don't need to close it here. + // See org.jabref.logic.remote.server.RemoteListenerServer.run + LOGGER.debug("Output stream not closed", e); } try { socket.close(); } catch (IOException e) { - LOGGER.warn("Socket not closed", e); + // On the server side, the socket is automatically closed, thus we don't need to close it here. + // See org.jabref.logic.remote.server.RemoteListenerServer.run + LOGGER.debug("Socket not closed", e); } } } diff --git a/src/main/java/org/jabref/logic/remote/client/RemoteClient.java b/src/main/java/org/jabref/logic/remote/client/RemoteClient.java index 6844547eef0..baa97114984 100644 --- a/src/main/java/org/jabref/logic/remote/client/RemoteClient.java +++ b/src/main/java/org/jabref/logic/remote/client/RemoteClient.java @@ -18,7 +18,9 @@ public class RemoteClient { private static final Logger LOGGER = LoggerFactory.getLogger(RemoteClient.class); - private static final int TIMEOUT = 1000; + // Opening a library can take time, thus 2 minutes is a reasonable timeout. + private static final int TIMEOUT = 120_000; + private final int port; public RemoteClient(int port) { diff --git a/src/main/java/org/jabref/logic/remote/server/RemoteListenerServer.java b/src/main/java/org/jabref/logic/remote/server/RemoteListenerServer.java index bfa0e8829da..be574525d28 100644 --- a/src/main/java/org/jabref/logic/remote/server/RemoteListenerServer.java +++ b/src/main/java/org/jabref/logic/remote/server/RemoteListenerServer.java @@ -35,7 +35,6 @@ public void run() { while (!Thread.interrupted()) { try (Socket socket = serverSocket.accept()) { socket.setSoTimeout(TIMEOUT); - try (Protocol protocol = new Protocol(socket)) { Pair input = protocol.receiveMessage(); handleMessage(protocol, input.getKey(), input.getValue()); diff --git a/src/main/java/org/jabref/logic/remote/server/RemoteListenerServerThread.java b/src/main/java/org/jabref/logic/remote/server/RemoteListenerServerThread.java index 431a45b0727..510fee50294 100644 --- a/src/main/java/org/jabref/logic/remote/server/RemoteListenerServerThread.java +++ b/src/main/java/org/jabref/logic/remote/server/RemoteListenerServerThread.java @@ -21,7 +21,7 @@ public RemoteListenerServerThread(RemoteMessageHandler messageHandler, int port) @Override public void interrupt() { - LOGGER.debug("Interrupting " + this.getName()); + LOGGER.debug("Interrupting {}", this.getName()); this.server.closeServerSocket(); super.interrupt(); } diff --git a/src/main/java/org/jabref/logic/remote/server/RemoteMessageHandler.java b/src/main/java/org/jabref/logic/remote/server/RemoteMessageHandler.java index a90a35e953f..398046563aa 100644 --- a/src/main/java/org/jabref/logic/remote/server/RemoteMessageHandler.java +++ b/src/main/java/org/jabref/logic/remote/server/RemoteMessageHandler.java @@ -2,6 +2,5 @@ @FunctionalInterface public interface RemoteMessageHandler { - void handleCommandLineArguments(String[] message); } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 46c4789916a..40df4f792ed 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -87,12 +87,6 @@ Assigned\ %0\ entries\ to\ group\ "%1".=Assigned %0 entries to group "%1". Assigned\ 1\ entry\ to\ group\ "%0".=Assigned 1 entry to group "%0". -Autogenerate\ citation\ keys=Autogenerate citation keys - -Autolink\ files\ with\ names\ starting\ with\ the\ citation\ key=Autolink files with names starting with the citation key - -Autolink\ only\ files\ that\ match\ the\ citation\ key=Autolink only files that match the citation key - Automatically\ create\ groups=Automatically create groups Automatically\ remove\ exact\ duplicates=Automatically remove exact duplicates @@ -354,23 +348,37 @@ Manage\ field\ names\ &\ content=Manage field names & content Field\ to\ group\ by=Field to group by File=File - file=file File\ directory\ is\ not\ set\ or\ does\ not\ exist\!=File directory is not set or does not exist! - File\ exists=File exists - File\ not\ found=File not found Filter=Filter - Filter\ groups=Filter groups Success\!\ Finished\ writing\ metadata.=Success! Finished writing metadata. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Error while writing metadata. See the error log for details. Failed\ to\ write\ metadata,\ file\ %1\ not\ found.=Failed to write metadata, file %1 not found. +Generate=Generate + First\ select\ the\ entries\ you\ want\ keys\ to\ be\ generated\ for.=First select the entries you want keys to be generated for. +Autogenerate\ citation\ keys=Autogenerate citation keys +Generate\ citation\ key=Generate citation key +Generate\ keys=Generate keys +Citation\ key\ generator=Citation key generator +Generate\ keys\ before\ saving\ (for\ entries\ without\ a\ key)=Generate keys before saving (for entries without a key) +Generated\ citation\ key\ for=Generated citation key for +Generating\ citation\ key\ for=Generating citation key for +Invalid\ citation\ key=Invalid citation key +Duplicate\ citation\ key=Duplicate citation key +Citation\ key\ '%0'\ to\ select\ not\ found\ in\ open\ libraries.=Citation key '%0' to select not found in open libraries. + +Jump\ to\ entry\ in\ library=Jump to entry in library +Jump\ to\ the\ entry\ of\ the\ given\ citation\ key.=Jump to the entry of the given citation key. + +Autolink\ files\ with\ names\ starting\ with\ the\ citation\ key=Autolink files with names starting with the citation key +Autolink\ only\ files\ that\ match\ the\ citation\ key=Autolink only files that match the citation key Fit\ table\ horizontally\ on\ screen=Fit table horizontally on screen @@ -390,17 +398,6 @@ Further\ information\ about\ Mr.\ DLib\ for\ JabRef\ users.=Further information General=General -Generate=Generate - -Generate\ citation\ key=Generate citation key - -Generate\ keys=Generate keys - -Generate\ keys\ before\ saving\ (for\ entries\ without\ a\ key)=Generate keys before saving (for entries without a key) - -Generated\ citation\ key\ for=Generated citation key for - -Generating\ citation\ key\ for=Generating citation key for Get\ fulltext=Get fulltext Gray\ out\ non-hits=Gray out non-hits @@ -456,8 +453,6 @@ Indexing\ pdf\ files=Indexing pdf files Indexing\ for\ %0=Indexing for %0 %0\ of\ %1\ linked\ files\ added\ to\ the\ index=%0 of %1 linked files added to the index -Invalid\ citation\ key=Invalid citation key - Invalid\ URL=Invalid URL Online\ help=Online help @@ -953,14 +948,10 @@ View=View View\ journal\ info=View journal info -Warn\ about\ unresolved\ duplicates\ when\ closing\ inspection\ window=Warn about unresolved duplicates when closing inspection window - -Warn\ before\ overwriting\ existing\ keys=Warn before overwriting existing keys - Warning=Warning - Warnings=Warnings - +Warn\ about\ unresolved\ duplicates\ when\ closing\ inspection\ window=Warn about unresolved duplicates when closing inspection window +Warn\ before\ overwriting\ existing\ keys=Warn before overwriting existing keys Warning\:\ You\ added\ field\ "%0"\ twice.\ Only\ one\ will\ be\ kept.=Warning: You added field "%0" twice. Only one will be kept. web\ link=web link @@ -974,7 +965,7 @@ Write\ BibTeXEntry\ as\ metadata\ to\ PDF.=Write BibTeXEntry as metadata to PDF. Write\ metadata\ for\ all\ PDFs\ in\ current\ library?=Write metadata for all PDFs in current library? Writing\ metadata...=Writing metadata... -Embed\ BibTeXEntry\ in\ PDF.=Embed BibTeXEntry in PDF. +Embed\ BibTeX\ as\ attached\ file\ in\ PDF.=Embed BibTeX as attached file in PDF. File\ '%0'\ is\ write\ protected.=File '%0' is write protected. Write\ BibTeXEntry\ as\ XMP\ metadata\ to\ PDF.=Write BibTeXEntry as XMP metadata to PDF. Write\ BibTeXEntry\ metadata\ to\ PDF.=Write BibTeXEntry metadata to PDF. @@ -983,6 +974,7 @@ Write\ metadata\ to\ PDF\ files=Write metadata to PDF files XMP-annotated\ PDF=XMP-annotated PDF XMP\ export\ privacy\ settings=XMP export privacy settings XMP\ metadata=XMP metadata + You\ must\ restart\ JabRef\ for\ this\ to\ come\ into\ effect.=You must restart JabRef for this to come into effect. The\ following\ fetchers\ are\ available\:=The following fetchers are available: @@ -1001,7 +993,6 @@ Could\ not\ find\ file\ '%0'.=Could not find file '%0'. Number\ of\ entries\ successfully\ imported=Number of entries successfully imported Error\ while\ fetching\ from\ %0=Error while fetching from %0 -Citation\ key\ generator=Citation key generator Unable\ to\ open\ link.=Unable to open link. MIME\ type=MIME type @@ -1036,13 +1027,12 @@ includes\ subgroups=includes subgroups contains=contains Optional\ fields\ 2=Optional fields 2 -Waiting\ for\ save\ operation\ to\ finish=Waiting for save operation to finish + +Waiting\ for\ save\ operation\ to\ finish...=Waiting for save operation to finish... Waiting\ for\ background\ tasks\ to\ finish.\ Quit\ anyway?=Waiting for background tasks to finish. Quit anyway? Find\ and\ remove\ duplicate\ citation\ keys=Find and remove duplicate citation keys Expected\ syntax\ for\ --fetch\='\:'=Expected syntax for --fetch=':' -Duplicate\ citation\ key=Duplicate citation key - General\ file\ directory=General file directory User-specific\ file\ directory=User-specific file directory @@ -2609,7 +2599,6 @@ Search\ aborted!=Search aborted! Citation\ relations=Citation relations Show\ articles\ related\ by\ citation=Show articles related by citation Error\ while\ fetching\ citing\ entries\:\ %0=Error while fetching citing entries: %0 -Jump\ to\ entry\ in\ database=Jump to entry in database Help\ on\ external\ applications=Help on external applications Identifier-based\ Web\ Search=Identifier-based Web Search From 47bdfc1317fddc41500ecffed281d6635b3b3375 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 5 Feb 2024 09:24:20 +0100 Subject: [PATCH 05/14] Enable auto merge of gradle wrapper update --- .github/workflows/automerge.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 7d339a6355d..1e5c7011f23 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -17,7 +17,8 @@ jobs: ( startsWith(github.event.pull_request.title, '[Bot] ') || startsWith(github.event.pull_request.title, 'Bump ') || - startsWith(github.event.pull_request.title, 'New Crowdin updates') + startsWith(github.event.pull_request.title, 'New Crowdin updates') || + startsWith(github.event.pull_request.title, 'Update Gradle Wrapper from') ) ) steps: From 6ebc78b85e0274fe6b87786abe71c47697ec0ede Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 5 Feb 2024 09:41:15 +0100 Subject: [PATCH 06/14] Update Gradle Wrapper from 8.5 to 8.6. (#10854) Signed-off-by: gradle-update-robot Co-authored-by: gradle-update-robot --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew.bat | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index db8c3baafe3..4baf5a11d45 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f135..25da30dbdee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail From 9b0d7b30d20f9eeaa98c6c150f8b6455b123cc7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:13:23 +0000 Subject: [PATCH 07/14] Bump peter-evans/create-pull-request from 5 to 6 (#10856) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 5 to 6. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/v5...v6) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-csl-subtrees.yml | 2 +- .github/workflows/refresh-journal-lists.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/refresh-csl-subtrees.yml b/.github/workflows/refresh-csl-subtrees.yml index 789bd4b6ba0..6f8230887e9 100644 --- a/.github/workflows/refresh-csl-subtrees.yml +++ b/.github/workflows/refresh-csl-subtrees.yml @@ -36,7 +36,7 @@ jobs: git add -f src/main/resources/csl-styles git add -f src/main/resources/csl-locales git diff-index --quiet HEAD || git commit -m 'Update CSL styles' - - uses: peter-evans/create-pull-request@v5 + - uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.GH_TOKEN_UPDATE_GRADLE_WRAPPER }} branch: refresh-csl diff --git a/.github/workflows/refresh-journal-lists.yml b/.github/workflows/refresh-journal-lists.yml index e1209a0f268..ca50dc8ffa0 100644 --- a/.github/workflows/refresh-journal-lists.yml +++ b/.github/workflows/refresh-journal-lists.yml @@ -41,7 +41,7 @@ jobs: run: | git add -f buildres/abbrv.jabref.org git diff-index --quiet HEAD || git commit -m 'Update journal abbreviation lists' - - uses: peter-evans/create-pull-request@v5 + - uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.GH_TOKEN_UPDATE_GRADLE_WRAPPER }} branch: update-journal-lists From e357aee4657f835f9e0536ca529e68ec6c5fba78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:14:13 +0000 Subject: [PATCH 08/14] Bump codecov/codecov-action from 3 to 4 (#10857) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b80fe24005a..f6af94b53fd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -293,7 +293,7 @@ jobs: CI: "true" CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} DBMS: "postgresql" - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 if: (github.ref == 'refs/heads/main') && (steps.checksecrets.outputs.secretspresent == 'YES') with: token: ${{ secrets.CODECOV_TOKEN }} From 3a9952a433403534af5ccb165f02f513d3a707d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:17:32 +0000 Subject: [PATCH 09/14] Bump gradle/wrapper-validation-action from 1 to 2 (#10858) Bumps [gradle/wrapper-validation-action](https://github.com/gradle/wrapper-validation-action) from 1 to 2. - [Release notes](https://github.com/gradle/wrapper-validation-action/releases) - [Commits](https://github.com/gradle/wrapper-validation-action/compare/v1...v2) --- updated-dependencies: - dependency-name: gradle/wrapper-validation-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f6af94b53fd..8b3eef04001 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -306,7 +306,7 @@ jobs: - uses: actions/checkout@v4 with: show-progress: 'false' - - uses: gradle/wrapper-validation-action@v1 + - uses: gradle/wrapper-validation-action@v2 # This ensures that no git merge conflict markers (<<<, ...) are contained merge_conflict_job: name: Find merge conflicts From fd33ba5e3cd4aad4f16e0efe4ada7f97440579e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:30:57 +0000 Subject: [PATCH 10/14] Bump org.apache.lucene:lucene-queryparser from 9.9.1 to 9.9.2 (#10859) Bumps org.apache.lucene:lucene-queryparser from 9.9.1 to 9.9.2. --- updated-dependencies: - dependency-name: org.apache.lucene:lucene-queryparser dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 96a64888610..6db261c50ba 100644 --- a/build.gradle +++ b/build.gradle @@ -120,7 +120,7 @@ dependencies { } implementation 'org.apache.lucene:lucene-core:9.9.1' - implementation 'org.apache.lucene:lucene-queryparser:9.9.1' + implementation 'org.apache.lucene:lucene-queryparser:9.9.2' implementation 'org.apache.lucene:lucene-queries:9.9.1' implementation 'org.apache.lucene:lucene-analysis-common:9.9.1' implementation 'org.apache.lucene:lucene-highlighter:9.9.1' From b1ae7d267d61708dc133de42df6a0f0339edc108 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:37:25 +0000 Subject: [PATCH 11/14] Bump org.tinylog:tinylog-api from 2.6.2 to 2.7.0 (#10862) Bumps [org.tinylog:tinylog-api](https://github.com/tinylog-org/tinylog) from 2.6.2 to 2.7.0. - [Release notes](https://github.com/tinylog-org/tinylog/releases) - [Changelog](https://github.com/tinylog-org/tinylog/blob/v2.7/release.md) - [Commits](https://github.com/tinylog-org/tinylog/compare/2.6.2...2.7.0) --- updated-dependencies: - dependency-name: org.tinylog:tinylog-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6db261c50ba..20f6a250e41 100644 --- a/build.gradle +++ b/build.gradle @@ -188,7 +188,7 @@ dependencies { implementation 'com.konghq:unirest-java:3.14.5' implementation 'org.slf4j:slf4j-api:2.0.11' - implementation "org.tinylog:tinylog-api:2.6.2" + implementation "org.tinylog:tinylog-api:2.7.0" implementation "org.tinylog:slf4j-tinylog:2.6.2" implementation "org.tinylog:tinylog-impl:2.6.2" From c5b23c56108ae75c30d53d147615e15af69f262a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:40:00 +0000 Subject: [PATCH 12/14] Bump org.apache.lucene:lucene-highlighter from 9.9.1 to 9.9.2 (#10861) Bumps org.apache.lucene:lucene-highlighter from 9.9.1 to 9.9.2. --- updated-dependencies: - dependency-name: org.apache.lucene:lucene-highlighter dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 20f6a250e41..4b92e249cf7 100644 --- a/build.gradle +++ b/build.gradle @@ -123,7 +123,7 @@ dependencies { implementation 'org.apache.lucene:lucene-queryparser:9.9.2' implementation 'org.apache.lucene:lucene-queries:9.9.1' implementation 'org.apache.lucene:lucene-analysis-common:9.9.1' - implementation 'org.apache.lucene:lucene-highlighter:9.9.1' + implementation 'org.apache.lucene:lucene-highlighter:9.9.2' implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.10.0' implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.14.0' From 70cc50a8f7b8c72e4cc9796799e2c17e359856fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:40:47 +0000 Subject: [PATCH 13/14] Bump org.junit.jupiter:junit-jupiter from 5.10.1 to 5.10.2 (#10863) Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.10.1 to 5.10.2. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.1...r5.10.2) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4b92e249cf7..0edaf722730 100644 --- a/build.gradle +++ b/build.gradle @@ -241,7 +241,7 @@ dependencies { implementation 'org.jspecify:jspecify:0.3.0' testImplementation 'io.github.classgraph:classgraph:4.8.165' - testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' testImplementation 'org.junit.platform:junit-platform-launcher:1.10.1' testImplementation 'org.mockito:mockito-core:5.10.0' From 7001cb398ff884e9d3b1d84dd03d8aca442a4482 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 15:08:05 +0000 Subject: [PATCH 14/14] Bump org.apache.lucene:lucene-core from 9.9.1 to 9.9.2 (#10860) Bumps org.apache.lucene:lucene-core from 9.9.1 to 9.9.2. --- updated-dependencies: - dependency-name: org.apache.lucene:lucene-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0edaf722730..c7f495b96bb 100644 --- a/build.gradle +++ b/build.gradle @@ -119,7 +119,7 @@ dependencies { exclude group: 'org.junit.jupiter' } - implementation 'org.apache.lucene:lucene-core:9.9.1' + implementation 'org.apache.lucene:lucene-core:9.9.2' implementation 'org.apache.lucene:lucene-queryparser:9.9.2' implementation 'org.apache.lucene:lucene-queries:9.9.1' implementation 'org.apache.lucene:lucene-analysis-common:9.9.1'