Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WebSearchPaneViewModel {

private static final Logger LOGGER = LoggerFactory.getLogger(WebSearchPaneViewModel.class);

private final ObjectProperty<SearchBasedFetcher> selectedFetcher = new SimpleObjectProperty<>();
private final ListProperty<SearchBasedFetcher> fetchers = new SimpleListProperty<>(FXCollections.observableArrayList());
Expand Down Expand Up @@ -137,43 +141,58 @@ public StringProperty queryProperty() {
}

public void search() {
String query = getQuery().trim();
LOGGER.debug("Starting web search with query: '{}'", query);

if (!preferences.getImporterPreferences().areImporterEnabled()) {
LOGGER.warn("Web search attempted but importers are disabled");
dialogService.notify(Localization.lang("Web search disabled"));
return;
}

String query = getQuery().trim();
if (StringUtil.isBlank(query)) {
LOGGER.warn("Web search attempted with empty query");
dialogService.notify(Localization.lang("Please enter a search string"));
return;
}
if (stateManager.getActiveDatabase().isEmpty()) {
LOGGER.warn("Web search attempted but no database is open");
dialogService.notify(Localization.lang("Please open or start a new library before searching"));
return;
}

SearchBasedFetcher activeFetcher = getSelectedFetcher();
LOGGER.info("Performing web search using fetcher: {} with query: '{}'",
activeFetcher.getName(), query);

Callable<ParserResult> parserResultCallable;

String fetcherName = activeFetcher.getName();

if (CompositeIdFetcher.containsValidId(query)) {
LOGGER.debug("Query contains valid identifier, using CompositeIdFetcher");
CompositeIdFetcher compositeIdFetcher = new CompositeIdFetcher(preferences.getImportFormatPreferences());
parserResultCallable = () -> new ParserResult(OptionalUtil.toList(compositeIdFetcher.performSearchById(query)));
fetcherName = Localization.lang("Identifier-based Web Search");
} else {
LOGGER.debug("Query is a regular search query, using selected fetcher");
// Exceptions are handled below at "task.onFailure(dialogService::showErrorDialogAndWait)"
parserResultCallable = () -> new ParserResult(activeFetcher.performSearch(query));
}

BackgroundTask<ParserResult> task = BackgroundTask.wrap(parserResultCallable)
.withInitialMessage(Localization.lang("Processing \"%0\"...", query));
task.onFailure(dialogService::showErrorDialogAndWait);
task.onFailure(exception -> {
LOGGER.error("Web search failed for query '{}' using fetcher '{}': {}",
query, activeFetcher.getName(), exception.getMessage());
dialogService.showErrorDialogAndWait(exception);
});

ImportEntriesDialog dialog = new ImportEntriesDialog(stateManager.getActiveDatabase().get(), task, activeFetcher, query);
dialog.setTitle(fetcherName);
dialogService.showCustomDialogAndWait(dialog);

LOGGER.debug("Web search dialog completed for query: '{}'", query);
}

public ValidationStatus queryValidationStatus() {
Expand All @@ -200,16 +219,20 @@ private void updateIdentifierDetection(String queryText) {
if (StringUtil.isBlank(queryText)) {
identifierDetected.set(false);
detectedIdentifierType.set("");
LOGGER.debug("Identifier detection cleared for empty query");
return;
}

Optional<Identifier> identifier = Identifier.from(queryText.trim());
if (identifier.isPresent()) {
String identifierType = getIdentifierTypeName(identifier.get());
identifierDetected.set(true);
detectedIdentifierType.set(getIdentifierTypeName(identifier.get()));
detectedIdentifierType.set(identifierType);
LOGGER.debug("Identifier detected: {} for query: '{}'", identifierType, queryText);
} else {
identifierDetected.set(false);
detectedIdentifierType.set("");
LOGGER.debug("No identifier detected for query: '{}'", queryText);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@
import org.jabref.logic.util.TaskExecutor;

import kong.unirest.core.UnirestException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WebSearchTabViewModel implements PreferenceTabViewModel {

private static final Logger LOGGER = LoggerFactory.getLogger(WebSearchTabViewModel.class);
private final BooleanProperty enableWebSearchProperty = new SimpleBooleanProperty();
private final BooleanProperty warnAboutDuplicatesOnImportProperty = new SimpleBooleanProperty();
private final BooleanProperty shouldDownloadLinkedOnlineFiles = new SimpleBooleanProperty();
Expand Down Expand Up @@ -140,6 +144,8 @@ private void setupPlainCitationParsers(CliPreferences preferences) {

@Override
public void setValues() {
LOGGER.debug("Setting values for WebSearchTabViewModel");

enableWebSearchProperty.setValue(importerPreferences.areImporterEnabled());
warnAboutDuplicatesOnImportProperty.setValue(importerPreferences.shouldWarnAboutDuplicatesOnImport());
shouldDownloadLinkedOnlineFiles.setValue(filePreferences.shouldDownloadLinkedFiles());
Expand All @@ -148,6 +154,11 @@ public void setValues() {
addImportedEntriesGroupName.setValue(libraryPreferences.getAddImportedEntriesGroupName());
defaultPlainCitationParser.setValue(importerPreferences.getDefaultPlainCitationParser());
citationsRelationStoreTTL.setValue(importerPreferences.getCitationsRelationsStoreTTL());

LOGGER.debug("Web search enabled: {}, Duplicate warning: {}, Download linked files: {}",
importerPreferences.areImporterEnabled(),
importerPreferences.shouldWarnAboutDuplicatesOnImport(),
filePreferences.shouldDownloadLinkedFiles());

useCustomDOIProperty.setValue(doiPreferences.isUseCustom());
useCustomDOINameProperty.setValue(doiPreferences.getDefaultBaseURI());
Expand Down Expand Up @@ -192,11 +203,18 @@ public void setValues() {

@Override
public void storeSettings() {
LOGGER.debug("Storing WebSearchTabViewModel settings");

importerPreferences.setImporterEnabled(enableWebSearchProperty.getValue());
importerPreferences.setWarnAboutDuplicatesOnImport(warnAboutDuplicatesOnImportProperty.getValue());
filePreferences.setDownloadLinkedFiles(shouldDownloadLinkedOnlineFiles.getValue());
filePreferences.setKeepDownloadUrl(shouldkeepDownloadUrl.getValue());
libraryPreferences.setAddImportedEntries(addImportedEntries.getValue());

LOGGER.info("Web search settings updated - Enabled: {}, Duplicate warning: {}, Download linked files: {}",
enableWebSearchProperty.getValue(),
warnAboutDuplicatesOnImportProperty.getValue(),
shouldDownloadLinkedOnlineFiles.getValue());
if (addImportedEntriesGroupName.getValue().isEmpty() || addImportedEntriesGroupName.getValue().startsWith(" ")) {
libraryPreferences.setAddImportedEntriesGroupName(Localization.lang("Imported entries"));
} else {
Expand Down Expand Up @@ -300,34 +318,49 @@ public BooleanProperty preferInspireTexkeysProperty() {
}

public void checkApiKey(FetcherViewModel fetcherViewModel, String apiKey, Consumer<Boolean> onFinished) {
LOGGER.debug("Checking API key for fetcher: {}", fetcherViewModel.getName());

Callable<Boolean> tester = () -> {
WebFetcher webFetcher = fetcherViewModel.getFetcher();

if (!(webFetcher instanceof CustomizableKeyFetcher fetcher)) {
LOGGER.warn("Fetcher {} is not a CustomizableKeyFetcher", fetcherViewModel.getName());
return false;
}

String testUrlWithoutApiKey = fetcher.getTestUrl();
if (testUrlWithoutApiKey == null) {
LOGGER.warn("No test URL available for fetcher: {}", fetcherViewModel.getName());
return false;
}

if (apiKey.isEmpty()) {
LOGGER.warn("Empty API key provided for fetcher: {}", fetcherViewModel.getName());
return false;
}

try {
URLDownload urlDownload = new URLDownload(testUrlWithoutApiKey + apiKey);
// The HEAD request cannot be used because its response is not 200 (maybe 404 or 596...).
int statusCode = ((HttpURLConnection) urlDownload.getSource().openConnection()).getResponseCode();
return (statusCode >= 200) && (statusCode < 300);
boolean isValid = (statusCode >= 200) && (statusCode < 300);
LOGGER.debug("API key validation for {} returned status code: {}, valid: {}",
fetcherViewModel.getName(), statusCode, isValid);
return isValid;
} catch (IOException | UnirestException e) {
LOGGER.warn("Error validating API key for fetcher {}: {}", fetcherViewModel.getName(), e.getMessage());
return false;
}
};
BackgroundTask.wrap(tester)
.onSuccess(onFinished)
.onFailure(_ -> onFinished.accept(false))
.onSuccess(result -> {
LOGGER.info("API key validation completed for {}: {}", fetcherViewModel.getName(), result);
onFinished.accept(result);
})
.onFailure(exception -> {
LOGGER.error("API key validation failed for {}: {}", fetcherViewModel.getName(), exception.getMessage());
onFinished.accept(false);
})
.executeWith(taskExecutor);
}

Expand Down