-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Fix issue #11189 - Implement a caching solution with local storage for citation relations #11845
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
3785cb1
8048da8
18db75e
0e9de3c
9a31735
b1133d0
6a8b21b
01f6da4
337780d
7c2e32d
187b5d4
b71d9fc
dda21ed
077e9ca
0bff913
ea36f1a
d7f9c2d
97b38c6
54e7e21
6415b1b
e18d557
57b5c02
16cfd2c
33f5cdf
2ebf62e
6861ae2
393b133
e8e223d
5727ecb
0cdf495
48c48ad
7af20c3
eb3dfd8
14ab672
061bb89
76b2289
a36acd4
8893936
37354f5
b3e1746
24c1707
d3cac8b
deab9b5
e40637b
543c48b
a2d3856
31cd171
2d31d0f
5bdc850
c03d1f5
4b33bfa
d82ab8c
e9ac90e
10a1d6a
033b296
59a2d24
4ffd652
7a81e73
70895c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,17 +36,20 @@ | |
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.preferences.GuiPreferences; | ||
import org.jabref.gui.util.NoSelectionModel; | ||
import org.jabref.gui.util.ViewModelListCellFactory; | ||
import org.jabref.logic.bibtex.BibEntryWriter; | ||
import org.jabref.logic.bibtex.FieldPreferences; | ||
import org.jabref.logic.bibtex.FieldWriter; | ||
import org.jabref.logic.citation.repository.LRUBibEntryRelationsCache; | ||
import org.jabref.logic.citation.repository.LRUBibEntryRelationsRepository; | ||
import org.jabref.logic.citation.service.SearchCitationsRelationsService; | ||
import org.jabref.logic.database.DuplicateCheck; | ||
import org.jabref.logic.exporter.BibWriter; | ||
import org.jabref.logic.importer.fetcher.CitationFetcher; | ||
import org.jabref.logic.importer.fetcher.SemanticScholarCitationFetcher; | ||
import org.jabref.logic.l10n.Localization; | ||
import org.jabref.logic.os.OS; | ||
import org.jabref.logic.util.BackgroundTask; | ||
|
@@ -85,7 +88,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; | ||
|
@@ -109,9 +112,22 @@ 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); | ||
var bibEntryRelationsRepository = new LRUBibEntryRelationsRepository( | ||
new LRUBibEntryRelationsCache() | ||
); | ||
this.searchCitationsRelationsService = new SearchCitationsRelationsService( | ||
new SemanticScholarCitationFetcher(preferences.getImporterPreferences()), | ||
bibEntryRelationsRepository | ||
); | ||
citationsRelationsTabViewModel = new CitationsRelationsTabViewModel( | ||
databaseContext, | ||
preferences, | ||
undoManager, | ||
stateManager, | ||
dialogService, | ||
fileUpdateMonitor, | ||
taskExecutor | ||
); | ||
} | ||
|
||
/** | ||
|
@@ -405,41 +421,54 @@ private void searchForRelations(BibEntry entry, CheckListView<CitationRelationIt | |
citedByTask.cancel(); | ||
} | ||
|
||
BackgroundTask<List<BibEntry>> 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, shouldRefresh) | ||
.consumeOnRunning(task -> prepareToSearchForRelations( | ||
alexandre-cremieux marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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); | ||
InAnYan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
hideNodes(abortButton, progress, importButton); | ||
listView.setPlaceholder(new Label(Localization.lang("Error while fetching citing entries: %0", | ||
exception.getMessage()))); | ||
|
||
listView.setPlaceholder( | ||
new Label(Localization.lang( | ||
"Error while fetching citing entries: %0", exception.getMessage()) | ||
) | ||
alexandre-cremieux marked this conversation as resolved.
Show resolved
Hide resolved
|
||
); | ||
refreshButton.setVisible(true); | ||
dialogService.notify(exception.getMessage()); | ||
}) | ||
.executeWith(taskExecutor); | ||
} | ||
|
||
private BackgroundTask<List<BibEntry>> createBackGroundTask( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo: Background (without uppercase G) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The highlighted TODO: Which benefts are this - and why should it implemetned lateR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This TODO follows a discussion we had earlier that you can read here: #11845 (comment) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But what stops you from returning a runnable? If it's used in many places and that makes you rewrite a lot of code to include There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just in theory, this could be needed to keep the level of abstration. Should be decided on where the method is actually used. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
As mentioned, this improvement could indeed imply several code changes (in this class):
At least, I think we agree that this should not be part of this PR. As I saw that the current implementation could be improved (stopping a task that maybe shouldn't be stopped, avoid same task but different executors for each instances of the tab, etc). I proposed to take a look to this after merging this PR. Reason why the TODO is there (after Oliver's accepted that at that time). But I can remove completely the TODO also if it bother you (and I understand that cause I do not like TODO also). However, I already have another to issue opened for which I volunteered related to same tab... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe just add a link your reasoning in your comment in the to-do? |
||
BibEntry entry, CitationFetcher.SearchType searchType, boolean shouldRefresh | ||
) { | ||
return switch (searchType) { | ||
case CitationFetcher.SearchType.CITES -> { | ||
citingTask = BackgroundTask.wrap( | ||
alexandre-cremieux marked this conversation as resolved.
Show resolved
Hide resolved
|
||
() -> this.searchCitationsRelationsService.searchReferences(entry, shouldRefresh) | ||
); | ||
yield citingTask; | ||
} | ||
case CitationFetcher.SearchType.CITED_BY -> { | ||
citedByTask = BackgroundTask.wrap( | ||
() -> this.searchCitationsRelationsService.searchCitations(entry, shouldRefresh) | ||
); | ||
yield citedByTask; | ||
} | ||
}; | ||
} | ||
|
||
private void onSearchForRelationsSucceed(BibEntry entry, CheckListView<CitationRelationItem> listView, | ||
Button abortButton, Button refreshButton, | ||
CitationFetcher.SearchType searchType, Button importButton, | ||
|
@@ -456,7 +485,7 @@ private void onSearchForRelationsSucceed(BibEntry entry, CheckListView<CitationR | |
.map(localEntry -> new CitationRelationItem(entr, localEntry, true)) | ||
.orElseGet(() -> new CitationRelationItem(entr, false))) | ||
.toList() | ||
); | ||
); | ||
|
||
if (!observableList.isEmpty()) { | ||
listView.refresh(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package org.jabref.logic.citation.repository; | ||
|
||
import java.util.List; | ||
|
||
import org.jabref.model.entry.BibEntry; | ||
|
||
public interface BibEntryRelationsRepository { | ||
alexandre-cremieux marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
void insertCitations(BibEntry entry, List<BibEntry> citations); | ||
|
||
List<BibEntry> readCitations(BibEntry entry); | ||
|
||
boolean containsCitations(BibEntry entry); | ||
|
||
void insertReferences(BibEntry entry, List<BibEntry> citations); | ||
|
||
List<BibEntry> readReferences(BibEntry entry); | ||
|
||
boolean containsReferences(BibEntry entry); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package org.jabref.logic.citation.repository; | ||
|
||
import java.util.LinkedHashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
import org.jabref.model.entry.BibEntry; | ||
import org.jabref.model.entry.identifier.DOI; | ||
|
||
import org.eclipse.jgit.util.LRUMap; | ||
|
||
public class LRUBibEntryRelationsCache { | ||
private static final Integer MAX_CACHED_ENTRIES = 100; | ||
private static final Map<DOI, Set<BibEntry>> CITATIONS_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES); | ||
private static final Map<DOI, Set<BibEntry>> REFERENCES_MAP = new LRUMap<>(MAX_CACHED_ENTRIES, MAX_CACHED_ENTRIES); | ||
|
||
public List<BibEntry> getCitations(BibEntry entry) { | ||
return entry | ||
alexandre-cremieux marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.getDOI() | ||
.stream() | ||
.flatMap(doi -> CITATIONS_MAP.getOrDefault(doi, Set.of()).stream()) | ||
.toList(); | ||
} | ||
|
||
public List<BibEntry> getReferences(BibEntry entry) { | ||
return entry | ||
.getDOI() | ||
.stream() | ||
.flatMap(doi -> REFERENCES_MAP.getOrDefault(doi, Set.of()).stream()) | ||
.toList(); | ||
} | ||
|
||
public void cacheOrMergeCitations(BibEntry entry, List<BibEntry> citations) { | ||
alexandre-cremieux marked this conversation as resolved.
Show resolved
Hide resolved
|
||
entry.getDOI().ifPresent(doi -> { | ||
var cachedRelations = CITATIONS_MAP.getOrDefault(doi, new LinkedHashSet<>()); | ||
cachedRelations.addAll(citations); | ||
CITATIONS_MAP.put(doi, cachedRelations); | ||
}); | ||
} | ||
|
||
public void cacheOrMergeReferences(BibEntry entry, List<BibEntry> references) { | ||
entry.getDOI().ifPresent(doi -> { | ||
var cachedRelations = REFERENCES_MAP.getOrDefault(doi, new LinkedHashSet<>()); | ||
cachedRelations.addAll(references); | ||
REFERENCES_MAP.put(doi, cachedRelations); | ||
}); | ||
} | ||
|
||
public boolean citationsCached(BibEntry entry) { | ||
return entry.getDOI().map(CITATIONS_MAP::containsKey).orElse(false); | ||
} | ||
|
||
public boolean referencesCached(BibEntry entry) { | ||
return entry.getDOI().map(REFERENCES_MAP::containsKey).orElse(false); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.