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/gstelementdeleter.h
globalsearch/librarysearchprovider.h
globalsearch/globalsearch.h
globalsearch/globalsearchpopup.h
globalsearch/globalsearchsettingspage.h

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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());
}

View File

@ -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.

View File

@ -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_);
}

View File

@ -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_;

View File

@ -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) {

View File

@ -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_;
};

View File

@ -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_);

View File

@ -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 ""