diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bf1442c8b..82bdba662 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -168,6 +168,8 @@ set(SOURCES lyrics/lyricsproviders.cpp lyrics/lyricsprovider.cpp + lyrics/lyricssearchrequest.h + lyrics/lyricssearchresult.h lyrics/lyricsfetcher.cpp lyrics/lyricsfetchersearch.cpp lyrics/jsonlyricsprovider.cpp diff --git a/src/context/contextview.cpp b/src/context/contextview.cpp index b40f91b81..f4e0e7507 100644 --- a/src/context/contextview.cpp +++ b/src/context/contextview.cpp @@ -407,7 +407,7 @@ void ContextView::SearchLyrics() { if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && action_search_lyrics_->isChecked() && !song_playing_.artist().isEmpty() && !song_playing_.title().isEmpty() && !lyrics_tried_ && lyrics_id_ == -1) { lyrics_fetcher_->Clear(); lyrics_tried_ = true; - lyrics_id_ = static_cast(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.album(), song_playing_.title())); + lyrics_id_ = static_cast(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.artist(), song_playing_.album(), song_playing_.title())); } } diff --git a/src/lyrics/auddlyricsprovider.cpp b/src/lyrics/auddlyricsprovider.cpp index a70b9cb65..a42a918d1 100644 --- a/src/lyrics/auddlyricsprovider.cpp +++ b/src/lyrics/auddlyricsprovider.cpp @@ -20,7 +20,6 @@ #include "config.h" #include -#include #include #include #include @@ -35,9 +34,8 @@ #include "core/logging.h" #include "core/networkaccessmanager.h" #include "utilities/strutils.h" -#include "jsonlyricsprovider.h" -#include "lyricsfetcher.h" -#include "lyricsprovider.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" #include "auddlyricsprovider.h" const char *AuddLyricsProvider::kUrlSearch = "https://api.audd.io/findLyrics/"; @@ -57,27 +55,18 @@ AuddLyricsProvider::~AuddLyricsProvider() { } -bool AuddLyricsProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) { - - Q_UNUSED(album); - - const ParamList params = ParamList() << Param("api_token", QByteArray::fromBase64(kAPITokenB64)) - << Param("q", QString(artist + " " + title)); - - QUrlQuery url_query; - for (const Param ¶m : params) { - url_query.addQueryItem(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); - } +bool AuddLyricsProvider::StartSearch(const int id, const LyricsSearchRequest &request) { QUrl url(kUrlSearch); + QUrlQuery url_query; + url_query.addQueryItem("api_token", QUrl::toPercentEncoding(QByteArray::fromBase64(kAPITokenB64))); + url_query.addQueryItem("q", QUrl::toPercentEncoding(QString(request.artist + " " + request.title))); url.setQuery(url_query); QNetworkRequest req(url); req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); QNetworkReply *reply = network_->get(req); replies_ << reply; - QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, artist, title]() { HandleSearchReply(reply, id, artist, title); }); - - //qLog(Debug) << "AudDLyrics: Sending request for" << url; + QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, request]() { HandleSearchReply(reply, id, request); }); return true; @@ -85,16 +74,23 @@ bool AuddLyricsProvider::StartSearch(const QString &artist, const QString &album void AuddLyricsProvider::CancelSearch(const int id) { Q_UNUSED(id); } -void AuddLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, const QString &artist, const QString &title) { +void AuddLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request) { if (!replies_.contains(reply)) return; replies_.removeAll(reply); QObject::disconnect(reply, nullptr, this, nullptr); reply->deleteLater(); - QJsonArray json_result = ExtractResult(reply, artist, title); + const QByteArray data = ExtractData(reply); + if (data.isEmpty()) { + emit SearchFinished(id); + return; + } + + QJsonArray json_result = ExtractResult(data); if (json_result.isEmpty()) { - emit SearchFinished(id, LyricsSearchResults()); + qLog(Debug) << "AudDLyrics: No lyrics for" << request.artist << request.title; + emit SearchFinished(id); return; } @@ -120,26 +116,31 @@ void AuddLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, c LyricsSearchResult result; result.artist = json_obj["artist"].toString(); result.title = json_obj["title"].toString(); - if (result.artist.compare(artist, Qt::CaseInsensitive) != 0 && result.title.compare(title, Qt::CaseInsensitive) != 0) continue; + if (result.artist.compare(request.albumartist, Qt::CaseInsensitive) != 0 && + result.artist.compare(request.artist, Qt::CaseInsensitive) != 0 && + result.title.compare(request.title, Qt::CaseInsensitive) != 0) { + continue; + } result.lyrics = json_obj["lyrics"].toString(); if (result.lyrics.isEmpty() || result.lyrics.length() > kMaxLength || result.lyrics == "error") continue; result.lyrics = Utilities::DecodeHtmlEntities(result.lyrics); - - //qLog(Debug) << "AudDLyrics:" << result.artist << result.title << result.lyrics.length(); - results << result; } - if (results.isEmpty()) qLog(Debug) << "AudDLyrics: No lyrics for" << artist << title; - else qLog(Debug) << "AudDLyrics: Got lyrics for" << artist << title; + if (results.isEmpty()) { + qLog(Debug) << "AudDLyrics: No lyrics for" << request.artist << request.title; + } + else { + qLog(Debug) << "AudDLyrics: Got lyrics for" << request.artist << request.title; + } emit SearchFinished(id, results); } -QJsonArray AuddLyricsProvider::ExtractResult(QNetworkReply *reply, const QString &artist, const QString &title) { +QJsonArray AuddLyricsProvider::ExtractResult(const QByteArray &data) { - QJsonObject json_obj = ExtractJsonObj(reply); + QJsonObject json_obj = ExtractJsonObj(data); if (json_obj.isEmpty()) return QJsonArray(); if (!json_obj.contains("status")) { @@ -162,18 +163,12 @@ QJsonArray AuddLyricsProvider::ExtractResult(QNetworkReply *reply, const QString return QJsonArray(); } - if (!json_obj.contains("result")) { - Error("Json reply is missing result.", json_obj); + if (!json_obj.contains("result") || !json_obj["result"].isArray()) { + Error("Json reply is missing result array.", json_obj); return QJsonArray(); } - QJsonArray json_result = json_obj["result"].toArray(); - if (json_result.isEmpty()) { - Error(QString("No lyrics for %1 %2").arg(artist, title)); - return QJsonArray(); - } - - return json_result; + return json_obj["result"].toArray(); } diff --git a/src/lyrics/auddlyricsprovider.h b/src/lyrics/auddlyricsprovider.h index 2b7320a32..1fe356016 100644 --- a/src/lyrics/auddlyricsprovider.h +++ b/src/lyrics/auddlyricsprovider.h @@ -42,15 +42,15 @@ class AuddLyricsProvider : public JsonLyricsProvider { explicit AuddLyricsProvider(NetworkAccessManager *network, QObject *parent = nullptr); ~AuddLyricsProvider() override; - bool StartSearch(const QString &artist, const QString &album, const QString &title, int id) override; + bool StartSearch(const int id, const LyricsSearchRequest &request) override; void CancelSearch(const int id) override; private: void Error(const QString &error, const QVariant &debug = QVariant()) override; - QJsonArray ExtractResult(QNetworkReply *reply, const QString &artist, const QString &title); + QJsonArray ExtractResult(const QByteArray &data); private slots: - void HandleSearchReply(QNetworkReply *reply, const int id, const QString &artist, const QString &title); + void HandleSearchReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request); private: static const char *kUrlSearch; diff --git a/src/lyrics/chartlyricsprovider.cpp b/src/lyrics/chartlyricsprovider.cpp index d4d199f66..6841a6ea7 100644 --- a/src/lyrics/chartlyricsprovider.cpp +++ b/src/lyrics/chartlyricsprovider.cpp @@ -32,8 +32,8 @@ #include "core/logging.h" #include "core/networkaccessmanager.h" #include "utilities/strutils.h" -#include "lyricsprovider.h" -#include "lyricsfetcher.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" #include "chartlyricsprovider.h" const char *ChartLyricsProvider::kUrlSearch = "http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect"; @@ -51,15 +51,11 @@ ChartLyricsProvider::~ChartLyricsProvider() { } -bool ChartLyricsProvider::StartSearch(const QString &artist, const QString&, const QString &title, const int id) { - - const ParamList params = ParamList() << Param("artist", artist) - << Param("song", title); +bool ChartLyricsProvider::StartSearch(const int id, const LyricsSearchRequest &request) { QUrlQuery url_query; - for (const Param ¶m : params) { - url_query.addQueryItem(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); - } + url_query.addQueryItem("artist", QUrl::toPercentEncoding(request.artist)); + url_query.addQueryItem("song", QUrl::toPercentEncoding(request.title)); QUrl url(kUrlSearch); url.setQuery(url_query); @@ -67,9 +63,7 @@ bool ChartLyricsProvider::StartSearch(const QString &artist, const QString&, con req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); QNetworkReply *reply = network_->get(req); replies_ << reply; - QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, artist, title]() { HandleSearchReply(reply, id, artist, title); }); - - //qLog(Debug) << "ChartLyrics: Sending request for" << url; + QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, request]() { HandleSearchReply(reply, id, request); }); return true; @@ -77,7 +71,7 @@ bool ChartLyricsProvider::StartSearch(const QString &artist, const QString&, con void ChartLyricsProvider::CancelSearch(const int) {} -void ChartLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, const QString &artist, const QString &title) { +void ChartLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request) { if (!replies_.contains(reply)) return; replies_.removeAll(reply); @@ -86,13 +80,13 @@ void ChartLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, if (reply->error() != QNetworkReply::NoError) { Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error())); - emit SearchFinished(id, LyricsSearchResults()); + emit SearchFinished(id); return; } if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { Error(QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt())); - emit SearchFinished(id, LyricsSearchResults()); + emit SearchFinished(id); return; } @@ -119,7 +113,10 @@ void ChartLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, } else if (type == QXmlStreamReader::EndElement) { if (name == "GetLyricResult") { - if (!result.artist.isEmpty() && !result.title.isEmpty() && !result.lyrics.isEmpty() && (result.artist.compare(artist, Qt::CaseInsensitive) == 0 || result.title.compare(title, Qt::CaseInsensitive) == 0)) { + if (!result.artist.isEmpty() && !result.title.isEmpty() && !result.lyrics.isEmpty() && + (result.artist.compare(request.albumartist, Qt::CaseInsensitive) == 0 || + result.artist.compare(request.artist, Qt::CaseInsensitive) == 0 || + result.title.compare(request.title, Qt::CaseInsensitive) == 0)) { result.lyrics = Utilities::DecodeHtmlEntities(result.lyrics); results << result; } @@ -128,8 +125,12 @@ void ChartLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, } } - if (results.isEmpty()) qLog(Debug) << "ChartLyrics: No lyrics for" << artist << title; - else qLog(Debug) << "ChartLyrics: Got lyrics for" << artist << title; + if (results.isEmpty()) { + qLog(Debug) << "ChartLyrics: No lyrics for" << request.artist << request.title; + } + else { + qLog(Debug) << "ChartLyrics: Got lyrics for" << request.artist << request.title; + } emit SearchFinished(id, results); diff --git a/src/lyrics/chartlyricsprovider.h b/src/lyrics/chartlyricsprovider.h index df1891aae..4fdbf3a10 100644 --- a/src/lyrics/chartlyricsprovider.h +++ b/src/lyrics/chartlyricsprovider.h @@ -40,14 +40,14 @@ class ChartLyricsProvider : public LyricsProvider { explicit ChartLyricsProvider(NetworkAccessManager *network, QObject *parent = nullptr); ~ChartLyricsProvider() override; - bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id) override; + bool StartSearch(const int id, const LyricsSearchRequest &request) override; void CancelSearch(int id) override; private: void Error(const QString &error, const QVariant &debug = QVariant()) override; private slots: - void HandleSearchReply(QNetworkReply *reply, const int id, const QString &artist, const QString &title); + void HandleSearchReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request); private: static const char *kUrlSearch; diff --git a/src/lyrics/geniuslyricsprovider.cpp b/src/lyrics/geniuslyricsprovider.cpp index a1e4a2230..0a76bd5a6 100644 --- a/src/lyrics/geniuslyricsprovider.cpp +++ b/src/lyrics/geniuslyricsprovider.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include #include @@ -50,7 +49,6 @@ #include "internet/localredirectserver.h" #include "jsonlyricsprovider.h" #include "lyricsfetcher.h" -#include "lyricsprovider.h" #include "geniuslyricsprovider.h" const char *GeniusLyricsProvider::kSettingsGroup = "GeniusLyrics"; @@ -106,16 +104,12 @@ void GeniusLyricsProvider::Authenticate() { code_challenge_.chop(1); } - const ParamList params = ParamList() << Param("client_id", QByteArray::fromBase64(kClientIDB64)) - << Param("redirect_uri", redirect_url.toString()) - << Param("scope", "me") - << Param("state", code_challenge_) - << Param("response_type", "code"); - QUrlQuery url_query; - for (const Param ¶m : params) { - url_query.addQueryItem(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); - } + url_query.addQueryItem("client_id", QUrl::toPercentEncoding(QByteArray::fromBase64(kClientIDB64))); + url_query.addQueryItem("redirect_uri", QUrl::toPercentEncoding(redirect_url.toString())); + url_query.addQueryItem("scope", "me"); + url_query.addQueryItem("state", QUrl::toPercentEncoding(code_challenge_)); + url_query.addQueryItem("response_type", "code"); QUrl url(kOAuthAuthorizeUrl); url.setQuery(url_query); @@ -171,19 +165,15 @@ void GeniusLyricsProvider::RequestAccessToken(const QUrl &url, const QUrl &redir if (url.hasQuery() && url_query.hasQueryItem("code") && url_query.hasQueryItem("state")) { - QString code = url_query.queryItemValue("code"); - - const ParamList params = ParamList() << Param("code", code) - << Param("client_id", QByteArray::fromBase64(kClientIDB64)) - << Param("client_secret", QByteArray::fromBase64(kClientSecretB64)) - << Param("redirect_uri", redirect_url.toString()) - << Param("grant_type", "authorization_code") - << Param("response_type", "code"); + const QString code = url_query.queryItemValue("code"); QUrlQuery new_url_query; - for (const Param ¶m : params) { - new_url_query.addQueryItem(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); - } + new_url_query.addQueryItem("code", QUrl::toPercentEncoding(code)); + new_url_query.addQueryItem("client_id", QUrl::toPercentEncoding(QByteArray::fromBase64(kClientIDB64))); + new_url_query.addQueryItem("client_secret", QUrl::toPercentEncoding(QByteArray::fromBase64(kClientSecretB64))); + new_url_query.addQueryItem("redirect_uri", QUrl::toPercentEncoding(redirect_url.toString())); + new_url_query.addQueryItem("grant_type", "authorization_code"); + new_url_query.addQueryItem("response_type", "code"); QUrl new_url(kOAuthAccessTokenUrl); QNetworkRequest req(new_url); @@ -296,25 +286,17 @@ void GeniusLyricsProvider::AccessTokenRequestFinished(QNetworkReply *reply) { } -bool GeniusLyricsProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) { - - Q_UNUSED(album); +bool GeniusLyricsProvider::StartSearch(const int id, const LyricsSearchRequest &request) { if (access_token_.isEmpty()) return false; - std::shared_ptr search = std::make_shared(); - + GeniusLyricsSearchContextPtr search = std::make_shared(); search->id = id; - search->artist = artist; - search->title = title; + search->request = request; requests_search_.insert(id, search); - const ParamList params = ParamList() << Param("q", QString(artist + " " + title)); - QUrlQuery url_query; - for (const Param ¶m : params) { - url_query.addQueryItem(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); - } + url_query.addQueryItem("q", QUrl::toPercentEncoding(QString(request.artist + " " + request.title))); QUrl url(kUrlSearch); url.setQuery(url_query); @@ -339,7 +321,7 @@ void GeniusLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id) reply->deleteLater(); if (!requests_search_.contains(id)) return; - std::shared_ptr search = requests_search_.value(id); + GeniusLyricsSearchContextPtr search = requests_search_.value(id); QJsonObject json_obj = ExtractJsonObj(reply); if (json_obj.isEmpty()) { @@ -421,7 +403,11 @@ void GeniusLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id) QString title = obj_result["title"].toString(); // Ignore results where both the artist and title don't match. - if (artist.compare(search->artist, Qt::CaseInsensitive) != 0 && title.compare(search->title, Qt::CaseInsensitive) != 0) continue; + if (!artist.startsWith(search->request.albumartist, Qt::CaseInsensitive) && + !artist.startsWith(search->request.artist, Qt::CaseInsensitive) && + !title.startsWith(search->request.title, Qt::CaseInsensitive)) { + continue; + } QUrl url(obj_result["url"].toString()); if (!url.isValid()) continue; @@ -454,7 +440,7 @@ void GeniusLyricsProvider::HandleLyricReply(QNetworkReply *reply, const int sear reply->deleteLater(); if (!requests_search_.contains(search_id)) return; - std::shared_ptr search = requests_search_.value(search_id); + GeniusLyricsSearchContextPtr search = requests_search_.value(search_id); if (!search->requests_lyric_.contains(url)) { EndSearch(search); @@ -487,10 +473,9 @@ void GeniusLyricsProvider::HandleLyricReply(QNetworkReply *reply, const int sear } if (!lyrics.isEmpty()) { - LyricsSearchResult result; + LyricsSearchResult result(lyrics); result.artist = lyric.artist; result.title = lyric.title; - result.lyrics = lyrics; search->results.append(result); } @@ -519,7 +504,7 @@ void GeniusLyricsProvider::Error(const QString &error, const QVariant &debug) { } -void GeniusLyricsProvider::EndSearch(std::shared_ptr search, const GeniusLyricsLyricContext &lyric) { +void GeniusLyricsProvider::EndSearch(GeniusLyricsSearchContextPtr search, const GeniusLyricsLyricContext &lyric) { if (search->requests_lyric_.contains(lyric.url)) { search->requests_lyric_.remove(lyric.url); @@ -527,10 +512,10 @@ void GeniusLyricsProvider::EndSearch(std::shared_ptr if (search->requests_lyric_.count() == 0) { requests_search_.remove(search->id); if (search->results.isEmpty()) { - qLog(Debug) << "GeniusLyrics: No lyrics for" << search->artist << search->title; + qLog(Debug) << "GeniusLyrics: No lyrics for" << search->request.artist << search->request.title; } else { - qLog(Debug) << "GeniusLyrics: Got lyrics for" << search->artist << search->title; + qLog(Debug) << "GeniusLyrics: Got lyrics for" << search->request.artist << search->request.title; } emit SearchFinished(search->id, search->results); } diff --git a/src/lyrics/geniuslyricsprovider.h b/src/lyrics/geniuslyricsprovider.h index 0206bb640..63b078f03 100644 --- a/src/lyrics/geniuslyricsprovider.h +++ b/src/lyrics/geniuslyricsprovider.h @@ -36,7 +36,8 @@ #include #include "jsonlyricsprovider.h" -#include "lyricsfetcher.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" class QNetworkReply; class NetworkAccessManager; @@ -53,7 +54,7 @@ class GeniusLyricsProvider : public JsonLyricsProvider { void Authenticate() override; void Deauthenticate() override { access_token_.clear(); } - bool StartSearch(const QString &artist, const QString &album, const QString &title, int id) override; + bool StartSearch(const int id, const LyricsSearchRequest &request) override; void CancelSearch(const int id) override; private: @@ -66,17 +67,18 @@ class GeniusLyricsProvider : public JsonLyricsProvider { struct GeniusLyricsSearchContext { explicit GeniusLyricsSearchContext() : id(-1) {} int id; - QString artist; - QString title; + LyricsSearchRequest request; QMap requests_lyric_; LyricsSearchResults results; }; + using GeniusLyricsSearchContextPtr = std::shared_ptr; + private: void RequestAccessToken(const QUrl &url, const QUrl &redirect_url); void AuthError(const QString &error = QString(), const QVariant &debug = QVariant()); void Error(const QString &error, const QVariant &debug = QVariant()) override; - void EndSearch(std::shared_ptr search, const GeniusLyricsLyricContext &lyric = GeniusLyricsLyricContext()); + void EndSearch(GeniusLyricsSearchContextPtr search, const GeniusLyricsLyricContext &lyric = GeniusLyricsLyricContext()); private slots: void HandleLoginSSLErrors(const QList &ssl_errors); diff --git a/src/lyrics/jsonlyricsprovider.cpp b/src/lyrics/jsonlyricsprovider.cpp index 530febcda..2519d342b 100644 --- a/src/lyrics/jsonlyricsprovider.cpp +++ b/src/lyrics/jsonlyricsprovider.cpp @@ -28,7 +28,6 @@ #include #include "core/networkaccessmanager.h" -#include "lyricsprovider.h" #include "jsonlyricsprovider.h" JsonLyricsProvider::JsonLyricsProvider(const QString &name, const bool enabled, const bool authentication_required, NetworkAccessManager *network, QObject *parent) : LyricsProvider(name, enabled, authentication_required, network, parent) {} diff --git a/src/lyrics/lololyricsprovider.cpp b/src/lyrics/lololyricsprovider.cpp index dd8fdbf50..cce37109d 100644 --- a/src/lyrics/lololyricsprovider.cpp +++ b/src/lyrics/lololyricsprovider.cpp @@ -20,7 +20,6 @@ #include "config.h" #include -#include #include #include #include @@ -33,8 +32,8 @@ #include "core/logging.h" #include "core/networkaccessmanager.h" #include "utilities/strutils.h" -#include "lyricsprovider.h" -#include "lyricsfetcher.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" #include "lololyricsprovider.h" const char *LoloLyricsProvider::kUrlSearch = "http://api.lololyrics.com/0.5/getLyric"; @@ -52,17 +51,11 @@ LoloLyricsProvider::~LoloLyricsProvider() { } -bool LoloLyricsProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) { - - Q_UNUSED(album); - - const ParamList params = ParamList() << Param("artist", artist) - << Param("track", title); +bool LoloLyricsProvider::StartSearch(const int id, const LyricsSearchRequest &request) { QUrlQuery url_query; - for (const Param ¶m : params) { - url_query.addQueryItem(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); - } + url_query.addQueryItem("artist", QUrl::toPercentEncoding(request.artist)); + url_query.addQueryItem("track", QUrl::toPercentEncoding(request.title)); QUrl url(kUrlSearch); url.setQuery(url_query); @@ -70,9 +63,7 @@ bool LoloLyricsProvider::StartSearch(const QString &artist, const QString &album req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); QNetworkReply *reply = network_->get(req); replies_ << reply; - QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, artist, title]() { HandleSearchReply(reply, id, artist, title); }); - - //qLog(Debug) << "LoloLyrics: Sending request for" << url; + QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, request]() { HandleSearchReply(reply, id, request); }); return true; @@ -80,7 +71,7 @@ bool LoloLyricsProvider::StartSearch(const QString &artist, const QString &album void LoloLyricsProvider::CancelSearch(const int id) { Q_UNUSED(id); } -void LoloLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, const QString &artist, const QString &title) { +void LoloLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request) { if (!replies_.contains(reply)) return; replies_.removeAll(reply); @@ -92,7 +83,7 @@ void LoloLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, c failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()); if (reply->error() < 200) { Error(failure_reason); - emit SearchFinished(id, LyricsSearchResults()); + emit SearchFinished(id); return; } } @@ -140,8 +131,12 @@ void LoloLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, c } } - if (results.isEmpty()) qLog(Debug) << "LoloLyrics: No lyrics for" << artist << title << failure_reason; - else qLog(Debug) << "LoloLyrics: Got lyrics for" << artist << title; + if (results.isEmpty()) { + qLog(Debug) << "LoloLyrics: No lyrics for" << request.artist << request.title << failure_reason; + } + else { + qLog(Debug) << "LoloLyrics: Got lyrics for" << request.artist << request.title; + } emit SearchFinished(id, results); diff --git a/src/lyrics/lololyricsprovider.h b/src/lyrics/lololyricsprovider.h index d34faf8f2..34ace9911 100644 --- a/src/lyrics/lololyricsprovider.h +++ b/src/lyrics/lololyricsprovider.h @@ -29,6 +29,7 @@ #include #include "lyricsprovider.h" +#include "lyricssearchrequest.h" class QNetworkReply; class NetworkAccessManager; @@ -40,14 +41,14 @@ class LoloLyricsProvider : public LyricsProvider { explicit LoloLyricsProvider(NetworkAccessManager *network, QObject *parent = nullptr); ~LoloLyricsProvider() override; - bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id) override; + bool StartSearch(const int id, const LyricsSearchRequest &request) override; void CancelSearch(const int id) override; private: void Error(const QString &error, const QVariant &debug = QVariant()) override; private slots: - void HandleSearchReply(QNetworkReply *reply, const int id, const QString &artist, const QString &title); + void HandleSearchReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request); private: static const char *kUrlSearch; diff --git a/src/lyrics/lyricsfetcher.cpp b/src/lyrics/lyricsfetcher.cpp index cfb949337..ffa6c6362 100644 --- a/src/lyrics/lyricsfetcher.cpp +++ b/src/lyrics/lyricsfetcher.cpp @@ -29,6 +29,8 @@ #include "core/song.h" #include "lyricsfetcher.h" #include "lyricsfetchersearch.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" using namespace std::chrono_literals; @@ -45,24 +47,28 @@ LyricsFetcher::LyricsFetcher(LyricsProviders *lyrics_providers, QObject *parent) } -quint64 LyricsFetcher::Search(const QString &artist, const QString &album, const QString &title) { +quint64 LyricsFetcher::Search(const QString &effective_albumartist, const QString &artist, const QString &album, const QString &title) { - LyricsSearchRequest request; - request.artist = artist; - request.album = album; - request.album.remove(Song::kAlbumRemoveMisc); - request.title = title; - request.title.remove(Song::kTitleRemoveMisc); + LyricsSearchRequest search_request; + search_request.albumartist = effective_albumartist; + search_request.artist = artist; + search_request.album = album; + search_request.album.remove(Song::kAlbumRemoveMisc); + search_request.title = title; + search_request.title.remove(Song::kTitleRemoveMisc); + + Request request; request.id = ++next_id_; + request.search_request = search_request; AddRequest(request); return request.id; } -void LyricsFetcher::AddRequest(const LyricsSearchRequest &req) { +void LyricsFetcher::AddRequest(const Request &request) { - queued_requests_.enqueue(req); + queued_requests_.enqueue(request); if (!request_starter_->isActive()) request_starter_->start(); @@ -92,9 +98,9 @@ void LyricsFetcher::StartRequests() { while (!queued_requests_.isEmpty() && active_requests_.size() < kMaxConcurrentRequests) { - LyricsSearchRequest request = queued_requests_.dequeue(); + Request request = queued_requests_.dequeue(); - LyricsFetcherSearch *search = new LyricsFetcherSearch(request, this); + LyricsFetcherSearch *search = new LyricsFetcherSearch(request.id, request.search_request, this); active_requests_.insert(request.id, search); QObject::connect(search, &LyricsFetcherSearch::SearchFinished, this, &LyricsFetcher::SingleSearchFinished); diff --git a/src/lyrics/lyricsfetcher.h b/src/lyrics/lyricsfetcher.h index b32ac1df2..df8804341 100644 --- a/src/lyrics/lyricsfetcher.h +++ b/src/lyrics/lyricsfetcher.h @@ -32,32 +32,13 @@ #include #include +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" + class QTimer; class LyricsProviders; class LyricsFetcherSearch; -struct LyricsSearchRequest { - explicit LyricsSearchRequest() : id(0) {} - quint64 id; - QString artist; - QString album; - QString title; -}; - -struct LyricsSearchResult { - explicit LyricsSearchResult() : score(0.0) {} - QString provider; - QString artist; - QString album; - QString title; - QString lyrics; - float score; -}; -using LyricsSearchResults = QList; - -Q_DECLARE_METATYPE(LyricsSearchResult) -Q_DECLARE_METATYPE(QList) - class LyricsFetcher : public QObject { Q_OBJECT @@ -65,11 +46,17 @@ class LyricsFetcher : public QObject { explicit LyricsFetcher(LyricsProviders *lyrics_providers, QObject *parent = nullptr); ~LyricsFetcher() override {} - quint64 Search(const QString &artist, const QString &album, const QString &title); + struct Request { + Request() : id(0) {} + quint64 id; + LyricsSearchRequest search_request; + }; + + quint64 Search(const QString &effective_albumartist, const QString &artist, const QString &album, const QString &title); void Clear(); private: - void AddRequest(const LyricsSearchRequest &req); + void AddRequest(const Request &request); signals: void LyricsFetched(quint64 request_id, QString provider, QString lyrics); @@ -86,7 +73,7 @@ class LyricsFetcher : public QObject { LyricsProviders *lyrics_providers_; quint64 next_id_; - QQueue queued_requests_; + QQueue queued_requests_; QHash active_requests_; QTimer *request_starter_; diff --git a/src/lyrics/lyricsfetchersearch.cpp b/src/lyrics/lyricsfetchersearch.cpp index 5c66fd5a1..313f4484f 100644 --- a/src/lyrics/lyricsfetchersearch.cpp +++ b/src/lyrics/lyricsfetchersearch.cpp @@ -28,6 +28,8 @@ #include "core/logging.h" #include "lyricsfetcher.h" #include "lyricsfetchersearch.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" #include "lyricsprovider.h" #include "lyricsproviders.h" @@ -35,8 +37,9 @@ const int LyricsFetcherSearch::kSearchTimeoutMs = 3000; const int LyricsFetcherSearch::kGoodLyricsLength = 60; const float LyricsFetcherSearch::kHighScore = 2.5; -LyricsFetcherSearch::LyricsFetcherSearch(const LyricsSearchRequest &request, QObject *parent) +LyricsFetcherSearch::LyricsFetcherSearch(const quint64 id, const LyricsSearchRequest &request, QObject *parent) : QObject(parent), + id_(id), request_(request), cancel_requested_(false) { @@ -69,8 +72,10 @@ void LyricsFetcherSearch::Start(LyricsProviders *lyrics_providers) { if (!provider->is_enabled() || !provider->IsAuthenticated()) continue; QObject::connect(provider, &LyricsProvider::SearchFinished, this, &LyricsFetcherSearch::ProviderSearchFinished); const int id = lyrics_providers->NextId(); - const bool success = provider->StartSearch(request_.artist, request_.album, request_.title, id); - if (success) pending_requests_[id] = provider; + const bool success = provider->StartSearch(id, request_); + if (success) { + pending_requests_.insert(id, provider); + } } if (pending_requests_.isEmpty()) TerminateSearch(); @@ -87,7 +92,7 @@ void LyricsFetcherSearch::ProviderSearchFinished(const int id, const LyricsSearc for (int i = 0; i < results_copy.count(); ++i) { results_copy[i].provider = provider->name(); results_copy[i].score = 0.0; - if (results_copy[i].artist.compare(request_.artist, Qt::CaseInsensitive) == 0) { + if (results_copy[i].artist.compare(request_.albumartist, Qt::CaseInsensitive) == 0 || results_copy[i].artist.compare(request_.artist, Qt::CaseInsensitive) == 0) { results_copy[i].score += 0.5; } if (results_copy[i].album.compare(request_.album, Qt::CaseInsensitive) == 0) { @@ -127,10 +132,10 @@ void LyricsFetcherSearch::AllProvidersFinished() { if (!results_.isEmpty()) { qLog(Debug) << "Using lyrics from" << results_.last().provider << "for" << request_.artist << request_.title << "with score" << results_.last().score; - emit LyricsFetched(request_.id, results_.last().provider, results_.last().lyrics); + emit LyricsFetched(id_, results_.last().provider, results_.last().lyrics); } - emit SearchFinished(request_.id, results_); + emit SearchFinished(id_, results_); } diff --git a/src/lyrics/lyricsfetchersearch.h b/src/lyrics/lyricsfetchersearch.h index 926330d6f..b88cda355 100644 --- a/src/lyrics/lyricsfetchersearch.h +++ b/src/lyrics/lyricsfetchersearch.h @@ -28,6 +28,8 @@ #include #include "lyricsfetcher.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" class LyricsProvider; class LyricsProviders; @@ -36,7 +38,7 @@ class LyricsFetcherSearch : public QObject { Q_OBJECT public: - explicit LyricsFetcherSearch(const LyricsSearchRequest &request, QObject *parent); + explicit LyricsFetcherSearch(const quint64 id, const LyricsSearchRequest &request, QObject *parent); void Start(LyricsProviders *lyrics_providers); void Cancel(); @@ -59,6 +61,7 @@ class LyricsFetcherSearch : public QObject { static const int kGoodLyricsLength; static const float kHighScore; + quint64 id_; LyricsSearchRequest request_; LyricsSearchResults results_; QMap pending_requests_; diff --git a/src/lyrics/lyricsprovider.h b/src/lyrics/lyricsprovider.h index e793afd4b..ac9b5ebc4 100644 --- a/src/lyrics/lyricsprovider.h +++ b/src/lyrics/lyricsprovider.h @@ -24,13 +24,13 @@ #include #include -#include #include #include #include #include -#include "lyricsfetcher.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" class NetworkAccessManager; @@ -47,7 +47,7 @@ class LyricsProvider : public QObject { void set_enabled(const bool enabled) { enabled_ = enabled; } void set_order(const int order) { order_ = order; } - virtual bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id) = 0; + virtual bool StartSearch(const int id, const LyricsSearchRequest &request) = 0; virtual void CancelSearch(const int id) { Q_UNUSED(id); } virtual bool AuthenticationRequired() const { return authentication_required_; } virtual void Authenticate() {} @@ -66,9 +66,6 @@ class LyricsProvider : public QObject { void SearchFinished(int id, LyricsSearchResults results = LyricsSearchResults()); protected: - using Param = QPair; - using ParamList = QList; - NetworkAccessManager *network_; QString name_; bool enabled_; diff --git a/src/lyrics/lyricssearchrequest.h b/src/lyrics/lyricssearchrequest.h new file mode 100644 index 000000000..c0f1a203e --- /dev/null +++ b/src/lyrics/lyricssearchrequest.h @@ -0,0 +1,37 @@ +/* +* Strawberry Music Player +* Copyright 2018-2021, Jonas Kvinge +* +* Strawberry is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* Strawberry is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Strawberry. If not, see . +* +*/ + +#ifndef LYRICSSEARCHREQUEST_H +#define LYRICSSEARCHREQUEST_H + +#include +#include + +class LyricsSearchRequest { + public: + explicit LyricsSearchRequest() {} + QString albumartist; + QString artist; + QString album; + QString title; +}; + +Q_DECLARE_METATYPE(LyricsSearchRequest) + +#endif // LYRICSSEARCHREQUEST_H diff --git a/src/lyrics/lyricssearchresult.h b/src/lyrics/lyricssearchresult.h new file mode 100644 index 000000000..fd41cb680 --- /dev/null +++ b/src/lyrics/lyricssearchresult.h @@ -0,0 +1,41 @@ +/* +* Strawberry Music Player +* Copyright 2018-2021, Jonas Kvinge +* +* Strawberry is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* Strawberry is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Strawberry. If not, see . +* +*/ + +#ifndef LYRICSSEARCHRESULT_H +#define LYRICSSEARCHRESULT_H + +#include +#include + +class LyricsSearchResult { + public: + explicit LyricsSearchResult(const QString _lyrics = QString()) : lyrics(_lyrics), score(0.0) {} + QString provider; + QString artist; + QString album; + QString title; + QString lyrics; + float score; +}; +using LyricsSearchResults = QList; + +Q_DECLARE_METATYPE(LyricsSearchResult) +Q_DECLARE_METATYPE(LyricsSearchResults) + +#endif // LYRICSSEARCHRESULT_H diff --git a/src/lyrics/musixmatchlyricsprovider.cpp b/src/lyrics/musixmatchlyricsprovider.cpp index e21935ec9..a4ffffec4 100644 --- a/src/lyrics/musixmatchlyricsprovider.cpp +++ b/src/lyrics/musixmatchlyricsprovider.cpp @@ -37,11 +37,12 @@ #include "core/networkaccessmanager.h" #include "utilities/strutils.h" #include "jsonlyricsprovider.h" -#include "lyricsfetcher.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" #include "musixmatchlyricsprovider.h" #include "providers/musixmatchprovider.h" -MusixmatchLyricsProvider::MusixmatchLyricsProvider(NetworkAccessManager *network, QObject *parent) : JsonLyricsProvider("Musixmatch", true, false, network, parent), rate_limit_exceeded_(false) {} +MusixmatchLyricsProvider::MusixmatchLyricsProvider(NetworkAccessManager *network, QObject *parent) : JsonLyricsProvider("Musixmatch", true, false, network, parent), use_api_(true) {} MusixmatchLyricsProvider::~MusixmatchLyricsProvider() { @@ -54,20 +55,18 @@ MusixmatchLyricsProvider::~MusixmatchLyricsProvider() { } -bool MusixmatchLyricsProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) { +bool MusixmatchLyricsProvider::StartSearch(const int id, const LyricsSearchRequest &request) { LyricsSearchContextPtr search = std::make_shared(); search->id = id; - search->artist = artist; - search->album = album; - search->title = title; + search->request = request; requests_search_.append(search); - if (rate_limit_exceeded_) { - return CreateLyricsRequest(search); + if (use_api_) { + return SendSearchRequest(search); } else { - return SendSearchRequest(search); + return CreateLyricsRequest(search); } } @@ -76,15 +75,11 @@ void MusixmatchLyricsProvider::CancelSearch(const int id) { Q_UNUSED(id); } bool MusixmatchLyricsProvider::SendSearchRequest(LyricsSearchContextPtr search) { - const ParamList params = ParamList() << Param("apikey", QByteArray::fromBase64(kApiKey)) - << Param("q_artist", search->artist) - << Param("q_track", search->title) - << Param("f_has_lyrics", "1"); - QUrlQuery url_query; - for (const Param ¶m : params) { - url_query.addQueryItem(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); - } + url_query.addQueryItem("apikey", QByteArray::fromBase64(kApiKey)); + url_query.addQueryItem("q_artist", QUrl::toPercentEncoding(search->request.artist)); + url_query.addQueryItem("q_track", QUrl::toPercentEncoding(search->request.title)); + url_query.addQueryItem("f_has_lyrics", "1"); QUrl url(QString(kApiUrl) + QString("/track.search")); url.setQuery(url_query); @@ -108,23 +103,25 @@ void MusixmatchLyricsProvider::HandleSearchReply(QNetworkReply *reply, LyricsSea reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { - Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error())); - if (reply->error() == 402) { - rate_limit_exceeded_ = true; + if (reply->error() == 401 || reply->error() == 402) { + Error(QString("Error %1 (%2) using API, switching to URL based lookup.").arg(reply->errorString()).arg(reply->error())); + use_api_ = false; CreateLyricsRequest(search); return; } + Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error())); EndSearch(search); return; } if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { - Error(QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt())); - if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 402) { - rate_limit_exceeded_ = true; + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 401 || reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 402) { + Error(QString("Received HTTP code %1 using API, switching to URL based lookup.").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt())); + use_api_ = false; CreateLyricsRequest(search); return; } + Error(QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt())); EndSearch(search); return; } @@ -162,13 +159,9 @@ void MusixmatchLyricsProvider::HandleSearchReply(QNetworkReply *reply, LyricsSea int status_code = obj_header["status_code"].toInt(); if (status_code != 200) { - Error(QString("Received status code %1").arg(status_code)); - if (status_code == 402) { - rate_limit_exceeded_ = true; - CreateLyricsRequest(search); - return; - } - EndSearch(search); + Error(QString("Received status code %1, switching to URL based lookup.").arg(status_code)); + use_api_ = false; + CreateLyricsRequest(search); return; } @@ -221,9 +214,11 @@ void MusixmatchLyricsProvider::HandleSearchReply(QNetworkReply *reply, LyricsSea QUrl track_share_url(obj_track["track_share_url"].toString()); // Ignore results where both the artist, album and title don't match. - if (artist_name.compare(search->artist, Qt::CaseInsensitive) != 0 && - album_name.compare(search->album, Qt::CaseInsensitive) != 0 && - track_name.compare(search->title, Qt::CaseInsensitive) != 0) { + if (use_api_ && + artist_name.compare(search->request.albumartist, Qt::CaseInsensitive) != 0 && + artist_name.compare(search->request.artist, Qt::CaseInsensitive) != 0 && + album_name.compare(search->request.album, Qt::CaseInsensitive) != 0 && + track_name.compare(search->request.title, Qt::CaseInsensitive) != 0) { continue; } @@ -247,8 +242,8 @@ void MusixmatchLyricsProvider::HandleSearchReply(QNetworkReply *reply, LyricsSea bool MusixmatchLyricsProvider::CreateLyricsRequest(LyricsSearchContextPtr search) { - QString artist_stripped = StringFixup(search->artist); - QString title_stripped = StringFixup(search->title); + QString artist_stripped = StringFixup(search->request.artist); + QString title_stripped = StringFixup(search->request.title); if (artist_stripped.isEmpty() || title_stripped.isEmpty()) { EndSearch(search); return false; @@ -373,9 +368,7 @@ void MusixmatchLyricsProvider::HandleLyricsReply(QNetworkReply *reply, LyricsSea result.title = obj_track["name"].toString(); result.lyrics = obj_lyrics["body"].toString(); - if (!result.lyrics.isEmpty() && - (result.artist.compare(search->artist, Qt::CaseInsensitive) == 0 || - result.title.compare(search->title, Qt::CaseInsensitive) == 0)) { + if (!result.lyrics.isEmpty()) { result.lyrics = Utilities::DecodeHtmlEntities(result.lyrics); search->results.append(result); } @@ -393,10 +386,10 @@ void MusixmatchLyricsProvider::EndSearch(LyricsSearchContextPtr search, const QU if (search->requests_lyrics_.count() == 0) { requests_search_.removeAll(search); if (search->results.isEmpty()) { - qLog(Debug) << "Musixmatch: No lyrics for" << search->artist << search->title; + qLog(Debug) << "MusixmatchLyrics: No lyrics for" << search->request.artist << search->request.title; } else { - qLog(Debug) << "Musixmatch: Got lyrics for" << search->artist << search->title; + qLog(Debug) << "MusixmatchLyrics: Got lyrics for" << search->request.artist << search->request.title; } emit SearchFinished(search->id, search->results); } diff --git a/src/lyrics/musixmatchlyricsprovider.h b/src/lyrics/musixmatchlyricsprovider.h index 5320161a8..c3abd9ac6 100644 --- a/src/lyrics/musixmatchlyricsprovider.h +++ b/src/lyrics/musixmatchlyricsprovider.h @@ -32,7 +32,8 @@ #include #include "jsonlyricsprovider.h" -#include "lyricsfetcher.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" #include "providers/musixmatchprovider.h" class QNetworkReply; @@ -45,16 +46,14 @@ class MusixmatchLyricsProvider : public JsonLyricsProvider, public MusixmatchPro explicit MusixmatchLyricsProvider(NetworkAccessManager *network, QObject *parent = nullptr); ~MusixmatchLyricsProvider() override; - bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id) override; + bool StartSearch(const int id, const LyricsSearchRequest &request) override; void CancelSearch(const int id) override; private: struct LyricsSearchContext { explicit LyricsSearchContext() : id(-1) {} int id; - QString artist; - QString album; - QString title; + LyricsSearchRequest request; QList requests_lyrics_; LyricsSearchResults results; }; @@ -63,6 +62,7 @@ class MusixmatchLyricsProvider : public JsonLyricsProvider, public MusixmatchPro bool SendSearchRequest(LyricsSearchContextPtr search); bool CreateLyricsRequest(LyricsSearchContextPtr search); + void SendLyricsRequest(const LyricsSearchRequest &request, const QString &artist, const QString &title); bool SendLyricsRequest(LyricsSearchContextPtr search, const QUrl &url); void EndSearch(LyricsSearchContextPtr search, const QUrl &url = QUrl()); void Error(const QString &error, const QVariant &debug = QVariant()) override; @@ -74,7 +74,7 @@ class MusixmatchLyricsProvider : public JsonLyricsProvider, public MusixmatchPro private: QList requests_search_; QList replies_; - bool rate_limit_exceeded_; + bool use_api_; }; diff --git a/src/lyrics/ovhlyricsprovider.cpp b/src/lyrics/ovhlyricsprovider.cpp index c1916cb22..fc8bd4cc7 100644 --- a/src/lyrics/ovhlyricsprovider.cpp +++ b/src/lyrics/ovhlyricsprovider.cpp @@ -30,7 +30,8 @@ #include "core/logging.h" #include "core/networkaccessmanager.h" #include "utilities/strutils.h" -#include "lyricsfetcher.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" #include "jsonlyricsprovider.h" #include "ovhlyricsprovider.h" @@ -49,18 +50,14 @@ OVHLyricsProvider::~OVHLyricsProvider() { } -bool OVHLyricsProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) { +bool OVHLyricsProvider::StartSearch(const int id, const LyricsSearchRequest &request) { - Q_UNUSED(album); - - QUrl url(kUrlSearch + QString(QUrl::toPercentEncoding(artist)) + "/" + QString(QUrl::toPercentEncoding(title))); + QUrl url(kUrlSearch + QString(QUrl::toPercentEncoding(request.artist)) + "/" + QString(QUrl::toPercentEncoding(request.title))); QNetworkRequest req(url); req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); QNetworkReply *reply = network_->get(req); replies_ << reply; - QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, artist, title]() { HandleSearchReply(reply, id, artist, title); }); - - //qLog(Debug) << "OVHLyrics: Sending request for" << url; + QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, request]() { HandleSearchReply(reply, id, request); }); return true; @@ -68,7 +65,7 @@ bool OVHLyricsProvider::StartSearch(const QString &artist, const QString &album, void OVHLyricsProvider::CancelSearch(const int id) { Q_UNUSED(id); } -void OVHLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, const QString &artist, const QString &title) { +void OVHLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request) { if (!replies_.contains(reply)) return; replies_.removeAll(reply); @@ -77,19 +74,19 @@ void OVHLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, co QJsonObject json_obj = ExtractJsonObj(reply); if (json_obj.isEmpty()) { - emit SearchFinished(id, LyricsSearchResults()); + emit SearchFinished(id); return; } if (json_obj.contains("error")) { Error(json_obj["error"].toString()); - qLog(Debug) << "OVHLyrics: No lyrics for" << artist << title; - emit SearchFinished(id, LyricsSearchResults()); + qLog(Debug) << "OVHLyrics: No lyrics for" << request.artist << request.title; + emit SearchFinished(id); return; } if (!json_obj.contains("lyrics")) { - emit SearchFinished(id, LyricsSearchResults()); + emit SearchFinished(id); return; } @@ -97,18 +94,17 @@ void OVHLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, co result.lyrics = json_obj["lyrics"].toString(); if (result.lyrics.isEmpty()) { - qLog(Debug) << "OVHLyrics: No lyrics for" << artist << title; - emit SearchFinished(id, LyricsSearchResults()); + qLog(Debug) << "OVHLyrics: No lyrics for" << request.artist << request.title; + emit SearchFinished(id); } else { result.lyrics = Utilities::DecodeHtmlEntities(result.lyrics); - qLog(Debug) << "OVHLyrics: Got lyrics for" << artist << title; + qLog(Debug) << "OVHLyrics: Got lyrics for" << request.artist << request.title; emit SearchFinished(id, LyricsSearchResults() << result); } } - void OVHLyricsProvider::Error(const QString &error, const QVariant &debug) { qLog(Error) << "OVHLyrics:" << error; diff --git a/src/lyrics/ovhlyricsprovider.h b/src/lyrics/ovhlyricsprovider.h index fec04fac5..e5de0ead5 100644 --- a/src/lyrics/ovhlyricsprovider.h +++ b/src/lyrics/ovhlyricsprovider.h @@ -29,6 +29,7 @@ #include #include "jsonlyricsprovider.h" +#include "lyricssearchrequest.h" class QNetworkReply; class NetworkAccessManager; @@ -40,14 +41,14 @@ class OVHLyricsProvider : public JsonLyricsProvider { explicit OVHLyricsProvider(NetworkAccessManager *network, QObject *parent = nullptr); ~OVHLyricsProvider() override; - bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id) override; + bool StartSearch(const int id, const LyricsSearchRequest &request) override; void CancelSearch(const int id) override; private: void Error(const QString &error, const QVariant &debug = QVariant()) override; private slots: - void HandleSearchReply(QNetworkReply *reply, const int id, const QString &artist, const QString &title); + void HandleSearchReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request); private: static const char *kUrlSearch; diff --git a/src/lyrics/stands4lyricsprovider.cpp b/src/lyrics/stands4lyricsprovider.cpp index 21bb03858..b1afb2806 100644 --- a/src/lyrics/stands4lyricsprovider.cpp +++ b/src/lyrics/stands4lyricsprovider.cpp @@ -35,7 +35,8 @@ #include "core/logging.h" #include "core/networkaccessmanager.h" #include "utilities/strutils.h" -#include "lyricsfetcher.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" #include "stands4lyricsprovider.h" const char *Stands4LyricsProvider::kApiUrl = "https://www.abbreviations.com/services/v2/lyrics.php"; @@ -43,7 +44,7 @@ const char *Stands4LyricsProvider::kLyricsUrl = "https://www.lyrics.com/lyrics/" const char *Stands4LyricsProvider::kUID = "11363"; const char *Stands4LyricsProvider::kTokenB64 = "b3FOYmxhV1ZKRGxIMnV4OA=="; -Stands4LyricsProvider::Stands4LyricsProvider(NetworkAccessManager *network, QObject *parent) : JsonLyricsProvider("Stands4Lyrics", true, false, network, parent), api_usage_exceeded_(false) {} +Stands4LyricsProvider::Stands4LyricsProvider(NetworkAccessManager *network, QObject *parent) : JsonLyricsProvider("Stands4Lyrics", true, false, network, parent), use_api_(true) {} Stands4LyricsProvider::~Stands4LyricsProvider() { @@ -56,41 +57,41 @@ Stands4LyricsProvider::~Stands4LyricsProvider() { } -bool Stands4LyricsProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) { +bool Stands4LyricsProvider::StartSearch(const int id, const LyricsSearchRequest &request) { - if (api_usage_exceeded_) { - SendLyricsRequest(id, artist, album, title); + if (use_api_) { + SendSearchRequest(id, request); } else { - SendSearchRequest(id, artist, album, title); + CreateLyricsRequest(id, request); } return true; } -void Stands4LyricsProvider::SendSearchRequest(const int id, const QString &artist, const QString &album, const QString &title) { +void Stands4LyricsProvider::SendSearchRequest(const int id, const LyricsSearchRequest &request) { QUrlQuery url_query; url_query.addQueryItem(QUrl::toPercentEncoding("uid"), QUrl::toPercentEncoding(kUID)); url_query.addQueryItem(QUrl::toPercentEncoding("tokenid"), QUrl::toPercentEncoding(QByteArray::fromBase64(kTokenB64))); url_query.addQueryItem(QUrl::toPercentEncoding("format"), "json"); - url_query.addQueryItem(QUrl::toPercentEncoding("artist"), QUrl::toPercentEncoding(artist)); - url_query.addQueryItem(QUrl::toPercentEncoding("album"), QUrl::toPercentEncoding(album)); - url_query.addQueryItem(QUrl::toPercentEncoding("term"), QUrl::toPercentEncoding(title)); + url_query.addQueryItem(QUrl::toPercentEncoding("artist"), QUrl::toPercentEncoding(request.artist)); + url_query.addQueryItem(QUrl::toPercentEncoding("album"), QUrl::toPercentEncoding(request.album)); + url_query.addQueryItem(QUrl::toPercentEncoding("term"), QUrl::toPercentEncoding(request.title)); QUrl url(kApiUrl); url.setQuery(url_query); QNetworkRequest req(url); req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); QNetworkReply *reply = network_->get(req); replies_ << reply; - QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, artist, album, title]() { HandleSearchReply(reply, id, artist, album, title); }); + QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, request]() { HandleSearchReply(reply, id, request); }); } void Stands4LyricsProvider::CancelSearch(const int id) { Q_UNUSED(id); } -void Stands4LyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, const QString &artist, const QString &album, const QString &title) { +void Stands4LyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request) { if (!replies_.contains(reply)) return; replies_.removeAll(reply); @@ -107,51 +108,47 @@ void Stands4LyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error); if (json_error.error != QJsonParseError::NoError) { - Error(QString("Failed to parse json data: %1").arg(json_error.errorString())); + qLog(Error) << "Stands4Lyrics: Failed to parse json data" << json_error.errorString(); emit SearchFinished(id); return; } if (json_doc.isEmpty()) { - qLog(Debug) << "Stands4Lyrics: No lyrics for" << artist << album << title; + qLog(Debug) << "Stands4Lyrics: No lyrics for" << request.artist << request.album << request.title; emit SearchFinished(id); return; } if (!json_doc.isObject()) { - Error("Json document is not an object.", json_doc); + qLog(Error) << "Stands4Lyrics: Json document is not an object."; emit SearchFinished(id); return; } QJsonObject json_obj = json_doc.object(); if (json_obj.isEmpty()) { - qLog(Debug) << "Stands4Lyrics: No lyrics for" << artist << album << title; + qLog(Debug) << "Stands4Lyrics: No lyrics for" << request.artist << request.album << request.title; emit SearchFinished(id); return; } if (json_obj.contains("error")) { const QString error = json_obj["error"].toString(); - if (error.compare("Daily Usage Exceeded", Qt::CaseInsensitive) == 0) { - api_usage_exceeded_ = true; - SendLyricsRequest(id, artist, album, title); - return; - } - Error(error); - emit SearchFinished(id); + qLog(Error) << "Stands4Lyrics: Received error:" << error << "switching to URL based lookup."; + use_api_ = false; + CreateLyricsRequest(id, request); return; } if (!json_obj.contains("result") || !json_obj["result"].isArray()) { - Error("Json reply is missing result.", json_obj); + qLog(Error) << "Stands4Lyrics: Json reply is missing result."; emit SearchFinished(id); return; } QJsonArray json_result = json_obj["result"].toArray(); if (json_result.isEmpty()) { - qLog(Debug) << "Stands4Lyrics: No lyrics for" << artist << album << title; + qLog(Debug) << "Stands4Lyrics: No lyrics for" << request.artist << request.album << request.title; emit SearchFinished(id); return; } @@ -160,7 +157,6 @@ void Stands4LyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id for (const QJsonValueRef value : json_result) { if (!value.isObject()) { qLog(Error) << "Stands4Lyrics: Invalid Json reply, result is not an object."; - qLog(Debug) << value; continue; } QJsonObject obj = value.toObject(); @@ -178,9 +174,11 @@ void Stands4LyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id QString result_album = obj["album"].toString(); QString result_title = obj["song"].toString(); QString song_link = obj["song-link"].toString(); - if (result_artist.compare(artist, Qt::CaseInsensitive) != 0 && - result_album.compare(album, Qt::CaseInsensitive) != 0 && - result_title.compare(title, Qt::CaseInsensitive) != 0) { + + if (result_artist.compare(request.albumartist, Qt::CaseInsensitive) != 0 && + result_artist.compare(request.artist, Qt::CaseInsensitive) != 0 && + result_album.compare(request.album, Qt::CaseInsensitive) != 0 && + result_title.compare(request.title, Qt::CaseInsensitive) != 0) { continue; } @@ -189,38 +187,44 @@ void Stands4LyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id if (QRegularExpression("^https:\\/\\/.*\\/lyrics\\/.*\\/.*\\.html$").match(song_link).hasMatch()) { QUrl url(song_link); if (url.isValid()) { - SendLyricsRequest(id, result_artist, result_album, result_title, url); + SendLyricsRequest(id, request, result_artist, result_album, result_title, url); return; } } } - SendLyricsRequest(id, result_artist, result_album, result_title); + SendLyricsRequest(id, request, result_artist, result_album, result_title); return; } - qLog(Debug) << "Stands4Lyrics: No lyrics for" << artist << album << title; + qLog(Debug) << "Stands4Lyrics: No lyrics for" << request.artist << request.album << request.title; emit SearchFinished(id); } -void Stands4LyricsProvider::SendLyricsRequest(const int id, const QString &artist, const QString &album, const QString &title, QUrl url) { +void Stands4LyricsProvider::CreateLyricsRequest(const int id, const LyricsSearchRequest &request) { + + SendLyricsRequest(id, request, request.artist, request.album, request.title); + +} + +void Stands4LyricsProvider::SendLyricsRequest(const int id, const LyricsSearchRequest &request, const QString &result_artist, const QString &result_album, const QString &result_title, QUrl url) { if (url.isEmpty() || !url.isValid()) { - url.setUrl(kLyricsUrl + StringFixup(artist) + "/" + StringFixup(title) + ".html"); + url.setUrl(kLyricsUrl + StringFixup(result_artist) + "/" + StringFixup(result_title) + ".html"); } QNetworkRequest req(url); req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); QNetworkReply *reply = network_->get(req); replies_ << reply; - QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, artist, album, title]() { HandleLyricsReply(reply, id, artist, album, title); }); + QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, request, result_artist, result_album, result_title]() { HandleLyricsReply(reply, id, request, result_artist, result_album, result_title); }); } -void Stands4LyricsProvider::HandleLyricsReply(QNetworkReply *reply, const int id, const QString &artist, const QString &album, const QString &title) { +void Stands4LyricsProvider::HandleLyricsReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request, const QString &result_artist, const QString &result_album, const QString &result_title) { if (!replies_.contains(reply)) return; replies_.removeAll(reply); @@ -228,37 +232,36 @@ void Stands4LyricsProvider::HandleLyricsReply(QNetworkReply *reply, const int id reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { - Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error())); + qLog(Error) << reply->errorString() << reply->error(); emit SearchFinished(id); return; } else if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) { - Error(QString("Received HTTP code %1").arg(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt())); + qLog(Error) << "Received HTTP code" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); emit SearchFinished(id); return; } const QByteArray data = reply->readAll(); if (data.isEmpty()) { - Error("Empty reply received from server."); + qLog(Error) << "Stands4Lyrics: Empty reply received from server."; emit SearchFinished(id); return; } const QString lyrics = ParseLyricsFromHTML(QString::fromUtf8(data), QRegularExpression("]*>"), QRegularExpression("<\\/div>"), QRegularExpression("
]+>"), false); if (lyrics.isEmpty() || lyrics.contains("Click to search for the Lyrics on Lyrics.com", Qt::CaseInsensitive)) { - qLog(Debug) << "Stands4Lyrics: No lyrics for" << artist << album << title; + qLog(Debug) << "Stands4Lyrics: No lyrics for" << request.artist << request.album << request.title; emit SearchFinished(id); return; } - qLog(Debug) << "Stands4Lyrics: Got lyrics for" << artist << album << title; + qLog(Debug) << "Stands4Lyrics: Got lyrics for" << request.artist << request.album << request.title; - LyricsSearchResult result; - result.artist = artist; - result.album = album; - result.title = title; - result.lyrics = lyrics; + LyricsSearchResult result(lyrics); + result.artist = result_artist; + result.album = result_album; + result.title = result_title; emit SearchFinished(id, LyricsSearchResults() << result); } diff --git a/src/lyrics/stands4lyricsprovider.h b/src/lyrics/stands4lyricsprovider.h index 420602845..85092a3dd 100644 --- a/src/lyrics/stands4lyricsprovider.h +++ b/src/lyrics/stands4lyricsprovider.h @@ -28,7 +28,7 @@ #include #include "jsonlyricsprovider.h" -#include "lyricsfetcher.h" +#include "lyricssearchrequest.h" class QNetworkReply; class NetworkAccessManager; @@ -40,18 +40,19 @@ class Stands4LyricsProvider : public JsonLyricsProvider { explicit Stands4LyricsProvider(NetworkAccessManager *network, QObject *parent = nullptr); ~Stands4LyricsProvider() override; - bool StartSearch(const QString &artist, const QString &album, const QString &title, int id) override; + bool StartSearch(const int id, const LyricsSearchRequest &request) override; void CancelSearch(const int id) override; private: - void SendSearchRequest(const int id, const QString &artist, const QString &album, const QString &title); - void SendLyricsRequest(const int id, const QString &artist, const QString &album, const QString &title, QUrl url = QUrl()); + void SendSearchRequest(const int id, const LyricsSearchRequest &request); + void CreateLyricsRequest(const int id, const LyricsSearchRequest &request); + void SendLyricsRequest(const int id, const LyricsSearchRequest &request, const QString &result_artist, const QString &result_album, const QString &result_title, QUrl url = QUrl()); void Error(const QString &error, const QVariant &debug = QVariant()) override; static QString StringFixup(QString string); private slots: - void HandleSearchReply(QNetworkReply *reply, const int id, const QString &artist, const QString &album, const QString &title); - void HandleLyricsReply(QNetworkReply *reply, const int id, const QString &artist, const QString &album, const QString &title); + void HandleSearchReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request); + void HandleLyricsReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request, const QString &result_artist, const QString &result_album, const QString &result_title); private: static const char *kApiUrl; @@ -59,7 +60,7 @@ class Stands4LyricsProvider : public JsonLyricsProvider { static const char *kUID; static const char *kTokenB64; QList replies_; - bool api_usage_exceeded_; + bool use_api_; }; #endif // STANDS4LYRICSPROVIDER_H