Improve Last.fm cover provider code

This commit is contained in:
Jonas Kvinge
2020-04-23 21:03:36 +02:00
parent 2b2b4dbcf4
commit 48bc1f8361
2 changed files with 122 additions and 81 deletions

View File

@@ -19,6 +19,8 @@
#include "config.h" #include "config.h"
#include <algorithm>
#include <QtGlobal> #include <QtGlobal>
#include <QObject> #include <QObject>
#include <QLocale> #include <QLocale>
@@ -40,7 +42,6 @@
#include <QtDebug> #include <QtDebug>
#include "core/application.h" #include "core/application.h"
#include "core/closure.h"
#include "core/network.h" #include "core/network.h"
#include "core/logging.h" #include "core/logging.h"
@@ -56,23 +57,34 @@ LastFmCoverProvider::LastFmCoverProvider(Application *app, QObject *parent) : Co
bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) { bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) {
Q_UNUSED(title);
typedef QPair<QString, QString> Param; typedef QPair<QString, QString> Param;
typedef QPair<QByteArray, QByteArray> EncodedParam;
typedef QList<Param> ParamList; typedef QList<Param> ParamList;
ParamList params = ParamList() QString method;
<< Param("album", QString(artist + " " + album)) QString type;
<< Param("api_key", kApiKey) QString query;
<< Param("lang", QLocale().name().left(2).toLower()) if (album.isEmpty()) {
<< Param("method", "album.search"); method = "track.search";
type = "track";
query = artist + " " + title;
}
else {
method = "album.search";
type = "album";
query = artist + " " + album;
}
ParamList params = ParamList() << Param("api_key", kApiKey)
<< Param("lang", QLocale().name().left(2).toLower())
<< Param("method", method)
<< Param(type, query);
std::sort(params.begin(), params.end());
QUrlQuery url_query; QUrlQuery url_query;
QString data_to_sign; QString data_to_sign;
for (const Param &param : params) { for (const Param &param : params) {
EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); url_query.addQueryItem(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second));
url_query.addQueryItem(encoded_param.first, encoded_param.second);
data_to_sign += param.first + param.second; data_to_sign += param.first + param.second;
} }
data_to_sign += kSecret; data_to_sign += kSecret;
@@ -88,13 +100,13 @@ bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &albu
req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply *reply = network_->post(req, url_query.toString(QUrl::FullyEncoded).toUtf8()); QNetworkReply *reply = network_->post(req, url_query.toString(QUrl::FullyEncoded).toUtf8());
NewClosure(reply, SIGNAL(finished()), this, SLOT(QueryFinished(QNetworkReply*, int)), reply, id); connect(reply, &QNetworkReply::finished, [=] { QueryFinished(reply, id, type); });
return true; return true;
} }
void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, const int id) { void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, const int id, const QString &type) {
reply->deleteLater(); reply->deleteLater();
@@ -106,92 +118,137 @@ void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, const int id) {
return; return;
} }
QJsonValue json_value = ExtractResults(data); QJsonObject json_obj = ExtractJsonObj(data);
if (!json_value.isObject()) { if (json_obj.isEmpty()) {
emit SearchFinished(id, results); emit SearchFinished(id, results);
return; return;
} }
QJsonObject json_results = json_value.toObject(); QJsonValue value_results;
if (json_results.isEmpty()) { if (json_obj.contains("results")) {
Error("Json object is empty.", json_value); value_results = json_obj["results"];
}
else if (json_obj.contains("error") && json_obj.contains("message")) {
int error = json_obj["error"].toInt();
QString message = json_obj["message"].toString();
Error(QString("Error: %1: %2").arg(QString::number(error)).arg(message));
emit SearchFinished(id, results);
return;
}
else {
Error(QString("Json reply is missing results."), json_obj);
emit SearchFinished(id, results); emit SearchFinished(id, results);
return; return;
} }
if (!json_results.contains("albummatches")) { if (!value_results.isObject()) {
Error("Json results is missing albummatches.", json_results); Error("Json results is not a object.", value_results);
emit SearchFinished(id, results); emit SearchFinished(id, results);
return; return;
} }
QJsonValue json_matches = json_results["albummatches"]; QJsonObject obj_results = value_results.toObject();
if (!json_matches.isObject()) { if (obj_results.isEmpty()) {
Error("Json albummatches is not an object.", json_matches); Error("Json results object is empty.", value_results);
emit SearchFinished(id, results); emit SearchFinished(id, results);
return; return;
} }
QJsonObject json_obj_matches = json_matches.toObject(); QJsonValue value_matches;
if (json_obj_matches.isEmpty()) {
Error("Json albummatches object is empty.", json_matches); if (type == "album") {
emit SearchFinished(id, results); if (obj_results.contains("albummatches")) {
return; value_matches = obj_results["albummatches"];
}
else {
Error("Json results object is missing albummatches.", obj_results);
emit SearchFinished(id, results);
return;
}
} }
if (!json_obj_matches.contains("album")) { else if (type == "track") {
Error("Json albummatches is missing album.", json_obj_matches); if (obj_results.contains("trackmatches")) {
value_matches = obj_results["trackmatches"];
}
else {
Error("Json results object is missing trackmatches.", obj_results);
emit SearchFinished(id, results);
return;
}
}
if (!value_matches.isObject()) {
Error("Json albummatches or trackmatches is not an object.", value_matches);
emit SearchFinished(id, results); emit SearchFinished(id, results);
return; return;
} }
QJsonValue json_album = json_obj_matches["album"]; QJsonObject obj_matches = value_matches.toObject();
if (!json_album.isArray()) { if (obj_matches.isEmpty()) {
Error("Json album is not an array.", json_album); Error("Json albummatches or trackmatches object is empty.", value_matches);
emit SearchFinished(id, results); emit SearchFinished(id, results);
return; return;
} }
QJsonArray json_array = json_album.toArray();
for (const QJsonValue &value : json_array) { QJsonValue value_type;
if (!obj_matches.contains(type)) {
Error(QString("Json object is missing %1.").arg(type), obj_matches);
emit SearchFinished(id, results);
return;
}
value_type = obj_matches[type];
if (!value_type.isArray()) {
Error("Json album value in albummatches object is not an array.", value_type);
emit SearchFinished(id, results);
return;
}
QJsonArray array_type = value_type.toArray();
for (const QJsonValue &value : array_type) {
if (!value.isObject()) { if (!value.isObject()) {
Error("Invalid Json reply, album value is not an object.", value); Error("Invalid Json reply, value in albummatches/trackmatches array is not a object.", value);
continue; continue;
} }
QJsonObject json_obj = value.toObject(); QJsonObject obj = value.toObject();
if (!json_obj.contains("artist") || !json_obj.contains("image") || !json_obj.contains("name")) { if (!obj.contains("artist") || !obj.contains("image") || !obj.contains("name")) {
Error("Invalid Json reply, album is missing artist, image or name.", json_obj); Error("Invalid Json reply, album is missing artist, image or name.", obj);
continue; continue;
} }
QString artist = json_obj["artist"].toString(); QString artist = obj["artist"].toString();
QString album = json_obj["name"].toString(); QString album;
if (type == "album") {
album = obj["name"].toString();
}
QJsonValue json_image = json_obj["image"]; QJsonValue json_image = obj["image"];
if (!json_image.isArray()) { if (!json_image.isArray()) {
Error("Invalid Json reply, album image is not an array.", json_image); Error("Invalid Json reply, album image is not a array.", json_image);
continue; continue;
} }
QJsonArray json_array_image = json_image.toArray(); QJsonArray array_image = json_image.toArray();
QUrl url; QUrl url;
LastFmImageSize size(LastFmImageSize::Unknown); LastFmImageSize size(LastFmImageSize::Unknown);
for (QJsonValue json_value_image : json_array_image) { for (const QJsonValue &value_image : array_image) {
if (!json_value_image.isObject()) { if (!value_image.isObject()) {
Error("Invalid Json reply, album image value is not an object.", json_value_image); Error("Invalid Json reply, album image value is not an object.", value_image);
continue; continue;
} }
QJsonObject json_object_image = json_value_image.toObject(); QJsonObject obj_image = value_image.toObject();
if (!json_object_image.contains("#text") || !json_object_image.contains("size")) { if (!obj_image.contains("#text") || !obj_image.contains("size")) {
Error("Invalid Json reply, album image value is missing #text or size.", json_object_image); Error("Invalid Json reply, album image value is missing #text or size.", obj_image);
continue; continue;
} }
QString image_url = json_object_image["#text"].toString(); QString image_url = obj_image["#text"].toString();
LastFmImageSize image_size = ImageSizeFromString(json_object_image["size"].toString().toLower()); LastFmImageSize image_size = ImageSizeFromString(obj_image["size"].toString().toLower());
if (url.isEmpty() || image_size > size) { if (url.isEmpty() || image_size > size) {
url.setUrl(image_url); url.setUrl(image_url);
size = image_size; size = image_size;
} }
} }
if (url.isEmpty()) continue; if (!url.isValid()) continue;
CoverSearchResult cover_result; CoverSearchResult cover_result;
cover_result.artist = artist; cover_result.artist = artist;
@@ -255,7 +312,7 @@ QJsonObject LastFmCoverProvider::ExtractJsonObj(const QByteArray &data) {
Error("Reply from server missing Json data.", data); Error("Reply from server missing Json data.", data);
return QJsonObject(); return QJsonObject();
} }
if (json_doc.isNull() || json_doc.isEmpty()) { if (json_doc.isEmpty()) {
Error("Received empty Json document.", json_doc); Error("Received empty Json document.", json_doc);
return QJsonObject(); return QJsonObject();
} }
@@ -273,36 +330,19 @@ QJsonObject LastFmCoverProvider::ExtractJsonObj(const QByteArray &data) {
} }
QJsonValue LastFmCoverProvider::ExtractResults(const QByteArray &data) {
QJsonObject json_obj = ExtractJsonObj(data);
if (json_obj.isEmpty()) return QJsonObject();
if (json_obj.contains("results")) {
QJsonValue json_results = json_obj["results"];
return json_results;
}
else if (json_obj.contains("error") && json_obj.contains("message")) {
int error = json_obj["error"].toInt();
QString message = json_obj["message"].toString();
Error(QString("Error: %1: %2").arg(QString::number(error)).arg(message));
}
else {
Error(QString("Json reply is missing results."), json_obj);
}
return QJsonValue();
}
void LastFmCoverProvider::Error(const QString &error, const QVariant &debug) { void LastFmCoverProvider::Error(const QString &error, const QVariant &debug) {
qLog(Error) << "LastFm:" << error; qLog(Error) << "LastFm:" << error;
if (debug.isValid()) qLog(Debug) << debug; if (debug.isValid()) qLog(Debug) << debug;
} }
LastFmCoverProvider::LastFmImageSize LastFmCoverProvider::ImageSizeFromString(const QString &size) { LastFmCoverProvider::LastFmImageSize LastFmCoverProvider::ImageSizeFromString(const QString &size) {
if (size == "small") return LastFmImageSize::Small; if (size == "small") return LastFmImageSize::Small;
else if (size == "medium") return LastFmImageSize::Medium; else if (size == "medium") return LastFmImageSize::Medium;
else if (size == "large") return LastFmImageSize::Large; else if (size == "large") return LastFmImageSize::Large;
else if (size == "extralarge") return LastFmImageSize::ExtraLarge; else if (size == "extralarge") return LastFmImageSize::ExtraLarge;
else return LastFmImageSize::Unknown; else return LastFmImageSize::Unknown;
} }

