Skip to content

Commit

Permalink
Deduplicate code for mass peak link detection
Browse files Browse the repository at this point in the history
For #199
  • Loading branch information
svetter committed May 16, 2024
1 parent f7e11f5 commit 63cfa19
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 109 deletions.
127 changes: 83 additions & 44 deletions src/dialogs/peak_dialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,32 +364,14 @@ void PeakDialog::handle_findWikiLink() {
}

// Use Google Search API to find Wikipedia link
QString searchString = peakName;
if (regionCombo->currentIndex() > 0) {
const ValidItemID regionID = selectableRegionIDs.at(regionCombo->currentIndex() - 1);
const ItemID rangeID = db.regionsTable.rangeIDColumn.getValueFor(regionID);
if (rangeID.isValid()) {
const QString rangeName = db.rangesTable.nameColumn.getValueFor(FORCE_VALID(rangeID)).toString();
searchString += " " + rangeName;
}
}
const QString sanitizedSearchString = urlSanitize(searchString, "+");
const ValidItemID regionID = selectableRegionIDs.at(regionCombo->currentIndex() - 1);

if (sanitizedSearchString.isEmpty()) {
const QUrl url = createLinkSearchUrl(db, website, peakName, regionID);
if (url.isEmpty()) {
return;
}
wikiFindButton->setEnabled(false);

QUrl url("https://customsearch.googleapis.com/customsearch/v1");
QUrlQuery query;
query.addQueryItem("key", Settings::googleApiKey.get());
query.addQueryItem("cx", "776b1f5ab722c4f75");
query.addQueryItem("q", sanitizedSearchString);
query.addQueryItem("num", "1");
query.addQueryItem("safe", "active");
query.addQueryItem("siteSearch", website);
query.addQueryItem("siteSearchFilter", "i");
url.setQuery(query);
wikiFindButton->setEnabled(false);

QNetworkAccessManager* const networkManager = new QNetworkAccessManager();
connect(networkManager, &QNetworkAccessManager::finished, this, &PeakDialog::handle_wikiLinkSearchResponse);
Expand All @@ -405,36 +387,22 @@ void PeakDialog::handle_findWikiLink() {
*/
void PeakDialog::handle_wikiLinkSearchResponse(QNetworkReply* reply)
{
const QString request = reply->request().url().toString();
const QString response = reply->readAll();
const QString errorString = reply->errorString();
reply->manager()->deleteLater();
reply->deleteLater();
wikiFindButton->setEnabled(true);

// Parse JSON response
const QJsonDocument json = QJsonDocument::fromJson(response.toUtf8());
const QPair<bool, QString> resultPair = parseLinkSearchResponse(reply);
const bool success = resultPair.first;
const QString resultString = resultPair.second;

if (reply->error() != QNetworkReply::NoError) {
// Parse and display error message
const QString errorMessage = json["error"]["message"].toString();
qDebug() << "Google query error:" << errorString << errorMessage;
QMessageBox::critical(this, tr("Google search error"), errorMessage);
wikiFindButton->setEnabled(true);
if (!success) {
QMessageBox::critical(this, tr("Google search error"), resultString);
return;
}

// Check that there is at least one result
if (json["items"].toArray().isEmpty()) {
qDebug() << "No results for query" << request;
wikiFindButton->setEnabled(true);
if (resultString.isEmpty()) {
return;
}

// Get URL of first result
const QString foundURL = json["items"].toArray()[0].toObject()["link"].toString();
wikiLineEdit->setText(foundURL);

wikiFindButton->setEnabled(true);
wikiLineEdit->setText(resultString);
}


Expand Down Expand Up @@ -463,6 +431,77 @@ void PeakDialog::aboutToClose()



/**
* Creates a URL for a Google Programmable Search Engine search for the given peak on the given
* website.
*
* @param db The project database.
* @param website The website to search on, e.g. "en.wikipedia.org".
* @param peakName The name of the peak to search for.
* @param regionID The ID of the region the peak is in.
* @return The URL for the search, or an empty URL if the sanitized search string is empty.
*/
QUrl PeakDialog::createLinkSearchUrl(const Database& db, const QString& website, const QString& peakName, ItemID regionID)
{
QString searchString = peakName;
if (regionID.isValid()) {
const ItemID rangeID = db.regionsTable.rangeIDColumn.getValueFor(FORCE_VALID(regionID));
if (rangeID.isValid()) {
const QString rangeName = db.rangesTable.nameColumn.getValueFor(FORCE_VALID(rangeID)).toString();
searchString += " " + rangeName;
}
}
const QString sanitizedSearchString = urlSanitize(searchString, "+");

if (sanitizedSearchString.isEmpty()) {
return QUrl();
}

QUrl url = QUrl("https://customsearch.googleapis.com/customsearch/v1");
QUrlQuery query = QUrlQuery();
query.addQueryItem("key", Settings::googleApiKey.get());
query.addQueryItem("cx", "776b1f5ab722c4f75");
query.addQueryItem("q", sanitizedSearchString);
query.addQueryItem("num", "1");
query.addQueryItem("safe", "active");
query.addQueryItem("siteSearch", website);
query.addQueryItem("siteSearchFilter", "i");
url.setQuery(query);

return url;
}

QPair<bool, QString> PeakDialog::parseLinkSearchResponse(QNetworkReply* reply)
{
const QString request = reply->request().url().toString();
const QString response = reply->readAll();
const QString errorString = reply->errorString();
reply->manager()->deleteLater();
reply->deleteLater();

// Parse JSON response
const QJsonDocument json = QJsonDocument::fromJson(response.toUtf8());

if (reply->error() != QNetworkReply::NoError) {
// Parse and display error message
const QString errorMessage = json["error"]["message"].toString();
qDebug() << "Google query error:" << errorString << errorMessage;
return {false, errorMessage};
}

// Check that there is at least one result
if (json["items"].toArray().isEmpty()) {
qDebug() << "No results for query" << request;
return {true, QString()};
}

// Get URL of first result
const QString result = json["items"].toArray()[0].toObject()["link"].toString();
return {true, result};
}



/**
* Sanitizes a string for use in a URL.
*
Expand Down
3 changes: 3 additions & 0 deletions src/dialogs/peak_dialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ private slots:
virtual void aboutToClose() override;

public:
static QUrl createLinkSearchUrl(const Database& db, const QString& website, const QString& peakName, ItemID regionID);
static QPair<bool, QString> parseLinkSearchResponse(QNetworkReply* reply);

static QString urlSanitize(const QString& string, QString spaceReplacement = "+");
};

Expand Down
116 changes: 51 additions & 65 deletions src/tools/peak_link_finder_thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,69 +110,8 @@ void PeakLinkFinderThread::run()
wikiLink = "https://" + website + "/wiki/" + sanitizedPeakName;
}
else {
QString searchString = peakName;
if (peak->regionID.isValid()) {
const ItemID rangeID = db.regionsTable.rangeIDColumn.getValueFor(FORCE_VALID(peak->regionID));
if (rangeID.isValid()) {
const QString rangeName = db.rangesTable.nameColumn.getValueFor(FORCE_VALID(rangeID)).toString();
searchString += " " + rangeName;
}
}
const QString sanitizedSearchString = PeakDialog::urlSanitize(searchString, "+");

QUrl url("https://customsearch.googleapis.com/customsearch/v1");
QUrlQuery query;
query.addQueryItem("key", apiKey);
query.addQueryItem("cx", "776b1f5ab722c4f75");
query.addQueryItem("q", sanitizedSearchString);
query.addQueryItem("num", "1");
query.addQueryItem("safe", "active");
query.addQueryItem("siteSearch", website);
query.addQueryItem("siteSearchFilter", "i");
url.setQuery(query);

QNetworkAccessManager* const networkManager = new QNetworkAccessManager();

bool waiting = true;
QNetworkReply* reply = nullptr;
connect(networkManager, &QNetworkAccessManager::finished, [&waiting, &reply](QNetworkReply* incomingReply) {
reply = incomingReply;
waiting = false;
});

// Send network request and wait for response
networkManager->get(QNetworkRequest(url));
while (waiting) {
QCoreApplication::processEvents();
}
assert(reply);

const QString request = reply->request().url().toString();
const QString response = reply->readAll();
const QString errorString = reply->errorString();
reply->manager()->deleteLater();
reply->deleteLater();

// Parse JSON response
const QJsonDocument json = QJsonDocument::fromJson(response.toUtf8());

if (reply->error() != QNetworkReply::NoError) {
// Parse and display error message
const QString errorMessage = json["error"]["message"].toString();
qDebug() << "Google query error:" << errorString << errorMessage;
QMessageBox::critical(parent, tr("Google search error"), errorMessage);
abort();
continue;
}

// Check that there is at least one result
if (json["items"].toArray().isEmpty()) {
qDebug() << "No results for query" << request;
continue;
}

// Get URL of first result
wikiLink = json["items"].toArray()[0].toObject()["link"].toString();
wikiLink = searchForLink(*peak, website);
if (abortWasCalled) break;
}
}

Expand All @@ -183,11 +122,58 @@ void PeakLinkFinderThread::run()
}
}



/**
* Gracefully aborts the thread.
*/
void PeakLinkFinderThread::abort() {
abortWasCalled = true;
}



/**
* Searches for a link to the given peak on the given website.
*
* Triggers an abort if an error occurs during the search.
*
* @param peak The peak to search for.
* @param website The website to search on.
* @return The found link, or an empty string if no link was found.
*/
QString PeakLinkFinderThread::searchForLink(const Peak& peak, const QString website)
{
const QUrl url = PeakDialog::createLinkSearchUrl(db, website, peak.name, peak.regionID);

QNetworkAccessManager* const networkManager = new QNetworkAccessManager();

bool waiting = true;
QNetworkReply* reply = nullptr;
auto runWhenFinished = [&waiting, &reply](QNetworkReply* incomingReply) {
reply = incomingReply;
waiting = false;
};
connect(networkManager, &QNetworkAccessManager::finished, runWhenFinished);

// Send network request and wait for response
networkManager->get(QNetworkRequest(url));
while (waiting) {
QCoreApplication::processEvents();
}
assert(reply);

const QPair<bool, QString> resultPair = PeakDialog::parseLinkSearchResponse(reply);
const bool success = resultPair.first;
const QString resultString = resultPair.second;

if (!success) {
QMessageBox::critical(parent, tr("Google search error"), resultString);
abort();
return QString();
}

if (resultString.isEmpty()) {
return QString();
}

return resultString;
}
3 changes: 3 additions & 0 deletions src/tools/peak_link_finder_thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ class PeakLinkFinderThread : public QThread
void run() override;
void abort();

private:
QString searchForLink(const Peak& peak, const QString website);

signals:
/**
* Emitted when the thread has started and determined the number of peaks to process.
Expand Down

0 comments on commit 63cfa19

Please sign in to comment.