initial commit of "multiple covers providers" feature:

- simple API for cover providers (both C++ and SIP)
- a new "package" for cover related code
This commit is contained in:
Paweł Bara 2011-04-02 13:34:06 +00:00
parent ff8110244f
commit 032b5f7e48
41 changed files with 826 additions and 230 deletions

View File

@ -56,8 +56,6 @@ set(SOURCES
analyzers/sonogram.cpp
analyzers/turbine.cpp
core/albumcoverloader.cpp
core/artloader.cpp
core/backgroundstreams.cpp
core/backgroundthread.cpp
core/commandlineoptions.cpp
@ -71,7 +69,6 @@ set(SOURCES
core/globalshortcutbackend.cpp
core/globalshortcuts.cpp
core/gnomeglobalshortcutbackend.cpp
core/kittenloader.cpp
core/mergedproxymodel.cpp
core/musicstorage.cpp
core/network.cpp
@ -88,6 +85,15 @@ set(SOURCES
core/taskmanager.cpp
core/utilities.cpp
covers/albumcoverfetcher.cpp
covers/albumcoverfetchersearch.cpp
covers/albumcoverloader.cpp
covers/artloader.cpp
covers/coverprovider.cpp
covers/coverproviders.cpp
covers/kittenloader.cpp
covers/lastfmcoverprovider.cpp
devices/connecteddevice.cpp
devices/devicedatabasebackend.cpp
devices/devicelister.cpp
@ -268,8 +274,6 @@ set(HEADERS
analyzers/sonogram.h
analyzers/turbine.h
core/albumcoverloader.h
core/artloader.h
core/backgroundstreams.h
core/backgroundthread.h
core/crashreporting.h
@ -278,7 +282,6 @@ set(HEADERS
core/globalshortcuts.h
core/globalshortcutbackend.h
core/gnomeglobalshortcutbackend.h
core/kittenloader.h
core/mergedproxymodel.h
core/mimedata.h
core/network.h
@ -287,6 +290,15 @@ set(HEADERS
core/songloader.h
core/taskmanager.h
covers/albumcoverfetcher.h
covers/albumcoverfetchersearch.h
covers/albumcoverloader.h
covers/artloader.h
covers/coverprovider.h
covers/coverproviders.h
covers/kittenloader.h
covers/lastfmcoverprovider.h
devices/connecteddevice.h
devices/devicedatabasebackend.h
devices/devicelister.h
@ -545,7 +557,6 @@ endif(ENABLE_VISUALISATIONS)
# Lastfm
if(HAVE_LIBLASTFM)
list(APPEND SOURCES
core/albumcoverfetcher.cpp
radio/fixlastfm.cpp
radio/lastfmconfig.cpp
radio/lastfmservice.cpp
@ -559,7 +570,6 @@ if(HAVE_LIBLASTFM)
ui/albumcoversearcher.cpp
)
list(APPEND HEADERS
core/albumcoverfetcher.h
radio/lastfmconfig.h
radio/lastfmservice.h
radio/lastfmstationdialog.h
@ -767,6 +777,7 @@ if(HAVE_SCRIPTING_PYTHON)
${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemPtr.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementinePlaylistItemSpecialLoadResult.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementineTaskManagerTask.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100CoverSearchResult.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100Directory.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100LibraryBackendAlbum.cpp
${CMAKE_CURRENT_BINARY_DIR}/sipclementineQList0100PlaylistItemPtr.cpp

View File

@ -1,164 +0,0 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "albumcoverfetcher.h"
#include "network.h"
#include <QNetworkReply>
#include <QTimer>
#include <lastfm/Artist>
#include <lastfm/XmlQuery>
#include <lastfm/ws.h>
const int AlbumCoverFetcher::kMaxConcurrentRequests = 5;
AlbumCoverFetcher::AlbumCoverFetcher(QObject* parent, QNetworkAccessManager* network)
: QObject(parent),
network_(network ? network : new NetworkAccessManager(this)),
next_id_(0),
request_starter_(new QTimer(this))
{
request_starter_->setInterval(1000);
connect(request_starter_, SIGNAL(timeout()), SLOT(StartRequests()));
}
quint64 AlbumCoverFetcher::FetchAlbumCover(
const QString& artist_name, const QString& album_name) {
QueuedRequest request;
request.query = artist_name + " " + album_name;
request.search = false;
request.id = next_id_ ++;
AddRequest(request);
return request.id;
}
quint64 AlbumCoverFetcher::SearchForCovers(const QString &query) {
QueuedRequest request;
request.query = query;
request.search = true;
request.id = next_id_ ++;
AddRequest(request);
return request.id;
}
void AlbumCoverFetcher::AddRequest(QueuedRequest req) {
queued_requests_.enqueue(req);
if (!request_starter_->isActive())
request_starter_->start();
if (active_requests_.count() < kMaxConcurrentRequests)
StartRequests();
}
void AlbumCoverFetcher::Clear() {
queued_requests_.clear();
}
void AlbumCoverFetcher::StartRequests() {
if (queued_requests_.isEmpty()) {
request_starter_->stop();
return;
}
while (!queued_requests_.isEmpty() &&
active_requests_.count() < kMaxConcurrentRequests) {
QueuedRequest request = queued_requests_.dequeue();
QMap<QString, QString> params;
params["method"] = "album.search";
params["album"] = request.query;
QNetworkReply* reply = lastfm::ws::post(params);
connect(reply, SIGNAL(finished()), SLOT(AlbumSearchFinished()));
active_requests_.insert(reply, request);
}
}
void AlbumCoverFetcher::AlbumSearchFinished() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
reply->deleteLater();
QueuedRequest request = active_requests_.take(reply);
if (reply->error() != QNetworkReply::NoError) {
// TODO: retry request.
emit AlbumCoverFetched(request.id, QImage());
return;
}
try {
lastfm::XmlQuery query(lastfm::ws::parse(reply));
#ifdef Q_OS_WIN32
if (lastfm::ws::last_parse_error != lastfm::ws::NoError)
throw std::runtime_error("");
#endif
// Parse the list of search results
QList<lastfm::XmlQuery> elements = query["results"]["albummatches"].children("album");
SearchResults results;
foreach (const lastfm::XmlQuery& element, elements) {
SearchResult result;
result.album = element["name"].text();
result.artist = element["artist"].text();
result.image_url = element["image size=extralarge"].text();
results << result;
}
// If we only wanted to do the search then we're done
if (request.search) {
emit SearchFinished(request.id, results);
return;
}
// No results?
if (results.isEmpty()) {
emit AlbumCoverFetched(request.id, QImage());
return;
}
// Now we need to fetch the first result's image
QNetworkReply* image_reply = network_->get(QNetworkRequest(results[0].image_url));
connect(image_reply, SIGNAL(finished()), SLOT(AlbumCoverFetchFinished()));
active_requests_[image_reply] = request;
} catch (std::runtime_error&) {
if (request.search)
emit SearchFinished(request.id, AlbumCoverFetcher::SearchResults());
else
emit AlbumCoverFetched(request.id, QImage());
}
}
void AlbumCoverFetcher::AlbumCoverFetchFinished() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
reply->deleteLater();
QueuedRequest request = active_requests_.take(reply);
if (reply->error() != QNetworkReply::NoError) {
// TODO: retry request.
emit AlbumCoverFetched(request.id, QImage());
return;
}
QImage image;
image.loadFromData(reply->readAll());
emit AlbumCoverFetched(request.id, image);
}

View File

@ -18,7 +18,7 @@
#include "mpris.h"
#include "mpris1.h"
#include "mpris2.h"
#include "core/artloader.h"
#include "covers/artloader.h"
namespace mpris {

View File

@ -15,9 +15,9 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "artloader.h"
#include "mpris1.h"
#include "mpris_common.h"
#include "covers/artloader.h"
#include <QCoreApplication>
#include <QDBusConnection>

View File

@ -19,11 +19,11 @@
#include "mpris_common.h"
#include "mpris1.h"
#include "mpris2.h"
#include "core/artloader.h"
#include "core/mpris2_player.h"
#include "core/mpris2_root.h"
#include "core/mpris2_tracklist.h"
#include "core/player.h"
#include "covers/artloader.h"
#include "engines/enginebase.h"
#include "playlist/playlist.h"
#include "playlist/playlistmanager.h"

View File

@ -24,8 +24,8 @@
#include <boost/scoped_ptr.hpp>
#include "config.h"
#include "core/albumcoverloader.h"
#include "core/song.h"
#include "covers/albumcoverloader.h"
#include "engines/engine_fwd.h"
#include "playlist/playlistitem.h"

View File

@ -65,10 +65,9 @@
#include <boost/scoped_ptr.hpp>
using boost::scoped_ptr;
#include "albumcoverloader.h"
#include "encoding.h"
#include "utilities.h"
#include "covers/albumcoverloader.h"
#include "engines/enginebase.h"
#include "library/sqlrow.h"
#include "widgets/trackslider.h"

View File

@ -0,0 +1,106 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "albumcoverfetcher.h"
#include "albumcoverfetchersearch.h"
#include "core/network.h"
#include <QTimer>
const int AlbumCoverFetcher::kMaxConcurrentRequests = 5;
AlbumCoverFetcher::AlbumCoverFetcher(QObject* parent, QNetworkAccessManager* network)
: QObject(parent),
network_(network ? network : new NetworkAccessManager(this)),
next_id_(0),
request_starter_(new QTimer(this))
{
request_starter_->setInterval(1000);
connect(request_starter_, SIGNAL(timeout()), SLOT(StartRequests()));
}
quint64 AlbumCoverFetcher::FetchAlbumCover(
const QString& artist_name, const QString& album_name) {
CoverSearchRequest request;
request.query = artist_name + " " + album_name;
request.search = false;
request.id = next_id_ ++;
AddRequest(request);
return request.id;
}
quint64 AlbumCoverFetcher::SearchForCovers(const QString &query) {
CoverSearchRequest request;
request.query = query;
request.search = true;
request.id = next_id_ ++;
AddRequest(request);
return request.id;
}
void AlbumCoverFetcher::AddRequest(const CoverSearchRequest& req) {
queued_requests_.enqueue(req);
if (!request_starter_->isActive())
request_starter_->start();
if (active_requests_.size() < kMaxConcurrentRequests)
StartRequests();
}
void AlbumCoverFetcher::Clear() {
queued_requests_.clear();
}
void AlbumCoverFetcher::StartRequests() {
if (queued_requests_.isEmpty()) {
request_starter_->stop();
return;
}
while (!queued_requests_.isEmpty() &&
active_requests_.size() < kMaxConcurrentRequests) {
CoverSearchRequest request = queued_requests_.dequeue();
// search objects are this fetcher's children so worst case scenario - they get
// deleted with it
AlbumCoverFetcherSearch* search = new AlbumCoverFetcherSearch(request, network_,
this);
active_requests_.insert(request.id, search);
connect(search, SIGNAL(SearchFinished(quint64, CoverSearchResults)),
SLOT(SingleSearchFinished(quint64, CoverSearchResults)));
connect(search, SIGNAL(AlbumCoverFetched(quint64, const QImage&)),
SLOT(SingleCoverFetched(quint64, const QImage&)));
search->Start();
}
}
void AlbumCoverFetcher::SingleSearchFinished(quint64 request_id, CoverSearchResults results) {
delete active_requests_.take(request_id);
emit SearchFinished(request_id, results);
}
void AlbumCoverFetcher::SingleCoverFetched(quint64 request_id, const QImage& image) {
delete active_requests_.take(request_id);
emit AlbumCoverFetched(request_id, image);
}

View File

@ -18,21 +18,48 @@
#ifndef ALBUMCOVERFETCHER_H
#define ALBUMCOVERFETCHER_H
#include <QHash>
#include <QImage>
#include <QMap>
#include <QList>
#include <QNetworkAccessManager>
#include <QObject>
#include <QQueue>
#include <lastfm/Album>
#include <boost/scoped_ptr.hpp>
class QNetworkReply;
class QString;
class AlbumCoverFetcherSearch;
// This class represents a single search-for-cover request. It identifies
// and describes the request.
struct CoverSearchRequest {
// an unique (for one AlbumCoverFetcher) request identifier
quint64 id;
// a search query
QString query;
// is this only a search request or should we also fetch the first
// cover that's found?
bool search;
};
// This structure represents a single result of some album's cover search request.
// It contains an URL that leads to a found cover plus it's description (usually
// the "artist - album" string).
struct CoverSearchResult {
// description of this result (we suggest using the "artist - album" format)
QString description;
// an URL of a cover image described by this CoverSearchResult
QString image_url;
};
// This is a complete result of a single search request (a list of results, each
// describing one image, actually).
typedef QList<CoverSearchResult> CoverSearchResults;
// This class searches for album covers for a given query or artist/album and
// returns URLs.
// returns URLs. It's NOT thread-safe.
class AlbumCoverFetcher : public QObject {
Q_OBJECT
@ -40,13 +67,6 @@ class AlbumCoverFetcher : public QObject {
AlbumCoverFetcher(QObject* parent = 0, QNetworkAccessManager* network = 0);
virtual ~AlbumCoverFetcher() {}
struct SearchResult {
QString artist;
QString album;
QString image_url;
};
typedef QList<SearchResult> SearchResults;
static const int kMaxConcurrentRequests;
quint64 SearchForCovers(const QString& query);
@ -56,27 +76,21 @@ class AlbumCoverFetcher : public QObject {
signals:
void AlbumCoverFetched(quint64, const QImage& cover);
void SearchFinished(quint64, const AlbumCoverFetcher::SearchResults& results);
void SearchFinished(quint64, const CoverSearchResults& results);
private slots:
void AlbumSearchFinished();
void AlbumCoverFetchFinished();
void SingleSearchFinished(quint64, CoverSearchResults results);
void SingleCoverFetched(quint64, const QImage& cover);
void StartRequests();
private:
struct QueuedRequest {
quint64 id;
QString query;
bool search;
};
void AddRequest(const QueuedRequest req);
void AddRequest(const CoverSearchRequest& req);
QNetworkAccessManager* network_;
quint64 next_id_;
QQueue<QueuedRequest> queued_requests_;
QMap<QNetworkReply*, QueuedRequest> active_requests_;
QQueue<CoverSearchRequest> queued_requests_;
QHash<quint64, AlbumCoverFetcherSearch*> active_requests_;
QTimer* request_starter_;
};

View File

@ -0,0 +1,117 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "albumcoverfetcher.h"
#include "albumcoverfetchersearch.h"
#include "coverprovider.h"
#include "coverproviders.h"
#include <QMutexLocker>
#include <QNetworkReply>
const int AlbumCoverFetcherSearch::kSearchTimeout = 10000;
AlbumCoverFetcherSearch::AlbumCoverFetcherSearch(const CoverSearchRequest& request,
QNetworkAccessManager* network,
QObject* parent)
: QObject(parent),
request_(request),
network_(network)
{
// we will terminate the search after kSearchTimeout miliseconds if we are not
// able to find any results before that point in time
startTimer(kSearchTimeout);
}
void AlbumCoverFetcherSearch::timerEvent(QTimerEvent* event) {
Q_UNUSED(event);
if(request_.search) {
emit SearchFinished(request_.id, CoverSearchResults());
} else {
emit AlbumCoverFetched(request_.id, QImage());
}
}
void AlbumCoverFetcherSearch::Start() {
QList<CoverProvider*> providers_list = CoverProviders::instance().List();
providers_left_ = providers_list.size();
foreach(CoverProvider* provider, providers_list) {
QNetworkReply* reply = provider->SendRequest(request_.query);
connect(reply, SIGNAL(finished()), SLOT(ProviderSearchFinished()));
providers_.insert(reply, provider);
}
}
void AlbumCoverFetcherSearch::ProviderSearchFinished() {
{
QMutexLocker locker(&search_mutex_);
Q_UNUSED(locker);
providers_left_--;
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
reply->deleteLater();
if(reply->error() == QNetworkReply::NoError) {
CoverProvider* provider = providers_.take(reply);
CoverSearchResults partial_results = provider->ParseReply(reply);
// add results from the current provider to our pool
results_.append(partial_results);
}
// do we have more providers left?
if(providers_left_) {
return;
}
}
// if we only wanted to do the search then we're done
if (request_.search) {
emit SearchFinished(request_.id, results_);
return;
}
// no results?
if (results_.isEmpty()) {
emit AlbumCoverFetched(request_.id, QImage());
return;
}
// now we need to fetch the first result's image
QNetworkReply* image_reply = network_->get(QNetworkRequest(results_[0].image_url));
connect(image_reply, SIGNAL(finished()), SLOT(ProviderCoverFetchFinished()));
}
void AlbumCoverFetcherSearch::ProviderCoverFetchFinished() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
// TODO: retry request.
emit AlbumCoverFetched(request_.id, QImage());
} else {
QImage image;
image.loadFromData(reply->readAll());
emit AlbumCoverFetched(request_.id, image);
}
}

View File

@ -0,0 +1,81 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ALBUMCOVERFETCHERSEARCH_H
#define ALBUMCOVERFETCHERSEARCH_H
#include "albumcoverfetcher.h"
#include <QMap>
#include <QMutex>
#include <QObject>
class CoverProvider;
class QNetworkAccessManager;
class QNetworkReply;
class QTimerEvent;
// This class encapsulates a single search for covers initiated by an AlbumCoverFetcher.
// The search engages all of the known cover providers. AlbumCoverFetcherSearch signals
// search results to an interested AlbumCoverFetcher when all of the providers have done
// their part.
class AlbumCoverFetcherSearch : public QObject {
Q_OBJECT
public:
// A timeout (in miliseconds) for every search.
static const int kSearchTimeout;
AlbumCoverFetcherSearch(const CoverSearchRequest& request, QNetworkAccessManager* network,
QObject* parent);
virtual ~AlbumCoverFetcherSearch() {}
// Starts the search. This is the moment when we count cover providers available
// in the application.
void Start();
signals:
// It's the end of search (when there was no fetch-me-a-cover request).
void SearchFinished(quint64, CoverSearchResults results);
// It's the end of search and we've fetched a cover.
void AlbumCoverFetched(quint64, const QImage& cover);
protected:
void timerEvent(QTimerEvent* event);
private slots:
void ProviderSearchFinished();
void ProviderCoverFetchFinished();
private:
// Search request encapsulated by this AlbumCoverFetcherSearch.
CoverSearchRequest request_;
// Complete results (from all of the available providers).
CoverSearchResults results_;
// We initialize this in the Start() method.
// When this reaches 0, the search is over and appropriate signal
// is emitted.
int providers_left_;
QMap<QNetworkReply*, CoverProvider*> providers_;
QNetworkAccessManager* network_;
QMutex search_mutex_;
};
#endif // ALBUMCOVERFETCHERSEARCH_H

View File

@ -16,8 +16,8 @@
*/
#include "albumcoverloader.h"
#include "network.h"
#include "utilities.h"
#include "core/network.h"
#include "core/utilities.h"
#include <QPainter>
#include <QDir>

View File

@ -18,8 +18,8 @@
#ifndef ALBUMCOVERLOADER_H
#define ALBUMCOVERLOADER_H
#include "backgroundthread.h"
#include "song.h"
#include "core/backgroundthread.h"
#include "core/song.h"
#include <QObject>
#include <QImage>

View File

@ -18,8 +18,8 @@
#ifndef ARTLOADER_H
#define ARTLOADER_H
#include "backgroundthread.h"
#include "song.h"
#include "core/backgroundthread.h"
#include "core/song.h"
#include <QObject>

View File

@ -0,0 +1,24 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "coverprovider.h"
CoverProvider::CoverProvider(const QString& name, QObject* parent)
: QObject(parent),
name_(name)
{
}

View File

@ -0,0 +1,59 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef COVERPROVIDER_H
#define COVERPROVIDER_H
#include "albumcoverfetcher.h"
#include "coverproviders.h"
#include <QObject>
class QNetworkReply;
// Each implementation of this interface downloads covers from one online
// service. There are no limitations on what this service might be - last.fm,
// Amazon, Google Images - you name it.
class CoverProvider : public QObject {
Q_OBJECT
public:
CoverProvider(const QString& name, QObject* parent = &CoverProviders::instance());
virtual ~CoverProvider() {}
// A name (very short description) of this provider, like "last.fm".
QString name() const { return name_; }
// Given a search request from Clementine, provider has to create and invoke
// a NetworkRequest. It then has to return a corresponding NetworkReply,
// without connecting to it's finished() signal!
// Responsibilities of provider:
// - maps the given query to a NetworkRequest that a service this provider
// uses will understand
// - makes the prepared request and returns the resulting reply
virtual QNetworkReply* SendRequest(const QString& query) = 0;
// Provider parses a reply which is now filled with data obtained from a service
// this provider communicates with. The result is a QList of CoverSearchResult
// objects.
virtual CoverSearchResults ParseReply(QNetworkReply* reply) = 0;
private:
QString name_;
};
#endif // COVERPROVIDER_H

View File

@ -0,0 +1,63 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "coverprovider.h"
#include "coverproviders.h"
#include "lastfmcoverprovider.h"
CoverProviders::CoverProviders()
{
// registering built-in providers...
// every built-in provider needs an explicit parent; otherwise,
// the default parent, namely CoverProviders::instance(), will
// cause an infinite recursion here
cover_providers_.append(new LastFmCoverProvider(this));
}
void CoverProviders::AddCoverProvider(CoverProvider* provider) {
{
QMutexLocker locker(&mutex_);
Q_UNUSED(locker);
cover_providers_.append(provider);
connect(provider, SIGNAL(destroyed()), SLOT(RemoveCoverProvider()));
}
}
void CoverProviders::RemoveCoverProvider() {
// qobject_cast doesn't work here with providers created by python
CoverProvider* provider = static_cast<CoverProvider*>(sender());
if (provider) {
{
QMutexLocker locker(&mutex_);
Q_UNUSED(locker);
cover_providers_.removeAll(provider);
}
}
}
const QList<CoverProvider*> CoverProviders::List() {
{
QMutexLocker locker(&mutex_);
Q_UNUSED(locker);
return QList<CoverProvider*>(cover_providers_);
}
}

View File

@ -0,0 +1,59 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef COVERPROVIDERS_H
#define COVERPROVIDERS_H
#include <QMutex>
#include <QObject>
class CoverProvider;
// This is a singleton, a global repository for cover providers. Each one of those has to register
// with CoverProviders instance by invoking "CoverProviders::instance().AddCoverProvider(this)".
// Providers are automatically unregistered from the repository when they are deleted.
// The class is thread safe except for the initialization.
class CoverProviders : public QObject {
Q_OBJECT
public:
// This performs lazy initialization of the CoverProviders which is not thread-safe!
static CoverProviders& instance() {
static CoverProviders instance_;
return instance_;
}
// Let's a cover provider to register itself in the repository.
void AddCoverProvider(CoverProvider* provider);
// Returns a list of the currently registered cover providers.
const QList<CoverProvider*> List();
~CoverProviders() {}
private slots:
void RemoveCoverProvider();
private:
CoverProviders();
CoverProviders(CoverProviders const&);
void operator=(CoverProviders const&);
QList<CoverProvider*> cover_providers_;
QMutex mutex_;
};
#endif // COVERPROVIDERS_H

View File

@ -1,11 +1,10 @@
#include "kittenloader.h"
#include "core/network.h"
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QXmlStreamReader>
#include "core/network.h"
const char* KittenLoader::kFlickrKittenUrl =
"http://api.flickr.com/services/rest/"
"?method=flickr.photos.search"

View File

@ -0,0 +1,66 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "albumcoverfetcher.h"
#include "coverprovider.h"
#include "lastfmcoverprovider.h"
#include <lastfm/Artist>
#include <lastfm/XmlQuery>
#include <lastfm/ws.h>
#include <QNetworkReply>
LastFmCoverProvider::LastFmCoverProvider(QObject* parent)
: CoverProvider("last.fm", parent)
{
}
QNetworkReply* LastFmCoverProvider::SendRequest(const QString& query) {
QMap<QString, QString> params;
params["method"] = "album.search";
params["album"] = query;
return lastfm::ws::post(params);
}
CoverSearchResults LastFmCoverProvider::ParseReply(QNetworkReply* reply) {
CoverSearchResults results;
try {
lastfm::XmlQuery query(lastfm::ws::parse(reply));
#ifdef Q_OS_WIN32
if (lastfm::ws::last_parse_error != lastfm::ws::NoError)
return results;
#endif
// parse the list of search results
QList<lastfm::XmlQuery> elements = query["results"]["albummatches"].children("album");
foreach (const lastfm::XmlQuery& element, elements) {
CoverSearchResult result;
result.description = element["artist"].text() + " - " + element["name"].text();
result.image_url = element["image size=extralarge"].text();
results << result;
}
return results;
} catch(std::runtime_error&) {
return results;
}
}

View File

@ -0,0 +1,38 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFMCOVERPROVIDER_H
#define LASTFMCOVERPROVIDER_H
#include "albumcoverfetcher.h"
#include "coverprovider.h"
#include <QObject>
class QNetworkReply;
// A built-in cover provider which fetches covers from last.fm.
class LastFmCoverProvider : public CoverProvider {
public:
LastFmCoverProvider(QObject* parent);
virtual ~LastFmCoverProvider() {}
QNetworkReply* SendRequest(const QString& query);
CoverSearchResults ParseReply(QNetworkReply* reply);
};
#endif // LASTFMCOVERPROVIDER_H

View File

@ -21,9 +21,9 @@
#include "librarydirectorymodel.h"
#include "libraryview.h"
#include "sqlrow.h"
#include "core/albumcoverloader.h"
#include "core/database.h"
#include "core/taskmanager.h"
#include "covers/albumcoverloader.h"
#include "playlist/songmimedata.h"
#include "smartplaylists/generator.h"
#include "smartplaylists/generatormimedata.h"

View File

@ -24,7 +24,6 @@
#endif // Q_OS_WIN32
#include "config.h"
#include "core/artloader.h"
#include "core/commandlineoptions.h"
#include "core/crashreporting.h"
#include "core/database.h"
@ -37,6 +36,8 @@
#include "core/song.h"
#include "core/taskmanager.h"
#include "core/utilities.h"
#include "covers/artloader.h"
#include "covers/coverproviders.h"
#include "engines/enginebase.h"
#include "library/directory.h"
#include "playlist/playlist.h"
@ -307,6 +308,10 @@ int main(int argc, char *argv[]) {
PlaylistManager playlists(&task_manager, NULL);
RadioModel radio_model(database.get(), &task_manager, NULL);
// Initialize the repository of cover providers to avoid race conditions
// later
CoverProviders::instance();
// Get the last.fm service if it's available
LastFMService* lastfm_service = NULL;
#ifdef HAVE_LIBLASTFM

View File

@ -0,0 +1,27 @@
struct CoverSearchResult {
%TypeHeaderCode
#include "covers/albumcoverfetcher.h"
%End
%Docstring
Represents a single result of some album's cover search request.
It contains an URL that leads to a found cover plus it's description (usually
the "artist - album" string).
%End
QString description;
%Docstring
Description of this result.
We suggest using the "artist - album" format.
%End
QString image_url;
%Docstring
An URL of a cover image described by this CoverSearchResult
%End
};
typedef QList<CoverSearchResult> CoverSearchResults;

View File

@ -5,6 +5,9 @@
%Import QtNetwork/QtNetworkmod.sip
%Include autoexpandingtreeview.sip
%Include albumcoverfetcher.sip
%Include coverprovider.sip
%Include coverproviders.sip
%Include directory.sip
%Include engine_fwd.sip
%Include iconloader.sip

View File

@ -0,0 +1,58 @@
class CoverProvider : QObject {
%TypeHeaderCode
#include "covers/albumcoverfetcher.h"
#include "covers/coverprovider.h"
%End
%Docstring
Each implementation of this interface downloads covers from one online
service.
There are no limitations on what this service might be - last.fm, Amazon,
Google Images - you name it.
CoverProvider should be stateless since it will be used in multi-threaded
environment. It can and probably should use some contextual information
though because a single search spans through two interface methods invoked
independently. The key to connecting both invocations is the NetworkReply
object.
A flow of single request for covers:
- Provider is asked to prepare and invoke a NetworkRequest (L{SendRequest()})
for a given cover query. Provider then returns a corresponding NetworkReply.
It should not connect to finished() signal of the reply, though!
- Later, in a separate invocation (L{ParseReply()}), provider will be asked to
parse the NetworkReply it once prepared. The result is a QList of L{CoverSearchResult}
objects.
Every CoverProvider has a name which should describe (in a word or two) the service
it's using.
%End
public:
CoverProvider(const QString& name);
virtual ~CoverProvider();
QString name();
%Docstring
Name of this provider, like "last.fm".
%End
virtual QNetworkReply* SendRequest(const QString& query) = 0;
%Docstring
Given a search request from Clementine, provider has to create and invoke
a NetworkRequest.
It then has to return a corresponding NetworkReply, without connecting to
it's finished() signal!
%End
virtual CoverSearchResults ParseReply(QNetworkReply* reply) = 0;
%Docstring
Provider parses a reply which is now filled with data obtained from a service
this provider communicates with. The result is a QList of L{CoverSearchResult}
objects.
%End
};

View File

@ -0,0 +1,27 @@
class CoverProviders /NoDefaultCtors/ {
%TypeHeaderCode
#include "covers/coverprovider.h"
#include "covers/coverproviders.h"
#include "scripting/python/pythonengine.h"
%End
%Docstring
This is a global repository for cover providers.
Each one of those has to register with CoverProviders instance by invoking
"CoverProviders::instance().AddCoverProvider(this)". Providers are automatically
unregistered from the repository when they are deleted.
%End
public:
void AddCoverProvider(CoverProvider* provider /Transfer/);
%MethodCode
sipCpp->AddCoverProvider(a0);
PythonEngine::instance()->RegisterNativeObject(a0);
%End
%Docstring
Let's a cover provider to register itself in the repository.
%End
};

View File

@ -22,6 +22,7 @@
#include "pythonengine.h"
#include "pythonscript.h"
#include "sipAPIclementine.h"
#include "covers/coverproviders.h"
#include "library/library.h"
#include <QFile>
@ -138,6 +139,7 @@ bool PythonEngine::EnsureInitialised() {
AddObject(manager()->data().radio_model_, sipType_RadioModel, "radio_model");
AddObject(manager()->data().settings_dialog_, sipType_SettingsDialog, "settings_dialog");
AddObject(manager()->data().task_manager_, sipType_TaskManager, "task_manager");
AddObject(&CoverProviders::instance(), sipType_CoverProviders, "cover_providers");
}
AddObject(manager()->ui(), sipType_UIInterface, "ui");

View File

@ -32,6 +32,8 @@ appropriately.
const char *name;
sipTypeDef **type;
} list[] = {
CLASS(CoverProvider),
CLASS(CoverProviders),
CLASS(LibraryBackend),
CLASS(MergedProxyModel),
CLASS(NetworkAccessManager),

View File

@ -16,7 +16,8 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "core/albumcoverloader.h"
#include "covers/albumcoverfetcher.h"
#include "covers/albumcoverloader.h"
#include "library/librarybackend.h"
#include "ui/albumcoverchoicecontroller.h"
#include "ui/albumcovermanager.h"
@ -25,7 +26,6 @@
#ifdef HAVE_LIBLASTFM
# include "ui/albumcoversearcher.h"
# include "core/albumcoverfetcher.h"
#endif
#include <QAction>
@ -52,8 +52,8 @@ AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget* parent)
: QWidget(parent),
#ifdef HAVE_LIBLASTFM
cover_searcher_(new AlbumCoverSearcher(QIcon(":/nocover.png"), this)),
cover_fetcher_(new AlbumCoverFetcher(this)),
#endif
cover_fetcher_(new AlbumCoverFetcher(this)),
save_file_dialog_(NULL),
cover_from_url_dialog_(NULL),
library_(NULL)

View File

@ -19,7 +19,7 @@
#include "albumcoversearcher.h"
#include "iconloader.h"
#include "ui_albumcovermanager.h"
#include "core/albumcoverfetcher.h"
#include "covers/albumcoverfetcher.h"
#include "library/librarybackend.h"
#include "library/libraryquery.h"
#include "library/sqlrow.h"

View File

@ -24,9 +24,9 @@
#include "gtest/gtest_prod.h"
#include "core/albumcoverloader.h"
#include "core/backgroundthread.h"
#include "core/song.h"
#include "covers/albumcoverloader.h"
class AlbumCoverChoiceController;
class AlbumCoverFetcher;

View File

@ -17,8 +17,8 @@
#include "albumcoversearcher.h"
#include "ui_albumcoversearcher.h"
#include "core/albumcoverfetcher.h"
#include "core/albumcoverloader.h"
#include "covers/albumcoverfetcher.h"
#include "covers/albumcoverloader.h"
#include <QKeyEvent>
#include <QListWidgetItem>
@ -52,7 +52,7 @@ AlbumCoverSearcher::~AlbumCoverSearcher() {
void AlbumCoverSearcher::Init(AlbumCoverFetcher* fetcher) {
fetcher_ = fetcher;
connect(fetcher_, SIGNAL(SearchFinished(quint64,AlbumCoverFetcher::SearchResults)), SLOT(SearchFinished(quint64,AlbumCoverFetcher::SearchResults)));
connect(fetcher_, SIGNAL(SearchFinished(quint64,CoverSearchResults)), SLOT(SearchFinished(quint64,CoverSearchResults)));
}
QImage AlbumCoverSearcher::Exec(const QString &query) {
@ -84,7 +84,7 @@ void AlbumCoverSearcher::Search() {
id_ = fetcher_->SearchForCovers(ui_->query->text());
}
void AlbumCoverSearcher::SearchFinished(quint64 id, const AlbumCoverFetcher::SearchResults &results) {
void AlbumCoverSearcher::SearchFinished(quint64 id, const CoverSearchResults& results) {
if (id != id_)
return;
@ -95,7 +95,7 @@ void AlbumCoverSearcher::SearchFinished(quint64 id, const AlbumCoverFetcher::Sea
ui_->covers->clear();
cover_loading_tasks_.clear();
foreach (const AlbumCoverFetcher::SearchResult& result, results) {
foreach (const CoverSearchResult& result, results) {
if (result.image_url.isEmpty())
continue;
@ -103,7 +103,7 @@ void AlbumCoverSearcher::SearchFinished(quint64 id, const AlbumCoverFetcher::Sea
QListWidgetItem* item = new QListWidgetItem(ui_->covers);
item->setIcon(no_cover_icon_);
item->setText(result.artist + " - " + result.album);
item->setText(result.description);
item->setData(Role_ImageURL, result.image_url);
item->setData(Role_ImageRequestId, id);
item->setData(Qt::TextAlignmentRole, QVariant(Qt::AlignTop | Qt::AlignHCenter));

View File

@ -18,8 +18,8 @@
#ifndef ALBUMCOVERSEARCHER_H
#define ALBUMCOVERSEARCHER_H
#include "core/albumcoverfetcher.h"
#include "core/backgroundthread.h"
#include "covers/albumcoverfetcher.h"
#include <QDialog>
#include <QIcon>
@ -54,7 +54,7 @@ protected:
private slots:
void Search();
void SearchFinished(quint64 id, const AlbumCoverFetcher::SearchResults& results);
void SearchFinished(quint64 id, const CoverSearchResults& results);
void ImageLoaded(quint64 id, const QImage& image);
void CoverDoubleClicked(const QModelIndex& index);

View File

@ -17,8 +17,8 @@
#include "coverfromurldialog.h"
#include "ui_coverfromurldialog.h"
#include "core/albumcoverloader.h"
#include "core/network.h"
#include "covers/albumcoverloader.h"
#include <QImage>
#include <QMessageBox>

View File

@ -19,8 +19,8 @@
#include "edittagdialog.h"
#include "trackselectiondialog.h"
#include "ui_edittagdialog.h"
#include "core/albumcoverloader.h"
#include "core/utilities.h"
#include "covers/albumcoverloader.h"
#include "library/library.h"
#include "library/librarybackend.h"
#include "musicbrainz/fingerprinter.h"

View File

@ -17,7 +17,6 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "core/artloader.h"
#include "core/backgroundstreams.h"
#include "core/commandlineoptions.h"
#include "core/database.h"
@ -34,6 +33,7 @@
#include "core/stylesheetloader.h"
#include "core/taskmanager.h"
#include "core/utilities.h"
#include "covers/artloader.h"
#include "devices/devicemanager.h"
#include "devices/devicestatefiltermodel.h"
#include "devices/deviceview.h"

View File

@ -17,8 +17,8 @@
#include "fullscreenhypnotoad.h"
#include "nowplayingwidget.h"
#include "core/albumcoverloader.h"
#include "core/kittenloader.h"
#include "covers/albumcoverloader.h"
#include "covers/kittenloader.h"
#include "library/librarybackend.h"
#include "ui/albumcoverchoicecontroller.h"
#include "ui/iconloader.h"

View File

@ -24,9 +24,9 @@
#include "config.h"
#include "engines/engine_fwd.h"
#include "core/albumcoverloader.h"
#include "core/backgroundthread.h"
#include "core/song.h"
#include "covers/albumcoverloader.h"
#include "playlist/playlistsequence.h"
class OrgFreedesktopNotificationsInterface;