View File

@@ -26,7 +26,6 @@
#include <QVariant> #include <QVariant>
#include <QByteArray> #include <QByteArray>
#include <QString> #include <QString>
#include <QJsonValue>
#include <QJsonObject> #include <QJsonObject>
#include "coverprovider.h" #include "coverprovider.h"
@@ -43,12 +42,9 @@ class LastFmCoverProvider : public CoverProvider {
bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id); bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id);
private slots: private slots:
void QueryFinished(QNetworkReply *reply, const int id); void QueryFinished(QNetworkReply *reply, const int id, const QString &type);
private: private:
static const char *kUrl;
static const char *kApiKey;
static const char *kSecret;
enum LastFmImageSize { enum LastFmImageSize {
Unknown, Unknown,
Small, Small,
@@ -56,12 +52,17 @@ class LastFmCoverProvider : public CoverProvider {
Large, Large,
ExtraLarge ExtraLarge
}; };
QByteArray GetReplyData(QNetworkReply *reply); QByteArray GetReplyData(QNetworkReply *reply);
QJsonObject ExtractJsonObj(const QByteArray &data); QJsonObject ExtractJsonObj(const QByteArray &data);
QJsonValue ExtractResults(const QByteArray &data);
LastFmImageSize ImageSizeFromString(const QString &size); LastFmImageSize ImageSizeFromString(const QString &size);
void Error(const QString &error, const QVariant &debug = QVariant()); void Error(const QString &error, const QVariant &debug = QVariant());
private:
static const char *kUrl;
static const char *kApiKey;
static const char *kSecret;
QNetworkAccessManager *network_; QNetworkAccessManager *network_;
}; };