mirror of
https://github.com/clementine-player/Clementine
synced 2025-01-31 11:35:24 +01:00
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:
parent
37eeb70e3a
commit
2b6beb7417
@ -370,7 +370,6 @@ set(HEADERS
|
||||
engines/gstenginepipeline.h
|
||||
engines/gstelementdeleter.h
|
||||
|
||||
globalsearch/librarysearchprovider.h
|
||||
globalsearch/globalsearch.h
|
||||
globalsearch/globalsearchpopup.h
|
||||
globalsearch/globalsearchsettingspage.h
|
||||
|
@ -24,8 +24,8 @@ DigitallyImportedSearchProvider::DigitallyImportedSearchProvider(
|
||||
: SimpleSearchProvider(parent),
|
||||
service_(service)
|
||||
{
|
||||
Init(service_->name(), service->url_scheme(), service_->icon());
|
||||
icon_ = ScaleAndPad(QImage(service_->icon_path()));
|
||||
Init(service_->name(), service->url_scheme(), service_->icon(),
|
||||
ArtIsInSongMetadata);
|
||||
|
||||
set_safe_words(QStringList() << "sky.fm" << "skyfm" << "di.fm" << "difm"
|
||||
<< "digitallyimported");
|
||||
@ -33,21 +33,14 @@ DigitallyImportedSearchProvider::DigitallyImportedSearchProvider(
|
||||
connect(service_, SIGNAL(StreamsChanged()), SLOT(MaybeRecreateItems()));
|
||||
}
|
||||
|
||||
void DigitallyImportedSearchProvider::LoadArtAsync(int id, const Result& result) {
|
||||
emit ArtLoaded(id, icon_);
|
||||
}
|
||||
|
||||
void DigitallyImportedSearchProvider::RecreateItems() {
|
||||
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.set_title(stream.name_);
|
||||
song.set_artist(service_->service_description());
|
||||
song.set_url(QUrl(service_->url_scheme() + "://" + stream.key_));
|
||||
|
||||
service_->SongFromChannel(channel, &song);
|
||||
items << Item(song);
|
||||
}
|
||||
|
||||
|
@ -27,14 +27,11 @@ public:
|
||||
DigitallyImportedSearchProvider(DigitallyImportedServiceBase* service,
|
||||
QObject* parent);
|
||||
|
||||
void LoadArtAsync(int id, const Result& result);
|
||||
|
||||
protected:
|
||||
void RecreateItems();
|
||||
|
||||
private:
|
||||
DigitallyImportedServiceBase* service_;
|
||||
QImage icon_;
|
||||
};
|
||||
|
||||
#endif // DIGITALLYIMPORTEDSEARCHPROVIDER_H
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "librarysearchprovider.h"
|
||||
#include "globalsearch.h"
|
||||
#include "core/logging.h"
|
||||
#include "covers/albumcoverloader.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QStringBuilder>
|
||||
@ -29,8 +30,17 @@ const char* GlobalSearch::kSettingsGroup = "GlobalSearch";
|
||||
|
||||
GlobalSearch::GlobalSearch(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) {
|
||||
@ -169,7 +179,10 @@ int GlobalSearch::LoadArtAsync(const SearchProvider::Result& result) {
|
||||
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;
|
||||
request.id_ = id;
|
||||
request.result_ = result;
|
||||
@ -199,6 +212,18 @@ void GlobalSearch::TakeNextQueuedArt(SearchProvider* provider) {
|
||||
|
||||
void GlobalSearch::ArtLoadedSlot(int id, const QImage& image) {
|
||||
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);
|
||||
|
||||
QPixmap pixmap = QPixmap::fromImage(image);
|
||||
@ -206,7 +231,8 @@ void GlobalSearch::ArtLoadedSlot(int id, const QImage& image) {
|
||||
|
||||
emit ArtLoaded(id, pixmap);
|
||||
|
||||
if (providers_.contains(provider) &&
|
||||
if (provider &&
|
||||
providers_.contains(provider) &&
|
||||
!providers_[provider].queued_art_.isEmpty()) {
|
||||
providers_[provider].queued_art_.removeFirst();
|
||||
TakeNextQueuedArt(provider);
|
||||
|
@ -22,8 +22,11 @@
|
||||
#include <QPixmapCache>
|
||||
|
||||
#include "searchprovider.h"
|
||||
#include "core/backgroundthread.h"
|
||||
|
||||
|
||||
class AlbumCoverLoader;
|
||||
|
||||
class GlobalSearch : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
@ -74,10 +77,12 @@ private slots:
|
||||
void SearchFinishedSlot(int id);
|
||||
|
||||
void ArtLoadedSlot(int id, const QImage& image);
|
||||
void AlbumArtLoaded(quint64 id, const QImage& image);
|
||||
|
||||
void ProviderDestroyedSlot(QObject* object);
|
||||
|
||||
private:
|
||||
void HandleLoadedArt(int id, const QImage& image, SearchProvider* provider);
|
||||
void TakeNextQueuedArt(SearchProvider* provider);
|
||||
QString PixmapCacheKey(const SearchProvider::Result& result) const;
|
||||
|
||||
@ -111,6 +116,10 @@ private:
|
||||
QMap<int, QString> pending_art_searches_;
|
||||
|
||||
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
|
||||
|
@ -30,19 +30,9 @@ LibrarySearchProvider::LibrarySearchProvider(LibraryBackendInterface* backend,
|
||||
const QIcon& icon,
|
||||
QObject* parent)
|
||||
: BlockingSearchProvider(parent),
|
||||
backend_(backend),
|
||||
cover_loader_(new BackgroundThreadImplementation<AlbumCoverLoader, AlbumCoverLoader>(this))
|
||||
backend_(backend)
|
||||
{
|
||||
Init(name, id, icon, WantsSerialisedArtQueries);
|
||||
|
||||
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)));
|
||||
Init(name, id, icon, WantsSerialisedArtQueries | ArtIsInSongMetadata);
|
||||
}
|
||||
|
||||
SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString& query) {
|
||||
@ -114,19 +104,6 @@ SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString&
|
||||
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) {
|
||||
SongList ret;
|
||||
|
||||
|
@ -26,24 +26,15 @@ class LibraryBackendInterface;
|
||||
|
||||
|
||||
class LibrarySearchProvider : public BlockingSearchProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LibrarySearchProvider(LibraryBackendInterface* backend, const QString& name,
|
||||
const QString& id, const QIcon& icon, QObject* parent = 0);
|
||||
|
||||
ResultList Search(int id, const QString& query);
|
||||
void LoadArtAsync(int id, const Result& result);
|
||||
void LoadTracksAsync(int id, const Result& result);
|
||||
|
||||
private slots:
|
||||
void AlbumArtLoaded(quint64 id, const QImage& image);
|
||||
|
||||
private:
|
||||
LibraryBackendInterface* backend_;
|
||||
|
||||
BackgroundThread<AlbumCoverLoader>* cover_loader_;
|
||||
QMap<quint64, int> cover_loader_tasks_;
|
||||
};
|
||||
|
||||
#endif // LIBRARYSEARCHPROVIDER_H
|
||||
|
@ -137,3 +137,7 @@ namespace {
|
||||
void SearchProvider::SortSongs(SongList* list) {
|
||||
qStableSort(list->begin(), list->end(), SortSongsCompare);
|
||||
}
|
||||
|
||||
void SearchProvider::LoadArtAsync(int id, const Result& result) {
|
||||
emit ArtLoaded(id, QImage());
|
||||
}
|
||||
|
@ -75,7 +75,13 @@ public:
|
||||
// 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
|
||||
// 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)
|
||||
|
||||
@ -87,6 +93,7 @@ public:
|
||||
bool wants_delayed_queries() const { return hints() & WantsDelayedQueries; }
|
||||
bool wants_serialised_art() const { return hints() & WantsSerialisedArtQueries; }
|
||||
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
|
||||
// SearchFinished exactly once, using this ID.
|
||||
@ -94,7 +101,7 @@ public:
|
||||
|
||||
// Starts loading an icon for a result that was previously emitted by
|
||||
// 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
|
||||
// ResultsAvailable. Must emit TracksLoaded exactly once with this ID.
|
||||
|
@ -33,6 +33,9 @@ const char* DigitallyImportedClient::kApiPassword = "dayeiph0ne@pp";
|
||||
const char* DigitallyImportedClient::kAuthUrl =
|
||||
"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)
|
||||
: 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,
|
||||
const QString& password) {
|
||||
QNetworkRequest req(QUrl(QString(kAuthUrl).arg(service_name_)));
|
||||
req.setRawHeader("Authorization",
|
||||
"Basic " + QString("%1:%2").arg(kApiUsername, kApiPassword)
|
||||
.toAscii().toBase64());
|
||||
SetAuthorisationHeader(&req);
|
||||
|
||||
QByteArray postdata = "username=" + QUrl::toPercentEncoding(username) +
|
||||
"&password=" + QUrl::toPercentEncoding(password);
|
||||
@ -83,3 +90,65 @@ DigitallyImportedClient::ParseAuthReply(QNetworkReply* reply) const {
|
||||
ret.listen_hash_ = user["listen_hash"].toString();
|
||||
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_);
|
||||
}
|
||||
|
@ -20,9 +20,12 @@
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
#include <QUrl>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QNetworkRequest;
|
||||
|
||||
class DigitallyImportedClient : public QObject {
|
||||
Q_OBJECT
|
||||
@ -33,6 +36,7 @@ public:
|
||||
static const char* kApiUsername;
|
||||
static const char* kApiPassword;
|
||||
static const char* kAuthUrl;
|
||||
static const char* kChannelListUrl;
|
||||
|
||||
struct AuthReply {
|
||||
bool success_;
|
||||
@ -47,9 +51,30 @@ public:
|
||||
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);
|
||||
AuthReply ParseAuthReply(QNetworkReply* reply) const;
|
||||
|
||||
QNetworkReply* GetChannelList();
|
||||
ChannelList ParseChannelList(QNetworkReply* reply) const;
|
||||
|
||||
private:
|
||||
void SetAuthorisationHeader(QNetworkRequest* req) const;
|
||||
|
||||
private:
|
||||
QNetworkAccessManager* network_;
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "digitallyimportedservicebase.h"
|
||||
#include "digitallyimportedurlhandler.h"
|
||||
#include "internetmodel.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/player.h"
|
||||
@ -44,7 +45,6 @@ DigitallyImportedServiceBase::DigitallyImportedServiceBase(
|
||||
url_handler_(new DigitallyImportedUrlHandler(this)),
|
||||
basic_audio_type_(1),
|
||||
premium_audio_type_(2),
|
||||
task_id_(-1),
|
||||
root_(NULL),
|
||||
context_item_(NULL),
|
||||
api_client_(NULL)
|
||||
@ -98,7 +98,7 @@ void DigitallyImportedServiceBase::LazyPopulate(QStandardItem* parent) {
|
||||
}
|
||||
|
||||
void DigitallyImportedServiceBase::RefreshStreams() {
|
||||
if (IsStreamListStale()) {
|
||||
if (IsChannelListStale()) {
|
||||
ForceRefreshStreams();
|
||||
return;
|
||||
}
|
||||
@ -107,61 +107,25 @@ void DigitallyImportedServiceBase::RefreshStreams() {
|
||||
}
|
||||
|
||||
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
|
||||
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() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
if (!reply) {
|
||||
return;
|
||||
}
|
||||
|
||||
model()->task_manager()->SetTaskFinished(task_id_);
|
||||
void DigitallyImportedServiceBase::RefreshStreamsFinished(QNetworkReply* reply, int task_id) {
|
||||
model()->task_manager()->SetTaskFinished(task_id);
|
||||
reply->deleteLater();
|
||||
|
||||
const QString data = QString::fromUtf8(reply->readAll());
|
||||
|
||||
// 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;
|
||||
}
|
||||
saved_channels_ = api_client_->ParseChannelList(reply);
|
||||
|
||||
// Sort by name
|
||||
qSort(saved_streams_);
|
||||
qSort(saved_channels_);
|
||||
|
||||
SaveStreams(saved_streams_);
|
||||
SaveChannels(saved_channels_);
|
||||
PopulateStreams();
|
||||
|
||||
emit StreamsChanged();
|
||||
@ -172,21 +136,27 @@ void DigitallyImportedServiceBase::PopulateStreams() {
|
||||
root_->removeRows(0, root_->rowCount());
|
||||
|
||||
// Add each stream to the model
|
||||
foreach (const Stream& stream, saved_streams_) {
|
||||
foreach (const DigitallyImportedClient::Channel& channel, saved_channels_) {
|
||||
Song song;
|
||||
song.set_title(stream.name_);
|
||||
song.set_artist(service_description_);
|
||||
song.set_url(QUrl(url_scheme_ + "://" + stream.key_));
|
||||
SongFromChannel(channel, &song);
|
||||
|
||||
QStandardItem* item = new QStandardItem(QIcon(":/last.fm/icon_radio.png"),
|
||||
stream.name_);
|
||||
item->setData(stream.description_, Qt::ToolTipRole);
|
||||
song.title());
|
||||
item->setData(channel.description_, Qt::ToolTipRole);
|
||||
item->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour);
|
||||
item->setData(QVariant::fromValue(song), InternetModel::Role_SongMetadata);
|
||||
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() {
|
||||
QDesktopServices::openUrl(homepage_url_);
|
||||
}
|
||||
@ -199,8 +169,8 @@ void DigitallyImportedServiceBase::ReloadSettings() {
|
||||
premium_audio_type_ = s.value("premium_audio_type", 2).toInt();
|
||||
username_ = s.value("username").toString();
|
||||
listen_hash_ = s.value("listen_hash").toString();
|
||||
last_refreshed_streams_ = s.value("last_refreshed_" + url_scheme_).toDateTime();
|
||||
saved_streams_ = LoadStreams();
|
||||
last_refreshed_channels_ = s.value("last_refreshed_v2_" + url_scheme_).toDateTime();
|
||||
saved_channels_ = LoadChannels();
|
||||
}
|
||||
|
||||
void DigitallyImportedServiceBase::ShowContextMenu(
|
||||
@ -248,8 +218,8 @@ void DigitallyImportedServiceBase::ShowSettingsDialog() {
|
||||
emit OpenSettingsAtPage(SettingsDialog::Page_DigitallyImported);
|
||||
}
|
||||
|
||||
DigitallyImportedServiceBase::StreamList DigitallyImportedServiceBase::LoadStreams() const {
|
||||
StreamList ret;
|
||||
DigitallyImportedClient::ChannelList DigitallyImportedServiceBase::LoadChannels() const {
|
||||
DigitallyImportedClient::ChannelList ret;
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
@ -258,49 +228,44 @@ DigitallyImportedServiceBase::StreamList DigitallyImportedServiceBase::LoadStrea
|
||||
for (int i=0 ; i<count ; ++i) {
|
||||
s.setArrayIndex(i);
|
||||
|
||||
Stream stream;
|
||||
stream.id_ = s.value("id").toInt();
|
||||
stream.key_ = s.value("key").toString();
|
||||
stream.name_ = s.value("name").toString();
|
||||
stream.description_ = s.value("description").toString();
|
||||
ret << stream;
|
||||
DigitallyImportedClient::Channel channel;
|
||||
channel.Load(s);
|
||||
ret << channel;
|
||||
}
|
||||
s.endArray();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DigitallyImportedServiceBase::SaveStreams(const StreamList& streams) {
|
||||
void DigitallyImportedServiceBase::SaveChannels(
|
||||
const DigitallyImportedClient::ChannelList& channels) {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
s.beginWriteArray(url_scheme_, streams.count());
|
||||
for (int i=0 ; i<streams.count() ; ++i) {
|
||||
const Stream& stream = streams[i];
|
||||
s.beginWriteArray(url_scheme_, channels.count());
|
||||
for (int i=0 ; i<channels.count() ; ++i) {
|
||||
s.setArrayIndex(i);
|
||||
s.setValue("id", stream.id_);
|
||||
s.setValue("key", stream.key_);
|
||||
s.setValue("name", stream.name_);
|
||||
s.setValue("description", stream.description_);
|
||||
channels[i].Save(&s);
|
||||
}
|
||||
s.endArray();
|
||||
|
||||
last_refreshed_streams_ = QDateTime::currentDateTime();
|
||||
s.setValue("last_refreshed_" + url_scheme_, last_refreshed_streams_);
|
||||
last_refreshed_channels_ = QDateTime::currentDateTime();
|
||||
s.setValue("last_refreshed_v2_" + url_scheme_, last_refreshed_channels_);
|
||||
}
|
||||
|
||||
bool DigitallyImportedServiceBase::IsStreamListStale() const {
|
||||
return last_refreshed_streams_.isNull() ||
|
||||
last_refreshed_streams_.secsTo(QDateTime::currentDateTime()) >
|
||||
bool DigitallyImportedServiceBase::IsChannelListStale() const {
|
||||
return last_refreshed_channels_.isNull() ||
|
||||
last_refreshed_channels_.secsTo(QDateTime::currentDateTime()) >
|
||||
kStreamsCacheDurationSecs;
|
||||
}
|
||||
|
||||
DigitallyImportedServiceBase::StreamList DigitallyImportedServiceBase::Streams() {
|
||||
if (IsStreamListStale()) {
|
||||
DigitallyImportedClient::ChannelList DigitallyImportedServiceBase::Channels() {
|
||||
if (IsChannelListStale()) {
|
||||
metaObject()->invokeMethod(this, "ForceRefreshStreams", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
return saved_streams_;
|
||||
return saved_channels_;
|
||||
}
|
||||
|
||||
void DigitallyImportedServiceBase::LoadStation(const QString& key) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
#ifndef DIGITALLYIMPORTEDSERVICEBASE_H
|
||||
#define DIGITALLYIMPORTEDSERVICEBASE_H
|
||||
|
||||
#include "digitallyimportedclient.h"
|
||||
#include "internetservice.h"
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
@ -57,19 +58,10 @@ public:
|
||||
const QString& url_scheme() const { return url_scheme_; }
|
||||
const QString& api_service_name() const { return api_service_name_; }
|
||||
|
||||
// Public for the global search provider.
|
||||
struct Stream {
|
||||
int id_;
|
||||
QString key_;
|
||||
QString name_;
|
||||
QString description_;
|
||||
|
||||
bool operator <(const Stream& other) const { return name_ < other.name_; }
|
||||
};
|
||||
typedef QList<Stream> StreamList;
|
||||
|
||||
bool IsStreamListStale() const;
|
||||
StreamList Streams();
|
||||
bool IsChannelListStale() const;
|
||||
DigitallyImportedClient::ChannelList Channels();
|
||||
void SongFromChannel(const DigitallyImportedClient::Channel& channel,
|
||||
Song* song) const;
|
||||
|
||||
signals:
|
||||
void StreamsChanged();
|
||||
@ -90,13 +82,13 @@ private slots:
|
||||
void Homepage();
|
||||
void ForceRefreshStreams();
|
||||
void RefreshStreams();
|
||||
void RefreshStreamsFinished();
|
||||
void RefreshStreamsFinished(QNetworkReply* reply, int task_id);
|
||||
void ShowSettingsDialog();
|
||||
|
||||
private:
|
||||
void PopulateStreams();
|
||||
StreamList LoadStreams() const;
|
||||
void SaveStreams(const StreamList& streams);
|
||||
DigitallyImportedClient::ChannelList LoadChannels() const;
|
||||
void SaveChannels(const DigitallyImportedClient::ChannelList& streams);
|
||||
|
||||
void LoadStation(const QString& key);
|
||||
|
||||
@ -122,15 +114,13 @@ private:
|
||||
QString username_;
|
||||
QString listen_hash_;
|
||||
|
||||
int task_id_;
|
||||
|
||||
QStandardItem* root_;
|
||||
|
||||
boost::scoped_ptr<QMenu> context_menu_;
|
||||
QStandardItem* context_item_;
|
||||
|
||||
QList<Stream> saved_streams_;
|
||||
QDateTime last_refreshed_streams_;
|
||||
DigitallyImportedClient::ChannelList saved_channels_;
|
||||
QDateTime last_refreshed_channels_;
|
||||
|
||||
DigitallyImportedClient* api_client_;
|
||||
};
|
||||
|
@ -61,6 +61,8 @@ void DigitallyImportedSettingsPage::Login() {
|
||||
}
|
||||
|
||||
void DigitallyImportedSettingsPage::LoginFinished(QNetworkReply* reply) {
|
||||
reply->deleteLater();
|
||||
|
||||
DigitallyImportedClient::AuthReply result = client_->ParseAuthReply(reply);
|
||||
|
||||
QString name = QString("%1 %2").arg(result.first_name_, result.last_name_);
|
||||
|
@ -111,17 +111,17 @@ msgid "%L1 total plays"
|
||||
msgstr ""
|
||||
|
||||
#: transcoder/transcodedialog.cpp:198
|
||||
#, c-format
|
||||
#, c-format, qt-plural-format
|
||||
msgid "%n failed"
|
||||
msgstr ""
|
||||
|
||||
#: transcoder/transcodedialog.cpp:193
|
||||
#, c-format
|
||||
#, c-format, qt-plural-format
|
||||
msgid "%n finished"
|
||||
msgstr ""
|
||||
|
||||
#: transcoder/transcodedialog.cpp:188
|
||||
#, c-format
|
||||
#, c-format, qt-plural-format
|
||||
msgid "%n remaining"
|
||||
msgstr ""
|
||||
|
||||
@ -662,7 +662,7 @@ msgstr ""
|
||||
msgid "Authenticating..."
|
||||
msgstr ""
|
||||
|
||||
#: internet/digitallyimportedsettingspage.cpp:80
|
||||
#: internet/digitallyimportedsettingspage.cpp:82
|
||||
#: internet/magnatunesettingspage.cpp:113 internet/lastfmservice.cpp:434
|
||||
#: internet/lastfmsettingspage.cpp:79 remote/remotesettingspage.cpp:113
|
||||
msgid "Authentication failed"
|
||||
@ -1003,7 +1003,7 @@ msgstr ""
|
||||
msgid "Configure library..."
|
||||
msgstr ""
|
||||
|
||||
#: internet/digitallyimportedservicebase.cpp:219
|
||||
#: internet/digitallyimportedservicebase.cpp:189
|
||||
#: ../bin/src/ui_globalsearchsettingspage.h:167
|
||||
msgid "Configure..."
|
||||
msgstr ""
|
||||
@ -1593,7 +1593,7 @@ msgstr ""
|
||||
msgid "Error loading %1"
|
||||
msgstr ""
|
||||
|
||||
#: internet/digitallyimportedservicebase.cpp:241
|
||||
#: internet/digitallyimportedservicebase.cpp:211
|
||||
#: internet/digitallyimportedurlhandler.cpp:73
|
||||
msgid "Error loading di.fm playlist"
|
||||
msgstr ""
|
||||
@ -1615,7 +1615,7 @@ msgstr ""
|
||||
msgid "Except between tracks on the same album or in the same CUE sheet"
|
||||
msgstr ""
|
||||
|
||||
#: internet/digitallyimportedsettingspage.cpp:93
|
||||
#: internet/digitallyimportedsettingspage.cpp:95
|
||||
#, qt-format
|
||||
msgid "Expires on %1"
|
||||
msgstr ""
|
||||
@ -1854,7 +1854,7 @@ msgstr ""
|
||||
msgid "Getting channels"
|
||||
msgstr ""
|
||||
|
||||
#: internet/digitallyimportedservicebase.cpp:121
|
||||
#: internet/digitallyimportedservicebase.cpp:111
|
||||
msgid "Getting streams"
|
||||
msgstr ""
|
||||
|
||||
@ -2736,7 +2736,7 @@ msgstr ""
|
||||
msgid "Only show the first"
|
||||
msgstr ""
|
||||
|
||||
#: internet/digitallyimportedservicebase.cpp:212
|
||||
#: internet/digitallyimportedservicebase.cpp:182
|
||||
#, qt-format
|
||||
msgid "Open %1 in browser"
|
||||
msgstr ""
|
||||
@ -3146,7 +3146,7 @@ msgstr ""
|
||||
msgid "Refresh station list"
|
||||
msgstr ""
|
||||
|
||||
#: internet/digitallyimportedservicebase.cpp:215
|
||||
#: internet/digitallyimportedservicebase.cpp:185
|
||||
msgid "Refresh streams"
|
||||
msgstr ""
|
||||
|
||||
@ -4078,7 +4078,7 @@ msgstr ""
|
||||
msgid "Unknown"
|
||||
msgstr ""
|
||||
|
||||
#: internet/digitallyimportedclient.cpp:61 internet/lastfmservice.cpp:455
|
||||
#: internet/digitallyimportedclient.cpp:68 internet/lastfmservice.cpp:455
|
||||
msgid "Unknown error"
|
||||
msgstr ""
|
||||
|
||||
@ -4471,7 +4471,7 @@ msgid "Zero"
|
||||
msgstr ""
|
||||
|
||||
#: playlist/playlistundocommands.cpp:37
|
||||
#, c-format
|
||||
#, c-format, qt-plural-format
|
||||
msgid "add %n songs"
|
||||
msgstr ""
|
||||
|
||||
@ -4591,7 +4591,7 @@ msgid "press enter"
|
||||
msgstr ""
|
||||
|
||||
#: playlist/playlistundocommands.cpp:65 playlist/playlistundocommands.cpp:88
|
||||
#, c-format
|
||||
#, c-format, qt-plural-format
|
||||
msgid "remove %n songs"
|
||||
msgstr ""
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user