2018-02-27 18:06:05 +01:00
/*
* Strawberry Music Player
2021-03-20 21:14:47 +01:00
* Copyright 2018 - 2021 , Jonas Kvinge < jonas @ jkvinge . net >
2018-02-27 18:06:05 +01:00
*
* 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 < http : //www.gnu.org/licenses/>.
2018-08-09 18:39:44 +02:00
*
2018-02-27 18:06:05 +01:00
*/
# include "config.h"
2020-04-23 21:04:37 +02:00
# include <algorithm>
2020-02-09 02:29:35 +01:00
# include <QtGlobal>
2018-05-01 00:41:33 +02:00
# include <QObject>
# include <QVariant>
2018-12-15 15:12:18 +01:00
# include <QByteArray>
2018-05-01 00:41:33 +02:00
# include <QString>
# include <QUrl>
2018-02-27 18:06:05 +01:00
# include <QUrlQuery>
2020-05-15 22:53:21 +02:00
# include <QTimer>
2018-05-01 00:41:33 +02:00
# include <QNetworkRequest>
# include <QNetworkReply>
2018-12-15 15:12:18 +01:00
# include <QJsonDocument>
# include <QJsonValue>
# include <QJsonObject>
# include <QJsonArray>
2018-02-27 18:06:05 +01:00
2023-07-21 05:55:24 +02:00
# include "core/shared_ptr.h"
2019-04-14 16:40:05 +02:00
# include "core/application.h"
2021-01-11 16:48:46 +01:00
# include "core/networkaccessmanager.h"
2018-09-01 22:21:45 +02:00
# include "core/logging.h"
2018-05-01 00:41:33 +02:00
# include "albumcoverfetcher.h"
2020-05-10 12:49:11 +02:00
# include "jsoncoverprovider.h"
2018-02-27 18:06:05 +01:00
# include "musicbrainzcoverprovider.h"
2024-04-11 02:56:01 +02:00
namespace {
constexpr char kReleaseSearchUrl [ ] = " https://musicbrainz.org/ws/2/release/ " ;
constexpr char kAlbumCoverUrl [ ] = " https://coverartarchive.org/release/%1/front " ;
constexpr int kLimit = 8 ;
constexpr int kRequestsDelay = 1000 ;
} // namespace
2018-02-27 18:06:05 +01:00
2023-07-21 05:55:24 +02:00
MusicbrainzCoverProvider : : MusicbrainzCoverProvider ( Application * app , SharedPtr < NetworkAccessManager > network , QObject * parent )
2024-04-09 23:20:26 +02:00
: JsonCoverProvider ( QStringLiteral ( " MusicBrainz " ) , true , false , 1.5 , true , false , app , network , parent ) ,
2021-07-11 07:40:57 +02:00
timer_flush_requests_ ( new QTimer ( this ) ) {
2020-05-15 22:53:21 +02:00
timer_flush_requests_ - > setInterval ( kRequestsDelay ) ;
timer_flush_requests_ - > setSingleShot ( false ) ;
2021-01-26 16:48:04 +01:00
QObject : : connect ( timer_flush_requests_ , & QTimer : : timeout , this , & MusicbrainzCoverProvider : : FlushRequests ) ;
2020-05-15 22:53:21 +02:00
}
2018-02-27 18:06:05 +01:00
2020-05-12 21:28:42 +02:00
MusicbrainzCoverProvider : : ~ MusicbrainzCoverProvider ( ) {
while ( ! replies_ . isEmpty ( ) ) {
QNetworkReply * reply = replies_ . takeFirst ( ) ;
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( reply , nullptr , this , nullptr ) ;
2020-05-12 21:28:42 +02:00
reply - > abort ( ) ;
reply - > deleteLater ( ) ;
}
}
2020-04-20 18:03:18 +02:00
bool MusicbrainzCoverProvider : : StartSearch ( const QString & artist , const QString & album , const QString & title , const int id ) {
Q_UNUSED ( title ) ;
2018-02-27 18:06:05 +01:00
2020-08-07 00:18:31 +02:00
if ( artist . isEmpty ( ) | | album . isEmpty ( ) ) return false ;
2020-05-15 22:53:21 +02:00
SearchRequest request ( id , artist , album ) ;
queue_search_requests_ < < request ;
if ( ! timer_flush_requests_ - > isActive ( ) ) {
timer_flush_requests_ - > start ( ) ;
}
return true ;
}
void MusicbrainzCoverProvider : : SendSearchRequest ( const SearchRequest & request ) {
2024-04-11 02:56:01 +02:00
QString query = QStringLiteral ( " release: \" %1 \" AND artist: \" %2 \" " ) . arg ( request . album . trimmed ( ) . replace ( QLatin1Char ( ' " ' ) , QLatin1String ( " \\ \" " ) ) , request . artist . trimmed ( ) . replace ( QLatin1Char ( ' " ' ) , QLatin1String ( " \\ \" " ) ) ) ;
2018-12-15 15:12:18 +01:00
2018-02-27 18:06:05 +01:00
QUrlQuery url_query ;
2024-04-09 23:20:26 +02:00
url_query . addQueryItem ( QStringLiteral ( " query " ) , query ) ;
url_query . addQueryItem ( QStringLiteral ( " limit " ) , QString : : number ( kLimit ) ) ;
url_query . addQueryItem ( QStringLiteral ( " fmt " ) , QStringLiteral ( " json " ) ) ;
2018-12-15 15:12:18 +01:00
2024-04-11 02:56:01 +02:00
QUrl url ( QString : : fromLatin1 ( kReleaseSearchUrl ) ) ;
2018-02-27 18:06:05 +01:00
url . setQuery ( url_query ) ;
2019-08-22 19:28:54 +02:00
QNetworkRequest req ( url ) ;
2020-08-14 20:20:41 +02:00
req . setAttribute ( QNetworkRequest : : RedirectPolicyAttribute , QNetworkRequest : : NoLessSafeRedirectPolicy ) ;
2019-08-22 19:28:54 +02:00
QNetworkReply * reply = network_ - > get ( req ) ;
2020-05-12 21:28:42 +02:00
replies_ < < reply ;
2021-03-21 00:37:17 +01:00
QObject : : connect ( reply , & QNetworkReply : : finished , this , [ this , reply , request ] ( ) { HandleSearchReply ( reply , request . id ) ; } ) ;
2018-02-27 18:06:05 +01:00
2020-05-15 22:53:21 +02:00
}
void MusicbrainzCoverProvider : : FlushRequests ( ) {
if ( ! queue_search_requests_ . isEmpty ( ) ) {
SendSearchRequest ( queue_search_requests_ . dequeue ( ) ) ;
return ;
}
timer_flush_requests_ - > stop ( ) ;
2018-03-02 22:51:42 +01:00
2018-02-27 18:06:05 +01:00
}
2019-09-15 20:27:32 +02:00
void MusicbrainzCoverProvider : : HandleSearchReply ( QNetworkReply * reply , const int search_id ) {
2018-02-27 18:06:05 +01:00
2020-05-12 21:28:42 +02:00
if ( ! replies_ . contains ( reply ) ) return ;
replies_ . removeAll ( reply ) ;
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( reply , nullptr , this , nullptr ) ;
2018-02-27 18:06:05 +01:00
reply - > deleteLater ( ) ;
2021-02-26 21:03:51 +01:00
CoverProviderSearchResults results ;
2018-12-15 15:12:18 +01:00
QByteArray data = GetReplyData ( reply ) ;
if ( data . isEmpty ( ) ) {
emit SearchFinished ( search_id , results ) ;
return ;
}
QJsonObject json_obj = ExtractJsonObj ( data ) ;
if ( json_obj . isEmpty ( ) ) {
emit SearchFinished ( search_id , results ) ;
return ;
}
2024-04-09 23:20:26 +02:00
if ( ! json_obj . contains ( QStringLiteral ( " releases " ) ) ) {
if ( json_obj . contains ( QStringLiteral ( " error " ) ) ) {
QString error = json_obj [ QStringLiteral ( " error " ) ] . toString ( ) ;
2018-12-15 15:12:18 +01:00
Error ( error ) ;
2018-02-27 18:06:05 +01:00
}
2018-12-15 15:12:18 +01:00
else {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json reply is missing releases. " ) , json_obj ) ;
2018-12-15 15:12:18 +01:00
}
emit SearchFinished ( search_id , results ) ;
return ;
}
2024-04-09 23:20:26 +02:00
QJsonValue value_releases = json_obj [ QStringLiteral ( " releases " ) ] ;
2018-12-15 15:12:18 +01:00
2020-04-23 21:04:37 +02:00
if ( ! value_releases . isArray ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Json releases is not an array. " ) , value_releases ) ;
2018-12-15 15:12:18 +01:00
emit SearchFinished ( search_id , results ) ;
return ;
2018-02-27 18:06:05 +01:00
}
2020-04-23 21:04:37 +02:00
QJsonArray array_releases = value_releases . toArray ( ) ;
2018-02-27 18:06:05 +01:00
2020-04-23 21:04:37 +02:00
if ( array_releases . isEmpty ( ) ) {
2018-12-15 15:12:18 +01:00
emit SearchFinished ( search_id , results ) ;
return ;
2018-02-27 18:06:05 +01:00
}
2018-03-02 22:51:42 +01:00
2021-03-26 22:10:43 +01:00
for ( const QJsonValueRef value_release : array_releases ) {
2019-04-17 22:18:03 +02:00
2020-04-23 21:04:37 +02:00
if ( ! value_release . isObject ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Invalid Json reply, releases array value is not an object. " ) ) ;
2018-12-15 15:12:18 +01:00
continue ;
}
2020-04-23 21:04:37 +02:00
QJsonObject obj_release = value_release . toObject ( ) ;
2024-04-09 23:20:26 +02:00
if ( ! obj_release . contains ( QStringLiteral ( " id " ) ) | | ! obj_release . contains ( QStringLiteral ( " artist-credit " ) ) | | ! obj_release . contains ( QStringLiteral ( " title " ) ) ) {
Error ( QStringLiteral ( " Invalid Json reply, releases array object is missing id, artist-credit or title. " ) , obj_release ) ;
2019-04-17 22:18:03 +02:00
continue ;
}
2024-04-09 23:20:26 +02:00
QJsonValue json_artists = obj_release [ QStringLiteral ( " artist-credit " ) ] ;
2019-04-17 22:18:03 +02:00
if ( ! json_artists . isArray ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Invalid Json reply, artist-credit is not a array. " ) , json_artists ) ;
2018-12-15 15:12:18 +01:00
continue ;
}
2020-04-23 21:04:37 +02:00
QJsonArray array_artists = json_artists . toArray ( ) ;
2019-04-17 22:18:03 +02:00
int i = 0 ;
QString artist ;
2021-03-26 22:10:43 +01:00
for ( const QJsonValueRef value_artist : array_artists ) {
2020-04-23 21:04:37 +02:00
if ( ! value_artist . isObject ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Invalid Json reply, artist is not a object. " ) ) ;
2019-04-17 22:18:03 +02:00
continue ;
}
2020-04-23 21:04:37 +02:00
QJsonObject obj_artist = value_artist . toObject ( ) ;
2019-04-17 22:18:03 +02:00
2024-04-09 23:20:26 +02:00
if ( ! obj_artist . contains ( QStringLiteral ( " artist " ) ) ) {
Error ( QStringLiteral ( " Invalid Json reply, artist is missing. " ) , obj_artist ) ;
2019-04-17 22:18:03 +02:00
continue ;
}
2024-04-09 23:20:26 +02:00
QJsonValue value_artist2 = obj_artist [ QStringLiteral ( " artist " ) ] ;
2020-04-23 21:04:37 +02:00
if ( ! value_artist2 . isObject ( ) ) {
2024-04-09 23:20:26 +02:00
Error ( QStringLiteral ( " Invalid Json reply, artist is not an object. " ) , value_artist2 ) ;
2019-04-17 22:18:03 +02:00
continue ;
}
2020-04-23 21:04:37 +02:00
QJsonObject obj_artist2 = value_artist2 . toObject ( ) ;
2019-04-17 22:18:03 +02:00
2024-04-09 23:20:26 +02:00
if ( ! obj_artist2 . contains ( QStringLiteral ( " name " ) ) ) {
Error ( QStringLiteral ( " Invalid Json reply, artist is missing name. " ) , value_artist2 ) ;
2019-04-17 22:18:03 +02:00
continue ;
}
2024-04-09 23:20:26 +02:00
artist = obj_artist2 [ QStringLiteral ( " name " ) ] . toString ( ) ;
2019-04-17 22:18:03 +02:00
+ + i ;
}
2024-04-09 23:20:26 +02:00
if ( i > 1 ) artist = QStringLiteral ( " Various artists " ) ;
2019-04-17 22:18:03 +02:00
2024-04-09 23:20:26 +02:00
QString id = obj_release [ QStringLiteral ( " id " ) ] . toString ( ) ;
QString album = obj_release [ QStringLiteral ( " title " ) ] . toString ( ) ;
2020-04-23 21:04:37 +02:00
2021-02-26 21:03:51 +01:00
CoverProviderSearchResult cover_result ;
2024-04-11 02:56:01 +02:00
QUrl url ( QString : : fromLatin1 ( kAlbumCoverUrl ) . arg ( id ) ) ;
2019-04-17 22:18:03 +02:00
cover_result . artist = artist ;
cover_result . album = album ;
cover_result . image_url = url ;
results . append ( cover_result ) ;
2018-12-15 15:12:18 +01:00
}
emit SearchFinished ( search_id , results ) ;
2018-02-27 18:06:05 +01:00
}
2018-12-15 15:12:18 +01:00
QByteArray MusicbrainzCoverProvider : : GetReplyData ( QNetworkReply * reply ) {
QByteArray data ;
if ( reply - > error ( ) = = QNetworkReply : : NoError & & reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) = = 200 ) {
data = reply - > readAll ( ) ;
}
else {
2019-07-07 21:14:24 +02:00
if ( reply - > error ( ) ! = QNetworkReply : : NoError & & reply - > error ( ) < 200 ) {
2018-12-15 15:12:18 +01:00
// This is a network error, there is nothing more to do.
2024-04-09 23:20:26 +02:00
QString failure_reason = QStringLiteral ( " %1 (%2) " ) . arg ( reply - > errorString ( ) ) . arg ( reply - > error ( ) ) ;
2018-12-15 15:12:18 +01:00
Error ( failure_reason ) ;
}
else {
// See if there is Json data containing "error" - then use that instead.
data = reply - > readAll ( ) ;
2019-07-07 21:14:24 +02:00
QString error ;
QJsonParseError json_error ;
QJsonDocument json_doc = QJsonDocument : : fromJson ( data , & json_error ) ;
2020-10-02 19:27:47 +02:00
if ( json_error . error = = QJsonParseError : : NoError & & ! json_doc . isEmpty ( ) & & json_doc . isObject ( ) ) {
2018-12-15 15:12:18 +01:00
QJsonObject json_obj = json_doc . object ( ) ;
2024-04-09 23:20:26 +02:00
if ( json_obj . contains ( QStringLiteral ( " error " ) ) ) {
error = json_obj [ QStringLiteral ( " error " ) ] . toString ( ) ;
2019-07-07 21:14:24 +02:00
}
}
if ( error . isEmpty ( ) ) {
if ( reply - > error ( ) ! = QNetworkReply : : NoError ) {
2024-04-09 23:20:26 +02:00
error = QStringLiteral ( " %1 (%2) " ) . arg ( reply - > errorString ( ) ) . arg ( reply - > error ( ) ) ;
2018-12-15 15:12:18 +01:00
}
else {
2024-04-09 23:20:26 +02:00
error = QStringLiteral ( " Received HTTP code %1 " ) . arg ( reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ) ;
2018-12-15 15:12:18 +01:00
}
}
2019-07-07 21:14:24 +02:00
Error ( error ) ;
2018-02-27 18:06:05 +01:00
}
2018-12-15 15:12:18 +01:00
return QByteArray ( ) ;
2018-02-27 18:06:05 +01:00
}
2018-12-15 15:12:18 +01:00
return data ;
2018-02-27 18:06:05 +01:00
}
2020-04-23 21:04:37 +02:00
void MusicbrainzCoverProvider : : Error ( const QString & error , const QVariant & debug ) {
2018-12-15 15:12:18 +01:00
qLog ( Error ) < < " Musicbrainz: " < < error ;
if ( debug . isValid ( ) ) qLog ( Debug ) < < debug ;
2020-04-23 21:04:37 +02:00
2018-02-27 18:06:05 +01:00
}