Load the list of sky.fm/di.fm streams using the undocumented API, getting artwork for each stream as well.

This commit is contained in:
David Sansome 2011-11-04 22:31:19 +00:00
parent 37eeb70e3a
commit 2b6beb7417
15 changed files with 226 additions and 172 deletions

View File

@ -370,7 +370,6 @@ set(HEADERS
engines/gstenginepipeline.h engines/gstenginepipeline.h
engines/gstelementdeleter.h engines/gstelementdeleter.h
globalsearch/librarysearchprovider.h
globalsearch/globalsearch.h globalsearch/globalsearch.h
globalsearch/globalsearchpopup.h globalsearch/globalsearchpopup.h
globalsearch/globalsearchsettingspage.h globalsearch/globalsearchsettingspage.h

View File

@ -24,8 +24,8 @@ DigitallyImportedSearchProvider::DigitallyImportedSearchProvider(
: SimpleSearchProvider(parent), : SimpleSearchProvider(parent),
service_(service) service_(service)
{ {
Init(service_->name(), service->url_scheme(), service_->icon()); Init(service_->name(), service->url_scheme(), service_->icon(),
icon_ = ScaleAndPad(QImage(service_->icon_path())); ArtIsInSongMetadata);
set_safe_words(QStringList() << "sky.fm" << "skyfm" << "di.fm" << "difm" set_safe_words(QStringList() << "sky.fm" << "skyfm" << "di.fm" << "difm"
<< "digitallyimported"); << "digitallyimported");
@ -33,21 +33,14 @@ DigitallyImportedSearchProvider::DigitallyImportedSearchProvider(
connect(service_, SIGNAL(StreamsChanged()), SLOT(MaybeRecreateItems())); connect(service_, SIGNAL(StreamsChanged()), SLOT(MaybeRecreateItems()));
} }
void DigitallyImportedSearchProvider::LoadArtAsync(int id, const Result& result) {
emit ArtLoaded(id, icon_);
}
void DigitallyImportedSearchProvider::RecreateItems() { void DigitallyImportedSearchProvider::RecreateItems() {
QList<Item> items; QList<Item> items;
DigitallyImportedServiceBase::StreamList streams = service_->Streams(); DigitallyImportedClient::ChannelList channels = service_->Channels();
foreach (const DigitallyImportedServiceBase::Stream& stream, streams) { foreach (const DigitallyImportedClient::Channel& channel, channels) {
Song song; Song song;
song.set_title(stream.name_); service_->SongFromChannel(channel, &song);
song.set_artist(service_->service_description());
song.set_url(QUrl(service_->url_scheme() + "://" + stream.key_));
items << Item(song); items << Item(song);
} }

View File

@ -27,14 +27,11 @@ public:
DigitallyImportedSearchProvider(DigitallyImportedServiceBase* service, DigitallyImportedSearchProvider(DigitallyImportedServiceBase* service,
QObject* parent); QObject* parent);
void LoadArtAsync(int id, const Result& result);
protected: protected:
void RecreateItems(); void RecreateItems();
private: private:
DigitallyImportedServiceBase* service_; DigitallyImportedServiceBase* service_;
QImage icon_;
}; };
#endif // DIGITALLYIMPORTEDSEARCHPROVIDER_H #endif // DIGITALLYIMPORTEDSEARCHPROVIDER_H

View File

@ -18,6 +18,7 @@
#include "librarysearchprovider.h" #include "librarysearchprovider.h"
#include "globalsearch.h" #include "globalsearch.h"
#include "core/logging.h" #include "core/logging.h"
#include "covers/albumcoverloader.h"
#include <QSettings> #include <QSettings>
#include <QStringBuilder> #include <QStringBuilder>
@ -29,8 +30,17 @@ const char* GlobalSearch::kSettingsGroup = "GlobalSearch";
GlobalSearch::GlobalSearch(QObject* parent) GlobalSearch::GlobalSearch(QObject* parent)
: QObject(parent), : QObject(parent),
next_id_(1) next_id_(1),
cover_loader_(new BackgroundThreadImplementation<AlbumCoverLoader, AlbumCoverLoader>(this))
{ {
cover_loader_->Start(true);
cover_loader_->Worker()->SetDesiredHeight(SearchProvider::kArtHeight);
cover_loader_->Worker()->SetPadOutputImage(true);
cover_loader_->Worker()->SetScaleOutputImage(true);
connect(cover_loader_->Worker().get(),
SIGNAL(ImageLoaded(quint64,QImage)),
SLOT(AlbumArtLoaded(quint64,QImage)));
} }
void GlobalSearch::AddProvider(SearchProvider* provider, bool enable_by_default) { void GlobalSearch::AddProvider(SearchProvider* provider, bool enable_by_default) {
@ -169,7 +179,10 @@ int GlobalSearch::LoadArtAsync(const SearchProvider::Result& result) {
return id; return id;
} }
if (result.provider_->wants_serialised_art()) { if (result.provider_->art_is_in_song_metadata()) {
quint64 loader_id = cover_loader_->Worker()->LoadImageAsync(result.metadata_);
cover_loader_tasks_[loader_id] = id;
} else if (result.provider_->wants_serialised_art()) {
QueuedArt request; QueuedArt request;
request.id_ = id; request.id_ = id;
request.result_ = result; request.result_ = result;
@ -199,6 +212,18 @@ void GlobalSearch::TakeNextQueuedArt(SearchProvider* provider) {
void GlobalSearch::ArtLoadedSlot(int id, const QImage& image) { void GlobalSearch::ArtLoadedSlot(int id, const QImage& image) {
SearchProvider* provider = static_cast<SearchProvider*>(sender()); SearchProvider* provider = static_cast<SearchProvider*>(sender());
HandleLoadedArt(id, image, provider);
}
void GlobalSearch::AlbumArtLoaded(quint64 id, const QImage& image) {
if (!cover_loader_tasks_.contains(id))
return;
int orig_id = cover_loader_tasks_.take(id);
HandleLoadedArt(orig_id, image, NULL);
}
void GlobalSearch::HandleLoadedArt(int id, const QImage& image, SearchProvider* provider) {
const QString key = pending_art_searches_.take(id); const QString key = pending_art_searches_.take(id);
QPixmap pixmap = QPixmap::fromImage(image); QPixmap pixmap = QPixmap::fromImage(image);
@ -206,7 +231,8 @@ void GlobalSearch::ArtLoadedSlot(int id, const QImage& image) {
emit ArtLoaded(id, pixmap); emit ArtLoaded(id, pixmap);
if (providers_.contains(provider) && if (provider &&
providers_.contains(provider) &&
!providers_[provider].queued_art_.isEmpty()) { !providers_[provider].queued_art_.isEmpty()) {
providers_[provider].queued_art_.removeFirst(); providers_[provider].queued_art_.removeFirst();
TakeNextQueuedArt(provider); TakeNextQueuedArt(provider);

View File

@ -22,8 +22,11 @@
#include <QPixmapCache> #include <QPixmapCache>
#include "searchprovider.h" #include "searchprovider.h"
#include "core/backgroundthread.h"
class AlbumCoverLoader;
class GlobalSearch : public QObject { class GlobalSearch : public QObject {
Q_OBJECT Q_OBJECT
@ -74,10 +77,12 @@ private slots:
void SearchFinishedSlot(int id); void SearchFinishedSlot(int id);
void ArtLoadedSlot(int id, const QImage& image); void ArtLoadedSlot(int id, const QImage& image);
void AlbumArtLoaded(quint64 id, const QImage& image);
void ProviderDestroyedSlot(QObject* object); void ProviderDestroyedSlot(QObject* object);
private: private:
void HandleLoadedArt(int id, const QImage& image, SearchProvider* provider);
void TakeNextQueuedArt(SearchProvider* provider); void TakeNextQueuedArt(SearchProvider* provider);
QString PixmapCacheKey(const SearchProvider::Result& result) const; QString PixmapCacheKey(const SearchProvider::Result& result) const;
@ -111,6 +116,10 @@ private:
QMap<int, QString> pending_art_searches_; QMap<int, QString> pending_art_searches_;
QMap<QString, bool> providers_state_preference_; QMap<QString, bool> providers_state_preference_;
// Used for providers with ArtIsInSongMetadata set.
BackgroundThread<AlbumCoverLoader>* cover_loader_;
QMap<quint64, int> cover_loader_tasks_;
}; };
#endif // GLOBALSEARCH_H #endif // GLOBALSEARCH_H

View File

@ -30,19 +30,9 @@ LibrarySearchProvider::LibrarySearchProvider(LibraryBackendInterface* backend,
const QIcon& icon, const QIcon& icon,
QObject* parent) QObject* parent)
: BlockingSearchProvider(parent), : BlockingSearchProvider(parent),
backend_(backend), backend_(backend)
cover_loader_(new BackgroundThreadImplementation<AlbumCoverLoader, AlbumCoverLoader>(this))
{ {
Init(name, id, icon, WantsSerialisedArtQueries); Init(name, id, icon, WantsSerialisedArtQueries | ArtIsInSongMetadata);
cover_loader_->Start(true);
cover_loader_->Worker()->SetDesiredHeight(kArtHeight);
cover_loader_->Worker()->SetPadOutputImage(true);
cover_loader_->Worker()->SetScaleOutputImage(true);
connect(cover_loader_->Worker().get(),
SIGNAL(ImageLoaded(quint64,QImage)),
SLOT(AlbumArtLoaded(quint64,QImage)));
} }
SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString& query) { SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString& query) {
@ -114,19 +104,6 @@ SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString&
return ret; return ret;
} }
void LibrarySearchProvider::LoadArtAsync(int id, const Result& result) {
quint64 loader_id = cover_loader_->Worker()->LoadImageAsync(result.metadata_);
cover_loader_tasks_[loader_id] = id;
}
void LibrarySearchProvider::AlbumArtLoaded(quint64 id, const QImage& image) {
if (!cover_loader_tasks_.contains(id))
return;
int orig_id = cover_loader_tasks_.take(id);
emit ArtLoaded(orig_id, image);
}
void LibrarySearchProvider::LoadTracksAsync(int id, const Result& result) { void LibrarySearchProvider::LoadTracksAsync(int id, const Result& result) {
SongList ret; SongList ret;

View File

@ -26,24 +26,15 @@ class LibraryBackendInterface;
class LibrarySearchProvider : public BlockingSearchProvider { class LibrarySearchProvider : public BlockingSearchProvider {
Q_OBJECT
public: public:
LibrarySearchProvider(LibraryBackendInterface* backend, const QString& name, LibrarySearchProvider(LibraryBackendInterface* backend, const QString& name,
const QString& id, const QIcon& icon, QObject* parent = 0); const QString& id, const QIcon& icon, QObject* parent = 0);
ResultList Search(int id, const QString& query); ResultList Search(int id, const QString& query);
void LoadArtAsync(int id, const Result& result);
void LoadTracksAsync(int id, const Result& result); void LoadTracksAsync(int id, const Result& result);
private slots:
void AlbumArtLoaded(quint64 id, const QImage& image);
private: private:
LibraryBackendInterface* backend_; LibraryBackendInterface* backend_;
BackgroundThread<AlbumCoverLoader>* cover_loader_;
QMap<quint64, int> cover_loader_tasks_;
}; };
#endif // LIBRARYSEARCHPROVIDER_H #endif // LIBRARYSEARCHPROVIDER_H

View File

@ -137,3 +137,7 @@ namespace {
void SearchProvider::SortSongs(SongList* list) { void SearchProvider::SortSongs(SongList* list) {
qStableSort(list->begin(), list->end(), SortSongsCompare); qStableSort(list->begin(), list->end(), SortSongsCompare);
} }
void SearchProvider::LoadArtAsync(int id, const Result& result) {
emit ArtLoaded(id, QImage());
}

View File

@ -75,7 +75,13 @@ public:
// If a third-party application is making art requests over dbus and has // If a third-party application is making art requests over dbus and has
// to get all the art it can before showing results to the user, it might // to get all the art it can before showing results to the user, it might
// not load art from this provider. // not load art from this provider.
ArtIsProbablyRemote = 0x04 ArtIsProbablyRemote = 0x04,
// Indicates the art URL (or filename) for each result is stored in the
// normal place in the song metadata. LoadArtAsync will never be called and
// WantsSerialisedArtQueries and ArtIsProbablyRemote will be ignored if
// they are set as well. The GlobalSearch engine will load the art itself.
ArtIsInSongMetadata = 0x08
}; };
Q_DECLARE_FLAGS(Hints, Hint) Q_DECLARE_FLAGS(Hints, Hint)
@ -87,6 +93,7 @@ public:
bool wants_delayed_queries() const { return hints() & WantsDelayedQueries; } bool wants_delayed_queries() const { return hints() & WantsDelayedQueries; }
bool wants_serialised_art() const { return hints() & WantsSerialisedArtQueries; } bool wants_serialised_art() const { return hints() & WantsSerialisedArtQueries; }
bool art_is_probably_remote() const { return hints() & ArtIsProbablyRemote; } bool art_is_probably_remote() const { return hints() & ArtIsProbablyRemote; }
bool art_is_in_song_metadata() const { return hints() & ArtIsInSongMetadata; }
// Starts a search. Must emit ResultsAvailable zero or more times and then // Starts a search. Must emit ResultsAvailable zero or more times and then
// SearchFinished exactly once, using this ID. // SearchFinished exactly once, using this ID.
@ -94,7 +101,7 @@ public:
// Starts loading an icon for a result that was previously emitted by // Starts loading an icon for a result that was previously emitted by
// ResultsAvailable. Must emit ArtLoaded exactly once with this ID. // ResultsAvailable. Must emit ArtLoaded exactly once with this ID.
virtual void LoadArtAsync(int id, const Result& result) = 0; virtual void LoadArtAsync(int id, const Result& result);
// Starts loading tracks for a result that was previously emitted by // Starts loading tracks for a result that was previously emitted by
// ResultsAvailable. Must emit TracksLoaded exactly once with this ID. // ResultsAvailable. Must emit TracksLoaded exactly once with this ID.

View File

@ -33,6 +33,9 @@ const char* DigitallyImportedClient::kApiPassword = "dayeiph0ne@pp";
const char* DigitallyImportedClient::kAuthUrl = const char* DigitallyImportedClient::kAuthUrl =
"http://api.audioaddict.com/%1/premium/auth"; "http://api.audioaddict.com/%1/premium/auth";
const char* DigitallyImportedClient::kChannelListUrl =
"http://api.v2.audioaddict.com/v1/%1/mobile/batch_update?asset_group_key=mobile_icons&stream_set_key=";
DigitallyImportedClient::DigitallyImportedClient(const QString& service_name, QObject* parent) DigitallyImportedClient::DigitallyImportedClient(const QString& service_name, QObject* parent)
: QObject(parent), : QObject(parent),
@ -41,12 +44,16 @@ DigitallyImportedClient::DigitallyImportedClient(const QString& service_name, QO
{ {
} }
void DigitallyImportedClient::SetAuthorisationHeader(QNetworkRequest* req) const {
req->setRawHeader("Authorization",
"Basic " + QString("%1:%2").arg(kApiUsername, kApiPassword)
.toAscii().toBase64());
}
QNetworkReply* DigitallyImportedClient::Auth(const QString& username, QNetworkReply* DigitallyImportedClient::Auth(const QString& username,
const QString& password) { const QString& password) {
QNetworkRequest req(QUrl(QString(kAuthUrl).arg(service_name_))); QNetworkRequest req(QUrl(QString(kAuthUrl).arg(service_name_)));
req.setRawHeader("Authorization", SetAuthorisationHeader(&req);
"Basic " + QString("%1:%2").arg(kApiUsername, kApiPassword)
.toAscii().toBase64());
QByteArray postdata = "username=" + QUrl::toPercentEncoding(username) + QByteArray postdata = "username=" + QUrl::toPercentEncoding(username) +
"&password=" + QUrl::toPercentEncoding(password); "&password=" + QUrl::toPercentEncoding(password);
@ -83,3 +90,65 @@ DigitallyImportedClient::ParseAuthReply(QNetworkReply* reply) const {
ret.listen_hash_ = user["listen_hash"].toString(); ret.listen_hash_ = user["listen_hash"].toString();
return ret; return ret;
} }
QNetworkReply* DigitallyImportedClient::GetChannelList() {
//QNetworkRequest req(QUrl(QString(kChannelListUrl)));
QNetworkRequest req(QUrl(QString(kChannelListUrl).arg(service_name_)));
SetAuthorisationHeader(&req);
return network_->get(req);
}
DigitallyImportedClient::ChannelList
DigitallyImportedClient::ParseChannelList(QNetworkReply* reply) const {
ChannelList ret;
QJson::Parser parser;
QVariantMap data = parser.parse(reply).toMap();
if (!data.contains("channel_filters"))
return ret;
QVariantList filters = data["channel_filters"].toList();
foreach (const QVariant& filter, filters) {
// Find the filter called "All"
QVariantMap filter_map = filter.toMap();
if (filter_map.value("name", QString()).toString() != "All")
continue;
// Add all its stations to the result
QVariantList channels = filter_map.value("channels", QVariantList()).toList();
foreach (const QVariant& channel_var, channels) {
QVariantMap channel_map = channel_var.toMap();
Channel channel;
channel.art_url_ = QUrl(channel_map.value("asset_url").toString());
channel.description_ = channel_map.value("description").toString();
channel.director_ = channel_map.value("channel_director").toString();
channel.key_ = channel_map.value("key").toString();
channel.name_ = channel_map.value("name").toString();
ret << channel;
}
break;
}
return ret;
}
void DigitallyImportedClient::Channel::Load(const QSettings& s) {
art_url_ = s.value("art_url").toUrl();
director_ = s.value("director").toString();
description_ = s.value("description").toString();
name_ = s.value("name").toString();
key_ = s.value("key").toString();
}
void DigitallyImportedClient::Channel::Save(QSettings* s) const {
s->setValue("art_url", art_url_);
s->setValue("director", director_);
s->setValue("description", description_);
s->setValue("name", name_);
s->setValue("key", key_);
}

View File

@ -20,9 +20,12 @@
#include <QDateTime> #include <QDateTime>
#include <QObject> #include <QObject>
#include <QSettings>
#include <QUrl>
class QNetworkAccessManager; class QNetworkAccessManager;
class QNetworkReply; class QNetworkReply;
class QNetworkRequest;
class DigitallyImportedClient : public QObject { class DigitallyImportedClient : public QObject {
Q_OBJECT Q_OBJECT
@ -33,6 +36,7 @@ public:
static const char* kApiUsername; static const char* kApiUsername;
static const char* kApiPassword; static const char* kApiPassword;
static const char* kAuthUrl; static const char* kAuthUrl;
static const char* kChannelListUrl;
struct AuthReply { struct AuthReply {
bool success_; bool success_;
@ -47,9 +51,30 @@ public:
QString listen_hash_; QString listen_hash_;
}; };
struct Channel {
QUrl art_url_;
QString director_;
QString description_;
QString name_;
QString key_;
void Save(QSettings* s) const;
void Load(const QSettings& s);
bool operator <(const Channel& other) const { return name_ < other.name_; }
};
typedef QList<Channel> ChannelList;
QNetworkReply* Auth(const QString& username, const QString& password); QNetworkReply* Auth(const QString& username, const QString& password);
AuthReply ParseAuthReply(QNetworkReply* reply) const; AuthReply ParseAuthReply(QNetworkReply* reply) const;
QNetworkReply* GetChannelList();
ChannelList ParseChannelList(QNetworkReply* reply) const;
private:
void SetAuthorisationHeader(QNetworkRequest* req) const;
private: private:
QNetworkAccessManager* network_; QNetworkAccessManager* network_;

View File

@ -19,6 +19,7 @@
#include "digitallyimportedservicebase.h" #include "digitallyimportedservicebase.h"
#include "digitallyimportedurlhandler.h" #include "digitallyimportedurlhandler.h"
#include "internetmodel.h" #include "internetmodel.h"
#include "core/closure.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/network.h" #include "core/network.h"
#include "core/player.h" #include "core/player.h"
@ -44,7 +45,6 @@ DigitallyImportedServiceBase::DigitallyImportedServiceBase(
url_handler_(new DigitallyImportedUrlHandler(this)), url_handler_(new DigitallyImportedUrlHandler(this)),
basic_audio_type_(1), basic_audio_type_(1),
premium_audio_type_(2), premium_audio_type_(2),
task_id_(-1),
root_(NULL), root_(NULL),
context_item_(NULL), context_item_(NULL),
api_client_(NULL) api_client_(NULL)
@ -98,7 +98,7 @@ void DigitallyImportedServiceBase::LazyPopulate(QStandardItem* parent) {
} }
void DigitallyImportedServiceBase::RefreshStreams() { void DigitallyImportedServiceBase::RefreshStreams() {
if (IsStreamListStale()) { if (IsChannelListStale()) {
ForceRefreshStreams(); ForceRefreshStreams();
return; return;
} }
@ -107,61 +107,25 @@ void DigitallyImportedServiceBase::RefreshStreams() {
} }
void DigitallyImportedServiceBase::ForceRefreshStreams() { void DigitallyImportedServiceBase::ForceRefreshStreams() {
if (task_id_ != -1) {
return;
}
qLog(Info) << "Getting stream list from" << stream_list_url_;
// Get the list of streams
QNetworkReply* reply = network_->get(QNetworkRequest(stream_list_url_));
connect(reply, SIGNAL(finished()), SLOT(RefreshStreamsFinished()));
// Start a task to tell the user we're busy // Start a task to tell the user we're busy
task_id_ = model()->task_manager()->StartTask(tr("Getting streams")); int task_id = model()->task_manager()->StartTask(tr("Getting streams"));
QNetworkReply* reply = api_client_->GetChannelList();
NewClosure(reply, SIGNAL(finished()),
this, SLOT(RefreshStreamsFinished(QNetworkReply*,int)),
reply, task_id);
} }
void DigitallyImportedServiceBase::RefreshStreamsFinished() { void DigitallyImportedServiceBase::RefreshStreamsFinished(QNetworkReply* reply, int task_id) {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender()); model()->task_manager()->SetTaskFinished(task_id);
if (!reply) {
return;
}
model()->task_manager()->SetTaskFinished(task_id_);
reply->deleteLater(); reply->deleteLater();
const QString data = QString::fromUtf8(reply->readAll()); saved_channels_ = api_client_->ParseChannelList(reply);
// Poor man's JSON parser that's good enough for the stream lists and means
// we don't have to pull in QJSON as a dependency.
const QRegExp re("\\{"
"\"id\":(\\d+),"
"\"key\":\"([^\"]+)\","
"\"name\":\"([^\"]+)\","
"\"description\":\"([^\"]+)\"");
saved_streams_.clear();
int pos = 0;
while (pos >= 0) {
pos = re.indexIn(data, pos);
if (pos == -1) {
break;
}
pos += re.matchedLength();
Stream stream;
stream.id_ = re.cap(1).toInt();
stream.key_ = re.cap(2).replace("\\/", "/");
stream.name_ = re.cap(3).replace("\\/", "/");
stream.description_ = re.cap(4).replace("\\/", "/");
saved_streams_ << stream;
}
// Sort by name // Sort by name
qSort(saved_streams_); qSort(saved_channels_);
SaveStreams(saved_streams_); SaveChannels(saved_channels_);
PopulateStreams(); PopulateStreams();
emit StreamsChanged(); emit StreamsChanged();
@ -172,21 +136,27 @@ void DigitallyImportedServiceBase::PopulateStreams() {
root_->removeRows(0, root_->rowCount()); root_->removeRows(0, root_->rowCount());
// Add each stream to the model // Add each stream to the model
foreach (const Stream& stream, saved_streams_) { foreach (const DigitallyImportedClient::Channel& channel, saved_channels_) {
Song song; Song song;
song.set_title(stream.name_); SongFromChannel(channel, &song);
song.set_artist(service_description_);
song.set_url(QUrl(url_scheme_ + "://" + stream.key_));
QStandardItem* item = new QStandardItem(QIcon(":/last.fm/icon_radio.png"), QStandardItem* item = new QStandardItem(QIcon(":/last.fm/icon_radio.png"),
stream.name_); song.title());
item->setData(stream.description_, Qt::ToolTipRole); item->setData(channel.description_, Qt::ToolTipRole);
item->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour); item->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour);
item->setData(QVariant::fromValue(song), InternetModel::Role_SongMetadata); item->setData(QVariant::fromValue(song), InternetModel::Role_SongMetadata);
root_->appendRow(item); root_->appendRow(item);
} }
} }
void DigitallyImportedServiceBase::SongFromChannel(
const DigitallyImportedClient::Channel& channel, Song* song) const {
song->set_title(channel.name_);
song->set_artist(service_description_ + " - " + channel.director_);
song->set_url(QUrl(url_scheme_ + "://" + channel.key_));
song->set_art_automatic(channel.art_url_.toString());
}
void DigitallyImportedServiceBase::Homepage() { void DigitallyImportedServiceBase::Homepage() {
QDesktopServices::openUrl(homepage_url_); QDesktopServices::openUrl(homepage_url_);
} }
@ -199,8 +169,8 @@ void DigitallyImportedServiceBase::ReloadSettings() {
premium_audio_type_ = s.value("premium_audio_type", 2).toInt(); premium_audio_type_ = s.value("premium_audio_type", 2).toInt();
username_ = s.value("username").toString(); username_ = s.value("username").toString();
listen_hash_ = s.value("listen_hash").toString(); listen_hash_ = s.value("listen_hash").toString();
last_refreshed_streams_ = s.value("last_refreshed_" + url_scheme_).toDateTime(); last_refreshed_channels_ = s.value("last_refreshed_v2_" + url_scheme_).toDateTime();
saved_streams_ = LoadStreams(); saved_channels_ = LoadChannels();
} }
void DigitallyImportedServiceBase::ShowContextMenu( void DigitallyImportedServiceBase::ShowContextMenu(
@ -248,8 +218,8 @@ void DigitallyImportedServiceBase::ShowSettingsDialog() {
emit OpenSettingsAtPage(SettingsDialog::Page_DigitallyImported); emit OpenSettingsAtPage(SettingsDialog::Page_DigitallyImported);
} }
DigitallyImportedServiceBase::StreamList DigitallyImportedServiceBase::LoadStreams() const { DigitallyImportedClient::ChannelList DigitallyImportedServiceBase::LoadChannels() const {
StreamList ret; DigitallyImportedClient::ChannelList ret;
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
@ -258,49 +228,44 @@ DigitallyImportedServiceBase::StreamList DigitallyImportedServiceBase::LoadStrea
for (int i=0 ; i<count ; ++i) { for (int i=0 ; i<count ; ++i) {
s.setArrayIndex(i); s.setArrayIndex(i);
Stream stream; DigitallyImportedClient::Channel channel;
stream.id_ = s.value("id").toInt(); channel.Load(s);
stream.key_ = s.value("key").toString(); ret << channel;
stream.name_ = s.value("name").toString();
stream.description_ = s.value("description").toString();
ret << stream;
} }
s.endArray(); s.endArray();
return ret; return ret;
} }
void DigitallyImportedServiceBase::SaveStreams(const StreamList& streams) { void DigitallyImportedServiceBase::SaveChannels(
const DigitallyImportedClient::ChannelList& channels) {
QSettings s; QSettings s;
s.beginGroup(kSettingsGroup); s.beginGroup(kSettingsGroup);
s.beginWriteArray(url_scheme_, streams.count()); s.beginWriteArray(url_scheme_, channels.count());
for (int i=0 ; i<streams.count() ; ++i) { for (int i=0 ; i<channels.count() ; ++i) {
const Stream& stream = streams[i];
s.setArrayIndex(i); s.setArrayIndex(i);
s.setValue("id", stream.id_); channels[i].Save(&s);
s.setValue("key", stream.key_);
s.setValue("name", stream.name_);
s.setValue("description", stream.description_);
} }
s.endArray(); s.endArray();
last_refreshed_streams_ = QDateTime::currentDateTime(); last_refreshed_channels_ = QDateTime::currentDateTime();
s.setValue("last_refreshed_" + url_scheme_, last_refreshed_streams_); s.setValue("last_refreshed_v2_" + url_scheme_, last_refreshed_channels_);
} }
bool DigitallyImportedServiceBase::IsStreamListStale() const { bool DigitallyImportedServiceBase::IsChannelListStale() const {
return last_refreshed_streams_.isNull() || return last_refreshed_channels_.isNull() ||
last_refreshed_streams_.secsTo(QDateTime::currentDateTime()) > last_refreshed_channels_.secsTo(QDateTime::currentDateTime()) >
kStreamsCacheDurationSecs; kStreamsCacheDurationSecs;
} }
DigitallyImportedServiceBase::StreamList DigitallyImportedServiceBase::Streams() { DigitallyImportedClient::ChannelList DigitallyImportedServiceBase::Channels() {
if (IsStreamListStale()) { if (IsChannelListStale()) {
metaObject()->invokeMethod(this, "ForceRefreshStreams", Qt::QueuedConnection); metaObject()->invokeMethod(this, "ForceRefreshStreams", Qt::QueuedConnection);
} }
return saved_streams_; return saved_channels_;
} }
void DigitallyImportedServiceBase::LoadStation(const QString& key) { void DigitallyImportedServiceBase::LoadStation(const QString& key) {

View File

@ -18,6 +18,7 @@
#ifndef DIGITALLYIMPORTEDSERVICEBASE_H #ifndef DIGITALLYIMPORTEDSERVICEBASE_H
#define DIGITALLYIMPORTEDSERVICEBASE_H #define DIGITALLYIMPORTEDSERVICEBASE_H
#include "digitallyimportedclient.h"
#include "internetservice.h" #include "internetservice.h"
#include <boost/scoped_ptr.hpp> #include <boost/scoped_ptr.hpp>
@ -57,19 +58,10 @@ public:
const QString& url_scheme() const { return url_scheme_; } const QString& url_scheme() const { return url_scheme_; }
const QString& api_service_name() const { return api_service_name_; } const QString& api_service_name() const { return api_service_name_; }
// Public for the global search provider. bool IsChannelListStale() const;
struct Stream { DigitallyImportedClient::ChannelList Channels();
int id_; void SongFromChannel(const DigitallyImportedClient::Channel& channel,
QString key_; Song* song) const;
QString name_;
QString description_;
bool operator <(const Stream& other) const { return name_ < other.name_; }
};
typedef QList<Stream> StreamList;
bool IsStreamListStale() const;
StreamList Streams();
signals: signals:
void StreamsChanged(); void StreamsChanged();
@ -90,13 +82,13 @@ private slots:
void Homepage(); void Homepage();
void ForceRefreshStreams(); void ForceRefreshStreams();
void RefreshStreams(); void RefreshStreams();
void RefreshStreamsFinished(); void RefreshStreamsFinished(QNetworkReply* reply, int task_id);
void ShowSettingsDialog(); void ShowSettingsDialog();
private: private:
void PopulateStreams(); void PopulateStreams();
StreamList LoadStreams() const; DigitallyImportedClient::ChannelList LoadChannels() const;
void SaveStreams(const StreamList& streams); void SaveChannels(const DigitallyImportedClient::ChannelList& streams);
void LoadStation(const QString& key); void LoadStation(const QString& key);
@ -122,15 +114,13 @@ private:
QString username_; QString username_;
QString listen_hash_; QString listen_hash_;
int task_id_;
QStandardItem* root_; QStandardItem* root_;
boost::scoped_ptr<QMenu> context_menu_; boost::scoped_ptr<QMenu> context_menu_;
QStandardItem* context_item_; QStandardItem* context_item_;
QList<Stream> saved_streams_; DigitallyImportedClient::ChannelList saved_channels_;
QDateTime last_refreshed_streams_; QDateTime last_refreshed_channels_;
DigitallyImportedClient* api_client_; DigitallyImportedClient* api_client_;
}; };

View File

@ -61,6 +61,8 @@ void DigitallyImportedSettingsPage::Login() {
} }
void DigitallyImportedSettingsPage::LoginFinished(QNetworkReply* reply) { void DigitallyImportedSettingsPage::LoginFinished(QNetworkReply* reply) {
reply->deleteLater();
DigitallyImportedClient::AuthReply result = client_->ParseAuthReply(reply); DigitallyImportedClient::AuthReply result = client_->ParseAuthReply(reply);
QString name = QString("%1 %2").arg(result.first_name_, result.last_name_); QString name = QString("%1 %2").arg(result.first_name_, result.last_name_);

View File

@ -111,17 +111,17 @@ msgid "%L1 total plays"
msgstr "" msgstr ""
#: transcoder/transcodedialog.cpp:198 #: transcoder/transcodedialog.cpp:198
#, c-format #, c-format, qt-plural-format
msgid "%n failed" msgid "%n failed"
msgstr "" msgstr ""
#: transcoder/transcodedialog.cpp:193 #: transcoder/transcodedialog.cpp:193
#, c-format #, c-format, qt-plural-format
msgid "%n finished" msgid "%n finished"
msgstr "" msgstr ""
#: transcoder/transcodedialog.cpp:188 #: transcoder/transcodedialog.cpp:188
#, c-format #, c-format, qt-plural-format
msgid "%n remaining" msgid "%n remaining"
msgstr "" msgstr ""
@ -662,7 +662,7 @@ msgstr ""
msgid "Authenticating..." msgid "Authenticating..."
msgstr "" msgstr ""
#: internet/digitallyimportedsettingspage.cpp:80 #: internet/digitallyimportedsettingspage.cpp:82
#: internet/magnatunesettingspage.cpp:113 internet/lastfmservice.cpp:434 #: internet/magnatunesettingspage.cpp:113 internet/lastfmservice.cpp:434
#: internet/lastfmsettingspage.cpp:79 remote/remotesettingspage.cpp:113 #: internet/lastfmsettingspage.cpp:79 remote/remotesettingspage.cpp:113
msgid "Authentication failed" msgid "Authentication failed"
@ -1003,7 +1003,7 @@ msgstr ""
msgid "Configure library..." msgid "Configure library..."
msgstr "" msgstr ""
#: internet/digitallyimportedservicebase.cpp:219 #: internet/digitallyimportedservicebase.cpp:189
#: ../bin/src/ui_globalsearchsettingspage.h:167 #: ../bin/src/ui_globalsearchsettingspage.h:167
msgid "Configure..." msgid "Configure..."
msgstr "" msgstr ""
@ -1593,7 +1593,7 @@ msgstr ""
msgid "Error loading %1" msgid "Error loading %1"
msgstr "" msgstr ""
#: internet/digitallyimportedservicebase.cpp:241 #: internet/digitallyimportedservicebase.cpp:211
#: internet/digitallyimportedurlhandler.cpp:73 #: internet/digitallyimportedurlhandler.cpp:73
msgid "Error loading di.fm playlist" msgid "Error loading di.fm playlist"
msgstr "" msgstr ""
@ -1615,7 +1615,7 @@ msgstr ""
msgid "Except between tracks on the same album or in the same CUE sheet" msgid "Except between tracks on the same album or in the same CUE sheet"
msgstr "" msgstr ""
#: internet/digitallyimportedsettingspage.cpp:93 #: internet/digitallyimportedsettingspage.cpp:95
#, qt-format #, qt-format
msgid "Expires on %1" msgid "Expires on %1"
msgstr "" msgstr ""
@ -1854,7 +1854,7 @@ msgstr ""
msgid "Getting channels" msgid "Getting channels"
msgstr "" msgstr ""
#: internet/digitallyimportedservicebase.cpp:121 #: internet/digitallyimportedservicebase.cpp:111
msgid "Getting streams" msgid "Getting streams"
msgstr "" msgstr ""
@ -2736,7 +2736,7 @@ msgstr ""
msgid "Only show the first" msgid "Only show the first"
msgstr "" msgstr ""
#: internet/digitallyimportedservicebase.cpp:212 #: internet/digitallyimportedservicebase.cpp:182
#, qt-format #, qt-format
msgid "Open %1 in browser" msgid "Open %1 in browser"
msgstr "" msgstr ""
@ -3146,7 +3146,7 @@ msgstr ""
msgid "Refresh station list" msgid "Refresh station list"
msgstr "" msgstr ""
#: internet/digitallyimportedservicebase.cpp:215 #: internet/digitallyimportedservicebase.cpp:185
msgid "Refresh streams" msgid "Refresh streams"
msgstr "" msgstr ""
@ -4078,7 +4078,7 @@ msgstr ""
msgid "Unknown" msgid "Unknown"
msgstr "" msgstr ""
#: internet/digitallyimportedclient.cpp:61 internet/lastfmservice.cpp:455 #: internet/digitallyimportedclient.cpp:68 internet/lastfmservice.cpp:455
msgid "Unknown error" msgid "Unknown error"
msgstr "" msgstr ""
@ -4471,7 +4471,7 @@ msgid "Zero"
msgstr "" msgstr ""
#: playlist/playlistundocommands.cpp:37 #: playlist/playlistundocommands.cpp:37
#, c-format #, c-format, qt-plural-format
msgid "add %n songs" msgid "add %n songs"
msgstr "" msgstr ""
@ -4591,7 +4591,7 @@ msgid "press enter"
msgstr "" msgstr ""
#: playlist/playlistundocommands.cpp:65 playlist/playlistundocommands.cpp:88 #: playlist/playlistundocommands.cpp:65 playlist/playlistundocommands.cpp:88
#, c-format #, c-format, qt-plural-format
msgid "remove %n songs" msgid "remove %n songs"
msgstr "" msgstr ""