Improve discogs cover provider

This commit is contained in:
Jonas Kvinge 2018-12-02 23:29:22 +01:00
parent a8f13a4157
commit 1706ba5765
2 changed files with 157 additions and 102 deletions

View File

@ -25,8 +25,6 @@
#include <QByteArray> #include <QByteArray>
#include <QList> #include <QList>
#include <QPair> #include <QPair>
#include <QMap>
#include <QSet>
#include <QVariant> #include <QVariant>
#include <QString> #include <QString>
#include <QStringBuilder> #include <QStringBuilder>
@ -38,7 +36,8 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QVector> #include <QJsonValue>
#include <QJsonArray>
#include "core/closure.h" #include "core/closure.h"
#include "core/logging.h" #include "core/logging.h"
@ -115,7 +114,6 @@ void DiscogsCoverProvider::SendSearchRequest(DiscogsCoverSearchContext *s_ctx) {
} }
QUrlQuery url_query; QUrlQuery url_query;
QUrl url(kUrlSearch);
QStringList query_items; QStringList query_items;
// Encode the arguments // Encode the arguments
@ -125,6 +123,9 @@ void DiscogsCoverProvider::SendSearchRequest(DiscogsCoverSearchContext *s_ctx) {
url_query.addQueryItem(encoded_arg.first, encoded_arg.second); url_query.addQueryItem(encoded_arg.first, encoded_arg.second);
} }
QUrl url(kUrlSearch);
url.setQuery(url_query);
// Sign the request // Sign the request
const QByteArray data_to_sign = QString("GET\n%1\n%2\n%3").arg(url.host(), url.path(), query_items.join("&")).toUtf8(); const QByteArray data_to_sign = QString("GET\n%1\n%2\n%3").arg(url.host(), url.path(), query_items.join("&")).toUtf8();
const QByteArray signature(Utilities::HmacSha256(QByteArray::fromBase64(kSecretKeyB64), data_to_sign)); const QByteArray signature(Utilities::HmacSha256(QByteArray::fromBase64(kSecretKeyB64), data_to_sign));
@ -132,10 +133,7 @@ void DiscogsCoverProvider::SendSearchRequest(DiscogsCoverSearchContext *s_ctx) {
// Add the signature to the request // Add the signature to the request
url_query.addQueryItem("Signature", QUrl::toPercentEncoding(signature.toBase64())); url_query.addQueryItem("Signature", QUrl::toPercentEncoding(signature.toBase64()));
url.setQuery(url_query);
QNetworkReply *reply = network_->get(QNetworkRequest(url)); QNetworkReply *reply = network_->get(QNetworkRequest(url));
NewClosure(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(SearchRequestError(QNetworkReply::NetworkError, QNetworkReply*, int)), reply, s_ctx->id);
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleSearchReply(QNetworkReply*, int)), reply, s_ctx->id); NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleSearchReply(QNetworkReply*, int)), reply, s_ctx->id);
} }
@ -171,61 +169,147 @@ void DiscogsCoverProvider::SendReleaseRequest(DiscogsCoverSearchContext *s_ctx,
url_query.addQueryItem("Signature", QUrl::toPercentEncoding(signature.toBase64())); url_query.addQueryItem("Signature", QUrl::toPercentEncoding(signature.toBase64()));
url.setQuery(url_query); url.setQuery(url_query);
QNetworkReply *reply = network_->get(QNetworkRequest(url));
NewClosure(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(ReleaseRequestError(QNetworkReply::NetworkError, QNetworkReply*, int, int)), reply, s_ctx->id, r_ctx->id); QNetworkReply *reply = network_->get(QNetworkRequest(url));
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleReleaseReply(QNetworkReply*, int, int)), reply, s_ctx->id, r_ctx->id); NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleReleaseReply(QNetworkReply*, int, int)), reply, s_ctx->id, r_ctx->id);
} }
QByteArray DiscogsCoverProvider::GetReplyData(QNetworkReply *reply) {
QByteArray data;
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
data = reply->readAll();
}
else {
if (reply->error() < 200) {
// This is a network error, there is nothing more to do.
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
Error(failure_reason);
}
else {
// See if there is Json data containing "message" - then use that instead.
data = reply->readAll();
QJsonParseError error;
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
QString failure_reason;
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
QJsonObject json_obj = json_doc.object();
if (json_obj.contains("message")) {
failure_reason = json_obj["message"].toString();
}
else {
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
}
}
else {
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
}
Error(failure_reason);
}
return QByteArray();
}
return data;
}
QJsonObject DiscogsCoverProvider::ExtractJsonObj(const QByteArray &data) {
QJsonParseError error;
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
Error("Reply from server missing Json data.", data);
return QJsonObject();
}
if (json_doc.isNull() || json_doc.isEmpty()) {
Error("Received empty Json document.", json_doc);
return QJsonObject();
}
if (!json_doc.isObject()) {
Error("Json document is not an object.", json_doc);
return QJsonObject();
}
QJsonObject json_obj = json_doc.object();
if (json_obj.isEmpty()) {
Error("Received empty Json object.", json_doc);
return QJsonObject();
}
return json_obj;
}
QJsonValue DiscogsCoverProvider::ExtractData(const QByteArray &data, const QString name, const bool silent) {
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) return QJsonObject();
if (json_obj.contains(name)) {
QJsonValue json_results = json_obj[name];
return json_results;
}
else if (json_obj.contains("message")) {
QString message = json_obj["message"].toString();
Error(QString("%1").arg(message));
}
else {
if (!silent) Error(QString("Json reply is missing \"%1\".").arg(name), json_obj);
}
return QJsonValue();
}
void DiscogsCoverProvider::HandleSearchReply(QNetworkReply *reply, int s_id) { void DiscogsCoverProvider::HandleSearchReply(QNetworkReply *reply, int s_id) {
reply->deleteLater(); reply->deleteLater();
if (!requests_search_.contains(s_id)) { if (!requests_search_.contains(s_id)) {
//qLog(Error) << "Discogs: Got reply for cancelled request: " << s_id; Error(QString("Got reply for cancelled request: %1").arg(s_id));
return; return;
} }
DiscogsCoverSearchContext *s_ctx = requests_search_.value(s_id); DiscogsCoverSearchContext *s_ctx = requests_search_.value(s_id);
QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); QByteArray data = GetReplyData(reply);
if ((json_doc.isNull()) || (!json_doc.isObject())) {
qLog(Error) << "Discogs: Failed to create JSON doc."; QJsonValue json_value = ExtractData(data, "results");
if (!json_value.isArray()) {
EndSearch(s_ctx); EndSearch(s_ctx);
return; return;
} }
QJsonObject json_obj = json_doc.object(); QJsonArray json_results = json_value.toArray();
if (json_obj.isEmpty()) { if (json_results.isEmpty()) {
qLog(Error) << "Discogs: JSON object is empty."; Error("Json array is empty.");
EndSearch(s_ctx); EndSearch(s_ctx);
return; return;
} }
QVariantMap reply_map = json_obj.toVariantMap();
if (!reply_map.contains("results")) {
//qLog(Error) << "Discogs: Search reply from server is missing JSON results.";
//qLog(Error) << "Discogs: Map dump:";
//qLog(Error) << reply_map;
EndSearch(s_ctx);
return;
}
QVariantList results = reply_map["results"].toList();
int i = 0; int i = 0;
for (const QJsonValue &value : json_results) {
for (const QVariant &result : results) { if (!value.isObject()) {
QVariantMap result_map = result.toMap(); Error("Invalid Json reply, data is not an object.", value);
if ((result_map.contains("id")) && (result_map.contains("resource_url"))) { continue;
int r_id = result_map["id"].toInt();
QString title = result_map["title"].toString();
QString resource_url = result_map["resource_url"].toString();
if (resource_url.isEmpty()) continue;
StartRelease(s_ctx, r_id, resource_url);
i++;
} }
QJsonObject json_obj = value.toObject();
if (!json_obj.contains("id") || !json_obj.contains("title") || !json_obj.contains("resource_url")) {
Error("Invalid Json reply, value is missing ID, title or resource_url.", json_obj);
continue;
}
int r_id = json_obj["id"].toInt();
QString title = json_obj["title"].toString();
QString resource_url = json_obj["resource_url"].toString();
if (resource_url.isEmpty()) continue;
StartRelease(s_ctx, r_id, resource_url);
i++;
}
if (i <= 0) {
Error("Ending search with no results.");
EndSearch(s_ctx);
} }
if (i <= 0) EndSearch(s_ctx);
} }
@ -234,88 +318,58 @@ void DiscogsCoverProvider::HandleReleaseReply(QNetworkReply *reply, int s_id, in
reply->deleteLater(); reply->deleteLater();
if (!requests_release_.contains(r_id)) { if (!requests_release_.contains(r_id)) {
//qLog(Error) << "Discogs: Got reply for cancelled request: " << r_id; Error(QString("Got reply for cancelled request: %1 %2").arg(s_id).arg(r_id));
return; return;
} }
DiscogsCoverReleaseContext *r_ctx = requests_release_.value(r_id); DiscogsCoverReleaseContext *r_ctx = requests_release_.value(r_id);
if (!requests_search_.contains(s_id)) { if (!requests_search_.contains(s_id)) {
//qLog(Error) << "Discogs: Got reply for cancelled request: " << s_id << " " << r_id; Error(QString("Got reply for cancelled request: %1 %2").arg(s_id).arg(r_id));
EndSearch(r_ctx); EndSearch(r_ctx);
return; return;
} }
DiscogsCoverSearchContext *s_ctx = requests_search_.value(s_id); DiscogsCoverSearchContext *s_ctx = requests_search_.value(s_id);
QJsonDocument json_doc = QJsonDocument::fromJson(reply->readAll()); QByteArray data = GetReplyData(reply);
if ((json_doc.isNull()) || (!json_doc.isObject())) {
qLog(Error) << "Discogs: Failed to create JSON doc."; QJsonValue json_value = ExtractData(data, "images", true);
if (!json_value.isArray()) {
EndSearch(s_ctx, r_ctx); EndSearch(s_ctx, r_ctx);
return; return;
} }
QJsonObject json_obj = json_doc.object(); QJsonArray json_images = json_value.toArray();
if (json_obj.isEmpty()) { if (json_images.isEmpty()) {
qLog(Error) << "Discogs: JSON object is empty."; Error("Json array is empty.");
EndSearch(s_ctx, r_ctx); EndSearch(s_ctx, r_ctx);
return; return;
} }
QVariantMap reply_map = json_obj.toVariantMap(); int i = 0;
if (!reply_map.contains("images")) { for (const QJsonValue &value : json_images) {
//qLog(Error) << "Discogs: Search reply from server is missing JSON images."; if (!value.isObject()) {
//qLog(Error) << "Discogs: Map dump:"; Error("Invalid Json reply, value is not an object.", value);
//qLog(Error) << reply_map; continue;
EndSearch(s_ctx, r_ctx); }
return; QJsonObject json_obj = value.toObject();
} if (!json_obj.contains("type") || !json_obj.contains("resource_url")) {
Error("Invalid Json reply, value is missing ID or resource_url.", json_obj);
QVariantList results = reply_map["images"].toList(); continue;
}
for (const QVariant &result : results) {
QVariantMap result_map = result.toMap();
CoverSearchResult cover_result; CoverSearchResult cover_result;
cover_result.description = s_ctx->title; cover_result.description = s_ctx->title;
QString type = json_obj["type"].toString();
if (result_map.contains("type")) { i++;
QString type = result_map["type"].toString(); if (type != "primary") {
if (type != "primary") continue; continue;
}
if (result_map.contains("resource_url")) {
cover_result.image_url = QUrl(result_map["resource_url"].toString());
} }
cover_result.image_url = QUrl(json_obj["resource_url"].toString());
if (cover_result.image_url.isEmpty()) continue; if (cover_result.image_url.isEmpty()) continue;
s_ctx->results.append(cover_result); s_ctx->results.append(cover_result);
} }
if (i <= 0) {
EndSearch(s_ctx, r_ctx); Error("Ending search with no results.");
}
void DiscogsCoverProvider::SearchRequestError(QNetworkReply::NetworkError error, QNetworkReply *reply, int s_id) {
if (!requests_search_.contains(s_id)) {
//qLog(Error) << "Discogs: got reply for cancelled request: " << s_id;
return;
} }
DiscogsCoverSearchContext *s_ctx = requests_search_.value(s_id);
EndSearch(s_ctx);
}
void DiscogsCoverProvider::ReleaseRequestError(QNetworkReply::NetworkError error, QNetworkReply *reply, int s_id, int r_id) {
if (!requests_release_.contains(r_id)) {
//qLog(Error) << "Discogs: got reply for cancelled request: " << s_id << r_id;
return;
}
DiscogsCoverReleaseContext *r_ctx = requests_release_.value(r_id);
if (!requests_search_.contains(s_id)) {
EndSearch(r_ctx);
//qLog(Error) << "Discogs: got reply for cancelled request: " << s_id << r_id;
return;
}
DiscogsCoverSearchContext *s_ctx = requests_search_.value(s_id);
EndSearch(s_ctx, r_ctx); EndSearch(s_ctx, r_ctx);
@ -324,19 +378,13 @@ void DiscogsCoverProvider::ReleaseRequestError(QNetworkReply::NetworkError error
void DiscogsCoverProvider::EndSearch(DiscogsCoverSearchContext *s_ctx, DiscogsCoverReleaseContext *r_ctx) { void DiscogsCoverProvider::EndSearch(DiscogsCoverSearchContext *s_ctx, DiscogsCoverReleaseContext *r_ctx) {
delete requests_release_.take(r_ctx->id); delete requests_release_.take(r_ctx->id);
s_ctx->r_count--; s_ctx->r_count--;
//qLog(Debug) << "r_count: " << s_ctx->r_count;
if (s_ctx->r_count <= 0) EndSearch(s_ctx); if (s_ctx->r_count <= 0) EndSearch(s_ctx);
} }
void DiscogsCoverProvider::EndSearch(DiscogsCoverSearchContext *s_ctx) { void DiscogsCoverProvider::EndSearch(DiscogsCoverSearchContext *s_ctx) {
//qLog(Debug) << "Discogs: Finished." << s_ctx->id;
requests_search_.remove(s_ctx->id); requests_search_.remove(s_ctx->id);
emit SearchFinished(s_ctx->id, s_ctx->results); emit SearchFinished(s_ctx->id, s_ctx->results);
delete s_ctx; delete s_ctx;
@ -346,3 +394,8 @@ void DiscogsCoverProvider::EndSearch(DiscogsCoverSearchContext *s_ctx) {
void DiscogsCoverProvider::EndSearch(DiscogsCoverReleaseContext* r_ctx) { void DiscogsCoverProvider::EndSearch(DiscogsCoverReleaseContext* r_ctx) {
delete requests_release_.take(r_ctx->id); delete requests_release_.take(r_ctx->id);
} }
void DiscogsCoverProvider::Error(QString error, QVariant debug) {
qLog(Error) << "Discogs:" << error;
if (debug.isValid()) qLog(Debug) << debug;
}

View File

@ -73,8 +73,6 @@ class DiscogsCoverProvider : public CoverProvider {
void CancelSearch(int id); void CancelSearch(int id);
private slots: private slots:
void SearchRequestError(QNetworkReply::NetworkError error, QNetworkReply *reply, int s_id);
void ReleaseRequestError(QNetworkReply::NetworkError error, QNetworkReply *reply, int s_id, int r_id);
void HandleSearchReply(QNetworkReply *reply, int s_id); void HandleSearchReply(QNetworkReply *reply, int s_id);
void HandleReleaseReply(QNetworkReply *reply, int s_id, int r_id); void HandleReleaseReply(QNetworkReply *reply, int s_id, int r_id);
@ -92,9 +90,13 @@ class DiscogsCoverProvider : public CoverProvider {
void SendSearchRequest(DiscogsCoverSearchContext *s_ctx); void SendSearchRequest(DiscogsCoverSearchContext *s_ctx);
void SendReleaseRequest(DiscogsCoverSearchContext *s_ctx, DiscogsCoverReleaseContext *r_ctx); void SendReleaseRequest(DiscogsCoverSearchContext *s_ctx, DiscogsCoverReleaseContext *r_ctx);
QByteArray GetReplyData(QNetworkReply *reply);
QJsonObject ExtractJsonObj(const QByteArray &data);
QJsonValue ExtractData(const QByteArray &data, const QString name, const bool silent = false);
void EndSearch(DiscogsCoverSearchContext *s_ctx, DiscogsCoverReleaseContext *r_ctx); void EndSearch(DiscogsCoverSearchContext *s_ctx, DiscogsCoverReleaseContext *r_ctx);
void EndSearch(DiscogsCoverSearchContext *s_ctx); void EndSearch(DiscogsCoverSearchContext *s_ctx);
void EndSearch(DiscogsCoverReleaseContext *r_ctx); void EndSearch(DiscogsCoverReleaseContext *r_ctx);
void Error(QString error, QVariant debug = QVariant());
}; };