2018-02-27 18:06:05 +01:00
/*
* Strawberry Music Player
* This file was part of Clementine .
* Copyright 2010 , David Sansome < me @ davidsansome . com >
2020-05-09 01:48:08 +02:00
* Copyright 2018 - 2020 , 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"
2018-05-01 00:41:33 +02:00
# include <algorithm>
2018-02-27 18:06:05 +01:00
# include <cmath>
2018-05-01 00:41:33 +02:00
# include <QObject>
2020-04-25 00:03:43 +02:00
# include <QCoreApplication>
2018-02-27 18:06:05 +01:00
# include <QTimer>
2018-05-01 00:41:33 +02:00
# include <QList>
2020-05-29 17:46:41 +02:00
# include <QMap>
# include <QMultiMap>
2018-05-01 00:41:33 +02:00
# include <QString>
# include <QUrl>
# include <QImage>
2020-05-12 18:47:32 +02:00
# include <QImageReader>
2018-05-01 00:41:33 +02:00
# include <QNetworkRequest>
# include <QNetworkReply>
2018-02-27 18:06:05 +01:00
# include <QtDebug>
# include "core/logging.h"
2020-10-11 01:08:42 +02:00
# include "core/utilities.h"
2021-02-26 21:03:51 +01:00
# include "core/imageutils.h"
2021-01-11 16:48:46 +01:00
# include "core/networkaccessmanager.h"
2019-08-20 23:31:23 +02:00
# include "core/networktimeouts.h"
2018-05-01 00:41:33 +02:00
# include "albumcoverfetcher.h"
# include "albumcoverfetchersearch.h"
# include "coverprovider.h"
# include "coverproviders.h"
2021-02-26 21:03:51 +01:00
# include "albumcoverimageresult.h"
2018-02-27 18:06:05 +01:00
2020-04-25 00:13:48 +02:00
const int AlbumCoverFetcherSearch : : kSearchTimeoutMs = 20000 ;
const int AlbumCoverFetcherSearch : : kImageLoadTimeoutMs = 6000 ;
2018-02-27 18:06:05 +01:00
const int AlbumCoverFetcherSearch : : kTargetSize = 500 ;
2020-04-25 00:13:48 +02:00
const float AlbumCoverFetcherSearch : : kGoodScore = 4.0 ;
2018-02-27 18:06:05 +01:00
2020-12-09 18:39:37 +01:00
AlbumCoverFetcherSearch : : AlbumCoverFetcherSearch ( const CoverSearchRequest & request , NetworkAccessManager * network , QObject * parent )
2018-02-27 18:06:05 +01:00
: QObject ( parent ) ,
request_ ( request ) ,
image_load_timeout_ ( new NetworkTimeouts ( kImageLoadTimeoutMs , this ) ) ,
network_ ( network ) ,
cancel_requested_ ( false ) {
2018-03-05 21:05:30 +01:00
2019-08-22 18:45:32 +02:00
// We will terminate the search after kSearchTimeoutMs milliseconds if we are not able to find all of the results before that point in time
2021-01-26 16:48:04 +01:00
QTimer : : singleShot ( kSearchTimeoutMs , this , & AlbumCoverFetcherSearch : : TerminateSearch ) ;
2018-03-05 21:05:30 +01:00
2018-02-27 18:06:05 +01:00
}
2020-05-10 16:54:14 +02:00
AlbumCoverFetcherSearch : : ~ AlbumCoverFetcherSearch ( ) {
pending_requests_ . clear ( ) ;
Cancel ( ) ;
}
2018-02-27 18:06:05 +01:00
void AlbumCoverFetcherSearch : : TerminateSearch ( ) {
2018-03-05 21:05:30 +01:00
2019-07-07 21:14:24 +02:00
for ( quint64 id : pending_requests_ . keys ( ) ) {
2018-02-27 18:06:05 +01:00
pending_requests_ . take ( id ) - > CancelSearch ( id ) ;
}
AllProvidersFinished ( ) ;
2018-03-05 21:05:30 +01:00
2018-02-27 18:06:05 +01:00
}
void AlbumCoverFetcherSearch : : Start ( CoverProviders * cover_providers ) {
2020-09-10 17:17:55 +02:00
// Ignore Radio Paradise "commercial" break.
if ( request_ . artist . toLower ( ) = = " commercial-free " & & request_ . title . toLower ( ) = = " listener-supported " ) {
TerminateSearch ( ) ;
return ;
}
2020-05-09 01:48:08 +02:00
QList < CoverProvider * > cover_providers_sorted = cover_providers - > List ( ) ;
std : : stable_sort ( cover_providers_sorted . begin ( ) , cover_providers_sorted . end ( ) , ProviderCompareOrder ) ;
2018-03-05 21:05:30 +01:00
2020-05-09 01:48:08 +02:00
for ( CoverProvider * provider : cover_providers_sorted ) {
if ( ! provider - > is_enabled ( ) ) continue ;
// Skip any provider that requires authentication but is not authenticated.
if ( provider - > AuthenticationRequired ( ) & & ! provider - > IsAuthenticated ( ) ) {
continue ;
}
2021-02-26 21:03:51 +01:00
// Skip provider if it does not have batch set and we are doing a batch - "Fetch Missing Covers".
if ( ! provider - > batch ( ) & & request_ . batch ) {
2020-04-25 00:03:43 +02:00
continue ;
2020-04-20 18:03:18 +02:00
}
2020-05-09 01:48:08 +02:00
2020-08-07 00:18:31 +02:00
// If artist and album is missing, check if we can still use this provider by searching using title.
2020-08-07 00:28:46 +02:00
if ( ! provider - > allow_missing_album ( ) & & request_ . album . isEmpty ( ) & & ! request_ . title . isEmpty ( ) ) {
2020-04-25 00:03:43 +02:00
continue ;
2018-03-05 21:05:30 +01:00
}
2021-02-26 21:03:51 +01:00
QObject : : connect ( provider , & CoverProvider : : SearchResults , this , QOverload < const int , const CoverProviderSearchResults & > : : of ( & AlbumCoverFetcherSearch : : ProviderSearchResults ) ) ;
2021-01-26 16:48:04 +01:00
QObject : : connect ( provider , & CoverProvider : : SearchFinished , this , & AlbumCoverFetcherSearch : : ProviderSearchFinished ) ;
2018-02-27 18:06:05 +01:00
const int id = cover_providers - > NextId ( ) ;
2020-04-20 18:03:18 +02:00
const bool success = provider - > StartSearch ( request_ . artist , request_ . album , request_ . title , id ) ;
2018-02-27 18:06:05 +01:00
if ( success ) {
pending_requests_ [ id ] = provider ;
statistics_ . network_requests_made_ + + ;
}
}
2018-05-01 00:41:33 +02:00
// End this search before it even began if there are no providers...
2018-02-27 18:06:05 +01:00
if ( pending_requests_ . isEmpty ( ) ) {
TerminateSearch ( ) ;
}
}
2021-02-26 21:03:51 +01:00
void AlbumCoverFetcherSearch : : ProviderSearchResults ( const int id , const CoverProviderSearchResults & results ) {
2018-02-27 18:06:05 +01:00
if ( ! pending_requests_ . contains ( id ) ) return ;
2020-05-14 19:30:29 +02:00
CoverProvider * provider = pending_requests_ [ id ] ;
ProviderSearchResults ( provider , results ) ;
}
2021-02-26 21:03:51 +01:00
void AlbumCoverFetcherSearch : : ProviderSearchResults ( CoverProvider * provider , const CoverProviderSearchResults & results ) {
2018-02-27 18:06:05 +01:00
2021-02-26 21:03:51 +01:00
CoverProviderSearchResults results_copy ( results ) ;
2020-04-20 18:03:18 +02:00
for ( int i = 0 ; i < results_copy . count ( ) ; + + i ) {
2020-08-01 23:17:35 +02:00
2018-02-27 18:06:05 +01:00
results_copy [ i ] . provider = provider - > name ( ) ;
2020-08-09 20:10:53 +02:00
results_copy [ i ] . score_provider = provider - > quality ( ) ;
2020-08-01 23:17:35 +02:00
QString request_artist = request_ . artist . toLower ( ) ;
QString request_album = request_ . album . toLower ( ) ;
QString result_artist = results_copy [ i ] . artist . toLower ( ) ;
QString result_album = results_copy [ i ] . album . toLower ( ) ;
if ( result_artist = = request_artist ) {
2020-08-09 20:10:53 +02:00
results_copy [ i ] . score_match + = 0.5 ;
2019-04-17 22:18:03 +02:00
}
2020-08-01 23:17:35 +02:00
if ( result_album = = request_album ) {
2020-08-09 20:10:53 +02:00
results_copy [ i ] . score_match + = 0.5 ;
2019-04-17 22:24:34 +02:00
}
2020-08-01 23:17:35 +02:00
if ( result_artist ! = request_artist & & result_album ! = request_album ) {
2020-08-09 20:10:53 +02:00
results_copy [ i ] . score_match - = 1.5 ;
2019-04-17 22:18:03 +02:00
}
2020-08-01 23:17:35 +02:00
2020-08-02 04:34:15 +02:00
if ( request_album . isEmpty ( ) & & result_artist ! = request_artist ) {
2020-08-09 20:10:53 +02:00
results_copy [ i ] . score_match - = 1 ;
2020-08-02 04:34:15 +02:00
}
2020-08-01 23:17:35 +02:00
// Decrease score if the search was based on artist and song title, and the resulting album is a compilation or live album.
// This is done since we can't match the album titles, and we want to prevent compilation or live albums from being picked before studio albums for streams.
2020-08-09 20:58:27 +02:00
// TODO: Make these regular expressions.
2020-08-01 23:17:35 +02:00
if ( request_album . isEmpty ( ) & & (
result_album . contains ( " hits " ) | |
2020-08-09 20:58:27 +02:00
result_album . contains ( " greatest " ) | |
2020-08-01 23:17:35 +02:00
result_album . contains ( " best " ) | |
result_album . contains ( " collection " ) | |
result_album . contains ( " classics " ) | |
result_album . contains ( " singles " ) | |
result_album . contains ( " bootleg " ) | |
result_album . contains ( " live " ) | |
2020-08-09 20:58:27 +02:00
result_album . contains ( " concert " ) | |
result_album . contains ( " essential " ) | |
result_album . contains ( " ultimate " ) | |
2020-08-15 17:29:30 +02:00
result_album . contains ( " karaoke " ) | |
2020-08-15 17:33:54 +02:00
result_album . contains ( " mixtape " ) | |
2020-08-09 20:58:27 +02:00
result_album . contains ( " country rock " ) | |
result_album . contains ( " indie folk " ) | |
result_album . contains ( " soft rock " ) | |
result_album . contains ( " folk music " ) | |
result_album . contains ( " 60's rock " ) | |
result_album . contains ( " 60's romance " ) | |
result_album . contains ( " 60s music " ) | |
result_album . contains ( " late 60s " ) | |
result_album . contains ( " the 60s " ) | |
result_album . contains ( " folk and blues " ) | |
result_album . contains ( " 60 from the 60's " ) | |
result_album . contains ( " classic psychedelic " ) | |
result_album . contains ( " playlist: acoustic " ) | |
result_album . contains ( " 90's rnb playlist " ) | |
result_album . contains ( " rock 80s " ) | |
result_album . contains ( " classic 80s " ) | |
result_album . contains ( " rock anthems " ) | |
result_album . contains ( " rock songs " ) | |
result_album . contains ( " rock 2019 " ) | |
result_album . contains ( " guitar anthems " ) | |
result_album . contains ( " driving anthems " ) | |
result_album . contains ( " traffic jam jams " ) | |
result_album . contains ( " perfect background music " ) | |
result_album . contains ( " 70's gold " ) | |
result_album . contains ( " rockfluence " ) | |
2020-08-10 17:39:40 +02:00
result_album . contains ( " acoustic dinner accompaniment " ) | |
2020-08-15 17:29:30 +02:00
result_album . contains ( " complete studio albums " ) | |
result_album . contains ( " mellow rock " )
2020-08-01 23:17:35 +02:00
) ) {
2020-08-09 20:10:53 +02:00
results_copy [ i ] . score_match - = 1 ;
2020-08-01 23:17:35 +02:00
}
2020-09-10 17:17:55 +02:00
else if ( request_album . isEmpty ( ) & & result_album . contains ( " soundtrack " ) ) {
results_copy [ i ] . score_match - = 0.5 ;
}
2020-08-01 23:17:35 +02:00
2020-08-09 20:10:53 +02:00
// Set the initial image quality score besed on the size returned by the API, this is recalculated when the image is received.
results_copy [ i ] . score_quality + = ScoreImage ( results_copy [ i ] . image_size ) ;
2018-02-27 18:06:05 +01:00
}
// Add results from the current provider to our pool
results_ . append ( results_copy ) ;
statistics_ . total_images_by_provider_ [ provider - > name ( ) ] + + ;
2020-05-14 19:30:29 +02:00
}
2021-02-26 21:03:51 +01:00
void AlbumCoverFetcherSearch : : ProviderSearchFinished ( const int id , const CoverProviderSearchResults & results ) {
2020-05-14 19:30:29 +02:00
if ( ! pending_requests_ . contains ( id ) ) return ;
CoverProvider * provider = pending_requests_ . take ( id ) ;
ProviderSearchResults ( provider , results ) ;
2018-05-01 00:41:33 +02:00
// Do we have more providers left?
2018-02-27 18:06:05 +01:00
if ( ! pending_requests_ . isEmpty ( ) ) {
return ;
}
AllProvidersFinished ( ) ;
2019-04-17 22:18:03 +02:00
2018-02-27 18:06:05 +01:00
}
void AlbumCoverFetcherSearch : : AllProvidersFinished ( ) {
if ( cancel_requested_ ) {
return ;
}
2018-05-01 00:41:33 +02:00
// If we only wanted to do the search then we're done
2018-02-27 18:06:05 +01:00
if ( request_ . search ) {
emit SearchFinished ( request_ . id , results_ ) ;
return ;
}
2018-05-01 00:41:33 +02:00
// No results?
2018-02-27 18:06:05 +01:00
if ( results_ . isEmpty ( ) ) {
statistics_ . missing_images_ + + ;
2021-02-26 21:03:51 +01:00
emit AlbumCoverFetched ( request_ . id , AlbumCoverImageResult ( ) ) ;
2018-02-27 18:06:05 +01:00
return ;
}
// Now we have to load some images and figure out which one is the best.
2020-04-25 00:03:43 +02:00
// We'll sort the list of results by current score, then load the first 3 images from each category and use some heuristics for additional score.
2018-05-01 00:41:33 +02:00
// If no images are good enough we'll keep loading more images until we find one that is or we run out of results.
2020-04-25 00:03:43 +02:00
2021-02-26 21:03:51 +01:00
std : : stable_sort ( results_ . begin ( ) , results_ . end ( ) , CoverProviderSearchResultCompareScore ) ;
2020-04-25 00:03:43 +02:00
2018-02-27 18:06:05 +01:00
FetchMoreImages ( ) ;
}
void AlbumCoverFetcherSearch : : FetchMoreImages ( ) {
2020-04-25 00:03:43 +02:00
int i = 0 ;
while ( ! results_ . isEmpty ( ) ) {
+ + i ;
2021-02-26 21:03:51 +01:00
CoverProviderSearchResult result = results_ . takeFirst ( ) ;
2018-02-27 18:06:05 +01:00
2020-08-10 18:05:52 +02:00
qLog ( Debug ) < < " Loading " < < result . artist < < result . album < < result . image_url < < " from " < < result . provider < < " with current score " < < result . score ( ) ;
2018-02-27 18:06:05 +01:00
2019-08-22 19:28:54 +02:00
QNetworkRequest req ( result . image_url ) ;
2020-08-14 20:20:41 +02:00
# if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
req . setAttribute ( QNetworkRequest : : RedirectPolicyAttribute , QNetworkRequest : : NoLessSafeRedirectPolicy ) ;
# else
2019-08-22 19:28:54 +02:00
req . setAttribute ( QNetworkRequest : : FollowRedirectsAttribute , true ) ;
2020-08-14 20:20:41 +02:00
# endif
2019-08-22 19:28:54 +02:00
QNetworkReply * image_reply = network_ - > get ( req ) ;
2021-01-26 16:48:04 +01:00
QObject : : connect ( image_reply , & QNetworkReply : : finished , [ this , image_reply ] ( ) { ProviderCoverFetchFinished ( image_reply ) ; } ) ;
2019-04-17 22:18:03 +02:00
pending_image_loads_ [ image_reply ] = result ;
2018-02-27 18:06:05 +01:00
image_load_timeout_ - > AddReply ( image_reply ) ;
2019-08-22 19:28:54 +02:00
+ + statistics_ . network_requests_made_ ;
2020-04-25 00:03:43 +02:00
if ( i > = 3 ) break ;
2018-02-27 18:06:05 +01:00
}
if ( pending_image_loads_ . isEmpty ( ) ) {
// There were no more results? Time to give up.
SendBestImage ( ) ;
}
}
2019-08-22 19:28:54 +02:00
void AlbumCoverFetcherSearch : : ProviderCoverFetchFinished ( QNetworkReply * reply ) {
2018-02-27 18:06:05 +01:00
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( reply , nullptr , this , nullptr ) ;
2018-02-27 18:06:05 +01:00
reply - > deleteLater ( ) ;
2019-04-17 22:18:03 +02:00
if ( ! pending_image_loads_ . contains ( reply ) ) return ;
2021-02-26 21:03:51 +01:00
CoverProviderSearchResult result = pending_image_loads_ . take ( reply ) ;
2018-02-27 18:06:05 +01:00
statistics_ . bytes_transferred_ + = reply - > bytesAvailable ( ) ;
2021-02-26 21:03:51 +01:00
if ( cancel_requested_ ) return ;
2018-02-27 18:06:05 +01:00
if ( reply - > error ( ) ! = QNetworkReply : : NoError ) {
2019-04-17 22:18:03 +02:00
qLog ( Error ) < < " Error requesting " < < reply - > url ( ) < < reply - > errorString ( ) ;
2018-02-27 18:06:05 +01:00
}
2020-05-12 18:47:32 +02:00
else if ( reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ! = 200 ) {
qLog ( Error ) < < " Error requesting " < < reply - > url ( ) < < " received HTTP code " < < reply - > attribute ( QNetworkRequest : : HttpStatusCodeAttribute ) . toInt ( ) ;
}
2018-02-27 18:06:05 +01:00
else {
2020-05-12 18:47:32 +02:00
QString mimetype = reply - > header ( QNetworkRequest : : ContentTypeHeader ) . toString ( ) ;
2021-02-26 21:03:51 +01:00
if ( ImageUtils : : SupportedImageMimeTypes ( ) . contains ( mimetype , Qt : : CaseInsensitive ) | | ImageUtils : : SupportedImageFormats ( ) . contains ( mimetype , Qt : : CaseInsensitive ) ) {
QByteArray image_data = reply - > readAll ( ) ;
QString mime_type = Utilities : : MimeTypeFromData ( image_data ) ;
2020-05-12 18:47:32 +02:00
QImage image ;
2021-02-26 21:03:51 +01:00
if ( image . loadFromData ( image_data ) ) {
if ( result . image_size ! = QSize ( 0 , 0 ) & & result . image_size ! = image . size ( ) ) {
2020-08-09 20:10:53 +02:00
qLog ( Debug ) < < " API size for image " < < result . image_size < < " for " < < reply - > url ( ) < < " from " < < result . provider < < " did not match retrieved size " < < image . size ( ) ;
}
result . image_size = image . size ( ) ;
result . score_quality = ScoreImage ( image . size ( ) ) ;
2021-02-26 21:03:51 +01:00
candidate_images_ . insert ( result . score ( ) , CandidateImage ( result , AlbumCoverImageResult ( result . image_url , mime_type , image_data , image ) ) ) ;
2020-08-09 20:10:53 +02:00
qLog ( Debug ) < < reply - > url ( ) < < " from " < < result . provider < < " scored " < < result . score ( ) ;
2020-05-12 18:47:32 +02:00
}
else {
qLog ( Error ) < < " Error decoding image data from " < < reply - > url ( ) ;
}
2018-02-27 18:06:05 +01:00
}
else {
2020-05-12 18:47:32 +02:00
qLog ( Error ) < < " Unsupported mimetype for image reader: " < < mimetype < < " from " < < reply - > url ( ) ;
2018-02-27 18:06:05 +01:00
}
}
if ( pending_image_loads_ . isEmpty ( ) ) {
2018-05-01 00:41:33 +02:00
// We've fetched everything we wanted to fetch for now, check if we have an image that's good enough.
2018-02-27 18:06:05 +01:00
float best_score = 0.0 ;
if ( ! candidate_images_ . isEmpty ( ) ) {
best_score = candidate_images_ . keys ( ) . last ( ) ;
2020-04-25 00:03:43 +02:00
qLog ( Debug ) < < " Best image so far has a score of " < < best_score ;
2018-02-27 18:06:05 +01:00
}
if ( best_score > = kGoodScore ) {
SendBestImage ( ) ;
}
else {
FetchMoreImages ( ) ;
}
}
}
2020-08-09 20:10:53 +02:00
float AlbumCoverFetcherSearch : : ScoreImage ( const QSize size ) const {
2018-02-27 18:06:05 +01:00
2020-08-09 20:10:53 +02:00
if ( size . width ( ) = = 0 | | size . height ( ) = = 0 ) return 0.0 ;
2018-02-27 18:06:05 +01:00
// A 500x500px image scores 1.0, bigger scores higher
2020-08-09 20:10:53 +02:00
const float size_score = std : : sqrt ( float ( size . width ( ) * size . height ( ) ) ) / kTargetSize ;
2018-02-27 18:06:05 +01:00
// A 1:1 image scores 1.0, anything else scores less
2020-08-09 20:10:53 +02:00
const float aspect_score = 1.0 - float ( std : : max ( size . width ( ) , size . height ( ) ) - std : : min ( size . width ( ) , size . height ( ) ) ) / std : : max ( size . height ( ) , size . width ( ) ) ;
2018-02-27 18:06:05 +01:00
return size_score + aspect_score ;
}
void AlbumCoverFetcherSearch : : SendBestImage ( ) {
2021-02-26 21:03:51 +01:00
AlbumCoverImageResult result ;
2018-02-27 18:06:05 +01:00
if ( ! candidate_images_ . isEmpty ( ) ) {
const CandidateImage best_image = candidate_images_ . values ( ) . back ( ) ;
2021-02-26 21:03:51 +01:00
result = best_image . album_cover ;
2018-02-27 18:06:05 +01:00
2021-02-26 21:03:51 +01:00
qLog ( Info ) < < " Using " < < best_image . result . image_url < < " from " < < best_image . result . provider < < " with score " < < best_image . result . score ( ) ;
2019-04-17 22:18:03 +02:00
2021-02-26 21:03:51 +01:00
statistics_ . chosen_images_by_provider_ [ best_image . result . provider ] + + ;
2018-02-27 18:06:05 +01:00
statistics_ . chosen_images_ + + ;
2021-02-26 21:03:51 +01:00
statistics_ . chosen_width_ + = result . image . width ( ) ;
statistics_ . chosen_height_ + = result . image . height ( ) ;
2018-02-27 18:06:05 +01:00
}
else {
statistics_ . missing_images_ + + ;
}
2021-02-26 21:03:51 +01:00
emit AlbumCoverFetched ( request_ . id , result ) ;
2018-02-27 18:06:05 +01:00
}
void AlbumCoverFetcherSearch : : Cancel ( ) {
cancel_requested_ = true ;
if ( ! pending_requests_ . isEmpty ( ) ) {
TerminateSearch ( ) ;
}
else if ( ! pending_image_loads_ . isEmpty ( ) ) {
2019-08-22 19:28:54 +02:00
for ( QNetworkReply * reply : pending_image_loads_ . keys ( ) ) {
2021-01-26 16:48:04 +01:00
QObject : : disconnect ( reply , & QNetworkReply : : finished , this , nullptr ) ;
2018-02-27 18:06:05 +01:00
reply - > abort ( ) ;
2020-05-10 16:54:14 +02:00
reply - > deleteLater ( ) ;
2018-02-27 18:06:05 +01:00
}
pending_image_loads_ . clear ( ) ;
}
}
2020-04-25 00:03:43 +02:00
2020-05-09 01:48:08 +02:00
bool AlbumCoverFetcherSearch : : ProviderCompareOrder ( CoverProvider * a , CoverProvider * b ) {
return a - > order ( ) < b - > order ( ) ;
}
2021-02-26 21:03:51 +01:00
bool AlbumCoverFetcherSearch : : CoverProviderSearchResultCompareScore ( const CoverProviderSearchResult & a , const CoverProviderSearchResult & b ) {
2020-08-09 20:10:53 +02:00
return a . score ( ) > b . score ( ) ;
}
2021-02-26 21:03:51 +01:00
bool AlbumCoverFetcherSearch : : CoverProviderSearchResultCompareNumber ( const CoverProviderSearchResult & a , const CoverProviderSearchResult & b ) {
2020-08-09 20:10:53 +02:00
return a . number < b . number ;
2020-04-25 00:03:43 +02:00
}