diff --git a/CHANGELOG.md b/CHANGELOG.md index d114e148b6f..99caa4130c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,12 +14,14 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added a new CLI that supports txt, csv, and console-based output for consistency in BibTeX entries. [#11984](https://github.com/JabRef/jabref/issues/11984) - We added a new dialog for bibliography consistency check. [#11950](https://github.com/JabRef/jabref/issues/11950) - We added a feature for copying entries to libraries, available via the context menu, with an option to include cross-references. [#12374](https://github.com/JabRef/jabref/pull/12374) +- We introduced a settings parameters to manage citations' relations local storage time-to-live with a default value set to 30 days. [#11189](https://github.com/JabRef/jabref/issues/11189) ### Changed - We moved the "Generate a new key for imported entries" option from the "Web search" tab to the "Citation key generator" tab in preferences. [#12436](https://github.com/JabRef/jabref/pull/12436) - We improved the offline parsing of BibTeX data from PDF-documents. [#12278](https://github.com/JabRef/jabref/issues/12278) - The tab bar is now hidden when only one library is open. [#9971](https://github.com/JabRef/jabref/issues/9971) +- We improved the citations relations caching by implementing a two levels cache aside strategy including offline storage. [#11189](https://github.com/JabRef/jabref/issues/11189) - When working with CSL styles in LibreOffice, citing with a new style now updates all other citations in the document to have the currently selected style. [#12472](https://github.com/JabRef/jabref/pull/12472) - We improved the user comments field visibility so that it remains displayed if it contains text. Additionally, users can now easily toggle the field on or off via buttons unless disabled in preferences. [#11021](https://github.com/JabRef/jabref/issues/11021) - The LibreOffice integration for CSL styles is now more performant. [#12472](https://github.com/JabRef/jabref/pull/12472) diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index b85b2437d8f..680d48bdeab 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -28,6 +28,7 @@ import org.jabref.gui.util.UiTaskExecutor; import org.jabref.logic.UiCommand; import org.jabref.logic.ai.AiService; +import org.jabref.logic.citation.SearchCitationsRelationsService; import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.ProxyRegisterer; import org.jabref.logic.os.OS; @@ -173,6 +174,9 @@ public void initialize() { dialogService, taskExecutor); Injector.setModelOrService(AiService.class, aiService); + + var searchCitationsRelationsService = new SearchCitationsRelationsService(preferences.getImporterPreferences()); + Injector.setModelOrService(SearchCitationsRelationsService.class, searchCitationsRelationsService); } private void setupProxy() { diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index f36a42aeef0..3fe492c7749 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -52,6 +52,7 @@ import org.jabref.gui.util.UiTaskExecutor; import org.jabref.logic.ai.AiService; import org.jabref.logic.bibtex.TypedBibEntry; +import org.jabref.logic.citation.SearchCitationsRelationsService; import org.jabref.logic.help.HelpFile; import org.jabref.logic.importer.EntryBasedFetcher; import org.jabref.logic.importer.WebFetchers; @@ -123,6 +124,7 @@ public class EntryEditor extends BorderPane { @Inject private KeyBindingRepository keyBindingRepository; @Inject private JournalAbbreviationRepository journalAbbreviationRepository; @Inject private AiService aiService; + @Inject private SearchCitationsRelationsService searchCitationsRelationsService; private final List allPossibleTabs; @@ -305,8 +307,13 @@ private List createTabs() { tabs.add(new MathSciNetTab()); tabs.add(new FileAnnotationTab(libraryTab.getAnnotationCache())); tabs.add(new SciteTab(preferences, taskExecutor, dialogService)); - tabs.add(new CitationRelationsTab(dialogService, databaseContext, - undoManager, stateManager, fileMonitor, preferences, libraryTab, taskExecutor, bibEntryTypesManager)); + tabs.add(new CitationRelationsTab( + dialogService, databaseContext, + undoManager, stateManager, + fileMonitor, preferences, + libraryTab, taskExecutor, + bibEntryTypesManager, searchCitationsRelationsService + )); tabs.add(new RelatedArticlesTab(buildInfo, databaseContext, preferences, dialogService, taskExecutor)); sourceTab = new SourceTab( databaseContext, diff --git a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/BibEntryRelationsCache.java b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/BibEntryRelationsCache.java deleted file mode 100644 index de2f832b48f..00000000000 --- a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/BibEntryRelationsCache.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.jabref.gui.entryeditor.citationrelationtab; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.identifier.DOI; - -import org.eclipse.jgit.util.LRUMap; - -public class BibEntryRelationsCache { - private static final Integer MAX_CACHED_ENTRIES = 100; - private static final Map> CITATIONS_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES); - private static final Map> REFERENCES_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES); - - public List getCitations(BibEntry entry) { - return CITATIONS_MAP.getOrDefault(entry.getDOI().map(DOI::asString).orElse(""), Collections.emptyList()); - } - - public List getReferences(BibEntry entry) { - return REFERENCES_MAP.getOrDefault(entry.getDOI().map(DOI::asString).orElse(""), Collections.emptyList()); - } - - public void cacheOrMergeCitations(BibEntry entry, List citations) { - entry.getDOI().ifPresent(doi -> CITATIONS_MAP.put(doi.asString(), citations)); - } - - public void cacheOrMergeReferences(BibEntry entry, List references) { - entry.getDOI().ifPresent(doi -> REFERENCES_MAP.putIfAbsent(doi.asString(), references)); - } - - public boolean citationsCached(BibEntry entry) { - return CITATIONS_MAP.containsKey(entry.getDOI().map(DOI::asString).orElse("")); - } - - public boolean referencesCached(BibEntry entry) { - return REFERENCES_MAP.containsKey(entry.getDOI().map(DOI::asString).orElse("")); - } -} diff --git a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/BibEntryRelationsRepository.java b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/BibEntryRelationsRepository.java deleted file mode 100644 index f7f29052da2..00000000000 --- a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/BibEntryRelationsRepository.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.jabref.gui.entryeditor.citationrelationtab; - -import java.util.List; - -import org.jabref.gui.entryeditor.citationrelationtab.semanticscholar.SemanticScholarFetcher; -import org.jabref.logic.importer.FetcherException; -import org.jabref.model.entry.BibEntry; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class BibEntryRelationsRepository { - private static final Logger LOGGER = LoggerFactory.getLogger(BibEntryRelationsRepository.class); - - private final SemanticScholarFetcher fetcher; - private final BibEntryRelationsCache cache; - - public BibEntryRelationsRepository(SemanticScholarFetcher fetcher, BibEntryRelationsCache cache) { - this.fetcher = fetcher; - this.cache = cache; - } - - public List getCitations(BibEntry entry) { - if (needToRefreshCitations(entry)) { - forceRefreshCitations(entry); - } - - return cache.getCitations(entry); - } - - public List getReferences(BibEntry entry) { - if (needToRefreshReferences(entry)) { - List references; - try { - references = fetcher.searchCiting(entry); - } catch (FetcherException e) { - LOGGER.error("Error while fetching references", e); - references = List.of(); - } - cache.cacheOrMergeReferences(entry, references); - } - - return cache.getReferences(entry); - } - - public void forceRefreshCitations(BibEntry entry) { - try { - List citations = fetcher.searchCitedBy(entry); - cache.cacheOrMergeCitations(entry, citations); - } catch (FetcherException e) { - LOGGER.error("Error while fetching citations", e); - } - } - - public boolean needToRefreshCitations(BibEntry entry) { - return !cache.citationsCached(entry); - } - - public boolean needToRefreshReferences(BibEntry entry) { - return !cache.referencesCached(entry); - } - - public void forceRefreshReferences(BibEntry entry) { - List references; - try { - references = fetcher.searchCiting(entry); - } catch (FetcherException e) { - LOGGER.error("Error while fetching references", e); - references = List.of(); - } - cache.cacheOrMergeReferences(entry, references); - } -} 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 97c31f96234..362dae09c16 100644 --- a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationRelationsTab.java @@ -37,8 +37,6 @@ import org.jabref.gui.collab.entrychange.PreviewWithSourceTab; import org.jabref.gui.desktop.os.NativeDesktop; import org.jabref.gui.entryeditor.EntryEditorTab; -import org.jabref.gui.entryeditor.citationrelationtab.semanticscholar.CitationFetcher; -import org.jabref.gui.entryeditor.citationrelationtab.semanticscholar.SemanticScholarFetcher; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.mergeentries.EntriesMergeResult; import org.jabref.gui.mergeentries.MergeEntriesDialog; @@ -51,8 +49,10 @@ import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.bibtex.FieldWriter; +import org.jabref.logic.citation.SearchCitationsRelationsService; import org.jabref.logic.database.DuplicateCheck; import org.jabref.logic.exporter.BibWriter; +import org.jabref.logic.importer.fetcher.CitationFetcher; import org.jabref.logic.l10n.Localization; import org.jabref.logic.os.OS; import org.jabref.logic.util.BackgroundTask; @@ -92,7 +92,7 @@ public class CitationRelationsTab extends EntryEditorTab { private final GuiPreferences preferences; private final LibraryTab libraryTab; private final TaskExecutor taskExecutor; - private final BibEntryRelationsRepository bibEntryRelationsRepository; + private final SearchCitationsRelationsService searchCitationsRelationsService; private final CitationsRelationsTabViewModel citationsRelationsTabViewModel; private final DuplicateCheck duplicateCheck; private final BibEntryTypesManager entryTypesManager; @@ -107,7 +107,8 @@ public CitationRelationsTab(DialogService dialogService, GuiPreferences preferences, LibraryTab libraryTab, TaskExecutor taskExecutor, - BibEntryTypesManager bibEntryTypesManager) { + BibEntryTypesManager bibEntryTypesManager, + SearchCitationsRelationsService searchCitationsRelationsService) { this.dialogService = dialogService; this.databaseContext = databaseContext; this.preferences = preferences; @@ -121,9 +122,17 @@ public CitationRelationsTab(DialogService dialogService, this.entryTypesManager = bibEntryTypesManager; this.duplicateCheck = new DuplicateCheck(entryTypesManager); - this.bibEntryRelationsRepository = new BibEntryRelationsRepository(new SemanticScholarFetcher(preferences.getImporterPreferences()), - new BibEntryRelationsCache()); - citationsRelationsTabViewModel = new CitationsRelationsTabViewModel(databaseContext, preferences, undoManager, stateManager, dialogService, fileUpdateMonitor, taskExecutor); + this.searchCitationsRelationsService = searchCitationsRelationsService; + + this.citationsRelationsTabViewModel = new CitationsRelationsTabViewModel( + databaseContext, + preferences, + undoManager, + stateManager, + dialogService, + fileUpdateMonitor, + taskExecutor + ); } /** @@ -197,18 +206,13 @@ private SplitPane getPaneAndStartSearch(BibEntry entry) { citingVBox.getChildren().addAll(citingHBox, citingListView); citedByVBox.getChildren().addAll(citedByHBox, citedByListView); - refreshCitingButton.setOnMouseClicked(event -> searchForRelations( - entry, - citingListView, - abortCitingButton, - refreshCitingButton, - CitationFetcher.SearchType.CITES, - importCitingButton, - citingProgress, - true)); + refreshCitingButton.setOnMouseClicked(event -> { + searchForRelations(entry, citingListView, abortCitingButton, + refreshCitingButton, CitationFetcher.SearchType.CITES, importCitingButton, citingProgress); + }); refreshCitedByButton.setOnMouseClicked(event -> searchForRelations(entry, citedByListView, abortCitedButton, - refreshCitedByButton, CitationFetcher.SearchType.CITED_BY, importCitedByButton, citedByProgress, true)); + refreshCitedByButton, CitationFetcher.SearchType.CITED_BY, importCitedByButton, citedByProgress)); // Create SplitPane to hold all nodes above SplitPane container = new SplitPane(citingVBox, citedByVBox); @@ -216,10 +220,10 @@ private SplitPane getPaneAndStartSearch(BibEntry entry) { styleFetchedListView(citingListView); searchForRelations(entry, citingListView, abortCitingButton, refreshCitingButton, - CitationFetcher.SearchType.CITES, importCitingButton, citingProgress, false); + CitationFetcher.SearchType.CITES, importCitingButton, citingProgress); searchForRelations(entry, citedByListView, abortCitedButton, refreshCitedByButton, - CitationFetcher.SearchType.CITED_BY, importCitedByButton, citedByProgress, false); + CitationFetcher.SearchType.CITED_BY, importCitedByButton, citedByProgress); return container; } @@ -411,7 +415,7 @@ protected void bindToEntry(BibEntry entry) { */ private void searchForRelations(BibEntry entry, CheckListView listView, Button abortButton, Button refreshButton, CitationFetcher.SearchType searchType, Button importButton, - ProgressIndicator progress, boolean shouldRefresh) { + ProgressIndicator progress) { if (entry.getDOI().isEmpty()) { hideNodes(abortButton, progress); showNodes(refreshButton); @@ -425,47 +429,61 @@ private void searchForRelations(BibEntry entry, CheckListView> task; - - if (searchType == CitationFetcher.SearchType.CITES) { - task = BackgroundTask.wrap(() -> { - if (shouldRefresh) { - bibEntryRelationsRepository.forceRefreshReferences(entry); - } - return bibEntryRelationsRepository.getReferences(entry); - }); - citingTask = task; - } else { - task = BackgroundTask.wrap(() -> { - if (shouldRefresh) { - bibEntryRelationsRepository.forceRefreshCitations(entry); - } - return bibEntryRelationsRepository.getCitations(entry); - }); - citedByTask = task; - } - - task.onRunning(() -> prepareToSearchForRelations(abortButton, refreshButton, importButton, progress, task)) - .onSuccess(fetchedList -> onSearchForRelationsSucceed(entry, listView, abortButton, refreshButton, - searchType, importButton, progress, fetchedList, observableList)) + this.createBackgroundTask(entry, searchType) + .consumeOnRunning(task -> prepareToSearchForRelations( + abortButton, refreshButton, importButton, progress, task + )) + .onSuccess(fetchedList -> onSearchForRelationsSucceed( + entry, + listView, + abortButton, + refreshButton, + searchType, + importButton, + progress, + fetchedList, + observableList + )) .onFailure(exception -> { LOGGER.error("Error while fetching citing Articles", exception); hideNodes(abortButton, progress, importButton); listView.setPlaceholder(new Label(Localization.lang("Error while fetching citing entries: %0", exception.getMessage()))); - refreshButton.setVisible(true); dialogService.notify(exception.getMessage()); }) .executeWith(taskExecutor); } + /** + * TODO: Make the method return a callable and let the calling method create the background task. + */ + private BackgroundTask> createBackgroundTask( + BibEntry entry, CitationFetcher.SearchType searchType + ) { + return switch (searchType) { + case CitationFetcher.SearchType.CITES -> { + citingTask = BackgroundTask.wrap( + () -> this.searchCitationsRelationsService.searchReferences(entry) + ); + yield citingTask; + } + case CitationFetcher.SearchType.CITED_BY -> { + citedByTask = BackgroundTask.wrap( + () -> this.searchCitationsRelationsService.searchCitations(entry) + ); + yield citedByTask; + } + }; + } + private void onSearchForRelationsSucceed(BibEntry entry, CheckListView listView, Button abortButton, Button refreshButton, CitationFetcher.SearchType searchType, Button importButton, @@ -482,7 +500,7 @@ private void onSearchForRelationsSucceed(BibEntry entry, CheckListView new CitationRelationItem(entr, localEntry, true)) .orElseGet(() -> new CitationRelationItem(entr, false))) .toList() - ); + ); if (!observableList.isEmpty()) { listView.refresh(); diff --git a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java index be404997d09..e413aff6458 100644 --- a/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java +++ b/src/main/java/org/jabref/gui/entryeditor/citationrelationtab/CitationsRelationsTabViewModel.java @@ -7,10 +7,10 @@ import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; -import org.jabref.gui.entryeditor.citationrelationtab.semanticscholar.CitationFetcher; import org.jabref.gui.externalfiles.ImportHandler; import org.jabref.gui.preferences.GuiPreferences; import org.jabref.logic.citationkeypattern.CitationKeyGenerator; +import org.jabref.logic.importer.fetcher.CitationFetcher; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; diff --git a/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.fxml b/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.fxml index 86fc1471ee9..adfb1253f28 100644 --- a/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.fxml +++ b/src/main/java/org/jabref/gui/preferences/websearch/WebSearchTab.fxml @@ -25,6 +25,10 @@