Clementine-audio-player-Mac.../src/lastfmservice.cpp

550 lines
16 KiB
C++
Raw Normal View History

#include "lastfmservice.h"
#include "radioitem.h"
2009-12-26 23:15:57 +01:00
#include "song.h"
2009-12-30 03:15:38 +01:00
#include "lastfmstationdialog.h"
2010-02-03 19:32:48 +01:00
#include "lastfmconfigdialog.h"
2009-12-26 18:19:14 +01:00
#include <lastfm/ws.h>
#include <lastfm/misc.h>
#include <lastfm/XmlQuery>
2009-12-29 20:22:02 +01:00
#include <lastfm/Audioscrobbler>
2009-12-26 18:19:14 +01:00
#include <QSettings>
2009-12-30 00:01:07 +01:00
#include <QMenu>
2009-12-26 18:19:14 +01:00
2009-12-29 20:22:02 +01:00
const char* LastFMService::kServiceName = "Last.fm";
2009-12-26 18:19:14 +01:00
const char* LastFMService::kSettingsGroup = "Last.fm";
2010-01-18 03:23:55 +01:00
const char* LastFMService::kLoadingText = "Loading Last.fm radio";
2009-12-29 20:22:02 +01:00
const char* LastFMService::kAudioscrobblerClientId = "tng";
const char* LastFMService::kApiKey = "75d20fb472be99275392aefa2760ea09";
const char* LastFMService::kSecret = "d3072b60ae626be12be69448f5c46e70";
2009-12-26 18:19:14 +01:00
LastFMService::LastFMService(QObject* parent)
2009-12-29 20:22:02 +01:00
: RadioService(kServiceName, parent),
tuner_(NULL),
2009-12-29 20:22:02 +01:00
scrobbler_(NULL),
2010-02-03 19:32:48 +01:00
config_(NULL),
2009-12-30 03:15:38 +01:00
station_dialog_(new LastFMStationDialog),
2009-12-30 00:01:07 +01:00
context_menu_(new QMenu),
2009-12-29 21:48:50 +01:00
initial_tune_(false),
2009-12-30 01:31:00 +01:00
scrobbling_enabled_(false),
2009-12-30 03:15:38 +01:00
artist_list_(NULL),
tag_list_(NULL),
2009-12-30 01:31:00 +01:00
friends_list_(NULL),
neighbours_list_(NULL)
{
2009-12-29 20:22:02 +01:00
lastfm::ws::ApiKey = kApiKey;
lastfm::ws::SharedSecret = kSecret;
2009-12-26 18:19:14 +01:00
2010-02-03 19:32:48 +01:00
ReloadSettings();
2009-12-30 00:01:07 +01:00
2009-12-30 02:41:37 +01:00
play_action_ = context_menu_->addAction(QIcon(":media-playback-start.png"), "Add to playlist", this, SLOT(AddToPlaylist()));
remove_action_ = context_menu_->addAction(QIcon(":list-remove.png"), "Remove", this, SLOT(Remove()));
2009-12-30 02:41:37 +01:00
context_menu_->addSeparator();
2009-12-30 03:15:38 +01:00
add_artist_action_ = context_menu_->addAction(QIcon(":last.fm/icon_radio.png"), "Play artist radio...", this, SLOT(AddArtistRadio()));
add_tag_action_ = context_menu_->addAction(QIcon(":last.fm/icon_tag.png"), "Play tag radio...", this, SLOT(AddTagRadio()));
2009-12-30 00:01:07 +01:00
context_menu_->addAction(QIcon(":configure.png"), "Configure Last.fm...",
2010-02-03 19:32:48 +01:00
this, SLOT(ShowConfig()));
2009-12-30 02:41:37 +01:00
remove_action_->setEnabled(false);
2009-12-30 03:15:38 +01:00
add_artist_action_->setEnabled(false);
add_tag_action_->setEnabled(false);
2009-12-26 18:19:14 +01:00
}
LastFMService::~LastFMService() {
delete config_;
2009-12-30 03:15:38 +01:00
delete station_dialog_;
2009-12-30 00:01:07 +01:00
delete context_menu_;
}
2010-02-03 19:32:48 +01:00
void LastFMService::ReloadSettings() {
QSettings settings;
settings.beginGroup(kSettingsGroup);
lastfm::ws::Username = settings.value("Username").toString();
lastfm::ws::SessionKey = settings.value("Session").toString();
scrobbling_enabled_ = settings.value("ScrobblingEnabled", true).toBool();
emit ScrobblingEnabledChanged(scrobbling_enabled_);
2009-12-29 20:22:02 +01:00
}
2010-02-03 19:32:48 +01:00
void LastFMService::ShowConfig() {
if (!config_) {
config_ = new LastFMConfigDialog;
}
2009-12-29 21:48:50 +01:00
2010-02-03 19:32:48 +01:00
config_->show();
}
2009-12-29 21:48:50 +01:00
2010-02-03 19:32:48 +01:00
bool LastFMService::IsAuthenticated() const {
return !lastfm::ws::SessionKey.isEmpty();
2009-12-29 21:48:50 +01:00
}
RadioItem* LastFMService::CreateRootItem(RadioItem* parent) {
2010-01-18 03:23:55 +01:00
RadioItem* item = new RadioItem(this, RadioItem::Type_Service, kServiceName, parent);
item->icon = QIcon(":last.fm/as.png");
return item;
}
void LastFMService::LazyPopulate(RadioItem *item) {
switch (item->type) {
case RadioItem::Type_Service:
2009-12-30 01:31:00 +01:00
// Normal radio types
CreateStationItem(Type_MyRecommendations, "My Recommendations", ":last.fm/recommended_radio.png", item);
CreateStationItem(Type_MyRadio, "My Radio Station", ":last.fm/personal_radio.png", item);
CreateStationItem(Type_MyLoved, "My Loved Tracks", ":last.fm/loved_radio.png", item);
CreateStationItem(Type_MyNeighbourhood, "My Neighbourhood", ":last.fm/neighbour_radio.png", item);
2009-12-26 18:19:14 +01:00
2009-12-30 03:15:38 +01:00
// Types that have children
artist_list_ = new RadioItem(this, Type_ArtistRadio, "Artist radio", item);
artist_list_->icon = QIcon(":last.fm/icon_radio.png");
artist_list_->lazy_loaded = true;
2009-12-30 01:31:00 +01:00
2009-12-30 03:15:38 +01:00
tag_list_ = new RadioItem(this, Type_TagRadio, "Tag radio", item);
tag_list_->icon = QIcon(":last.fm/icon_tag.png");
tag_list_->lazy_loaded = true;
RestoreList("artists", Type_Artist, QIcon(":last.fm/icon_radio.png"), artist_list_);
RestoreList("tags", Type_Tag, QIcon(":last.fm/icon_tag.png"), tag_list_);
2009-12-30 01:31:00 +01:00
friends_list_ = new RadioItem(this, Type_MyFriends, "Friends", item);
friends_list_->icon = QIcon(":last.fm/my_friends.png");
neighbours_list_ = new RadioItem(this, Type_MyNeighbours, "Neighbours", item);
neighbours_list_->icon = QIcon(":last.fm/my_neighbours.png");
2009-12-29 20:22:02 +01:00
if (!IsAuthenticated())
2010-02-03 19:32:48 +01:00
ShowConfig();
2009-12-30 03:15:38 +01:00
add_artist_action_->setEnabled(true);
add_tag_action_->setEnabled(true);
2009-12-26 18:19:14 +01:00
break;
2009-12-30 01:31:00 +01:00
case Type_MyFriends:
RefreshFriends();
break;
case Type_MyNeighbours:
RefreshNeighbours();
break;
case Type_OtherUser:
CreateStationItem(Type_OtherUserRadio, item->key, ":last.fm/recommended_radio.png", item)
->display_text = item->key + "'s Radio Station";
CreateStationItem(Type_OtherUserLoved, item->key, ":last.fm/loved_radio.png", item)
->display_text = item->key + "'s Loved Tracks";
CreateStationItem(Type_OtherUserNeighbourhood, item->key, ":last.fm/neighbour_radio.png", item)
->display_text = item->key + "'s Neighbourhood";
break;
2009-12-26 18:19:14 +01:00
default:
break;
}
2009-12-26 18:19:14 +01:00
item->lazy_loaded = true;
}
RadioItem* LastFMService::CreateStationItem(ItemType type, const QString& name,
const QString& icon, RadioItem* parent) {
RadioItem* ret = new RadioItem(this, type, name, parent);
ret->lazy_loaded = true;
ret->icon = QIcon(icon);
2009-12-26 22:35:45 +01:00
ret->playable = true;
return ret;
}
2009-12-26 18:19:14 +01:00
void LastFMService::Authenticate(const QString& username, const QString& password) {
QMap<QString, QString> params;
params["method"] = "auth.getMobileSession";
params["username"] = username;
params["authToken"] = lastfm::md5((username + lastfm::md5(password.toUtf8())).toUtf8());
QNetworkReply* reply = lastfm::ws::post(params);
connect(reply, SIGNAL(finished()), SLOT(AuthenticateReplyFinished()));
}
void LastFMService::AuthenticateReplyFinished() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if (!reply) {
emit AuthenticationComplete(false);
return;
}
// Parse the reply
try {
lastfm::XmlQuery const lfm = lastfm::ws::parse(reply);
lastfm::ws::Username = lfm["session"]["name"].text();
lastfm::ws::SessionKey = lfm["session"]["key"].text();
} catch (std::runtime_error& e) {
qDebug() << e.what();
emit AuthenticationComplete(false);
return;
}
// Save the session key
QSettings settings;
settings.beginGroup(kSettingsGroup);
2010-02-03 19:32:48 +01:00
settings.setValue("Username", lastfm::ws::Username);
settings.setValue("Session", lastfm::ws::SessionKey);
2009-12-26 18:19:14 +01:00
2009-12-29 20:22:02 +01:00
// Invalidate the scrobbler - it will get recreated later
delete scrobbler_;
scrobbler_ = NULL;
2009-12-26 18:19:14 +01:00
emit AuthenticationComplete(true);
}
2009-12-26 22:35:45 +01:00
2009-12-30 00:17:54 +01:00
QUrl LastFMService::UrlForItem(const RadioItem* item) const {
2009-12-26 22:35:45 +01:00
switch (item->type) {
case Type_MyRecommendations:
2009-12-30 00:17:54 +01:00
return "lastfm://user/" + lastfm::ws::Username + "/recommended";
2009-12-26 22:35:45 +01:00
case Type_MyLoved:
2009-12-30 00:17:54 +01:00
return "lastfm://user/" + lastfm::ws::Username + "/loved";
2009-12-26 22:35:45 +01:00
case Type_MyNeighbourhood:
2009-12-30 00:17:54 +01:00
return "lastfm://user/" + lastfm::ws::Username + "/neighbours";
2009-12-26 22:35:45 +01:00
case Type_MyRadio:
2009-12-30 00:17:54 +01:00
return "lastfm://user/" + lastfm::ws::Username + "/library";
2009-12-30 01:31:00 +01:00
case Type_OtherUser:
case Type_OtherUserRadio:
2009-12-30 01:31:00 +01:00
return "lastfm://user/" + item->key + "/library";
case Type_OtherUserLoved:
return "lastfm://user/" + item->key + "/loved";
case Type_OtherUserNeighbourhood:
return "lastfm://user/" + item->key + "/neighbours";
2009-12-30 03:15:38 +01:00
case Type_Artist:
return "lastfm://artist/" + item->key + "/similarartists";
case Type_Tag:
return "lastfm://globaltags/" + item->key;
2009-12-26 22:35:45 +01:00
}
2009-12-30 00:17:54 +01:00
return QUrl();
}
2009-12-26 22:35:45 +01:00
2009-12-30 00:17:54 +01:00
QString LastFMService::TitleForItem(const RadioItem* item) const {
2009-12-30 01:31:00 +01:00
const QString me(lastfm::ws::Username);
2009-12-30 00:17:54 +01:00
switch (item->type) {
2009-12-30 01:31:00 +01:00
case Type_MyRecommendations: return me + "'s Recommended Radio";
case Type_MyLoved: return me + "'s Loved Tracks";
case Type_MyNeighbourhood: return me + "'s Neighbour Radio";
case Type_MyRadio: return me + "'s Library";
case Type_OtherUser:
case Type_OtherUserRadio: return item->key + "'s Library";
case Type_OtherUserLoved: return item->key + "'s Loved Tracks";
case Type_OtherUserNeighbourhood: return item->key + "'s Neighbour Radio";
2009-12-30 03:15:38 +01:00
case Type_Artist: return "Similar artists to " + item->key;
case Type_Tag: return "Tag radio: " + item->key;
2009-12-30 00:17:54 +01:00
}
return QString();
2009-12-26 22:35:45 +01:00
}
void LastFMService::StartLoading(const QUrl& url) {
if (url.scheme() != "lastfm")
return;
2009-12-29 20:22:02 +01:00
if (!IsAuthenticated())
2009-12-26 22:35:45 +01:00
return;
2010-01-18 03:23:55 +01:00
emit TaskStarted(kLoadingText);
2009-12-26 22:35:45 +01:00
delete tuner_;
2009-12-26 22:35:45 +01:00
last_url_ = url;
initial_tune_ = true;
2009-12-26 22:35:45 +01:00
tuner_ = new lastfm::RadioTuner(lastfm::RadioStation(url));
2009-12-26 22:35:45 +01:00
connect(tuner_, SIGNAL(trackAvailable()), SLOT(TunerTrackAvailable()));
connect(tuner_, SIGNAL(error(lastfm::ws::Error)), SLOT(TunerError(lastfm::ws::Error)));
}
void LastFMService::LoadNext(const QUrl &) {
2009-12-29 20:22:02 +01:00
last_track_ = tuner_->takeNextTrack();
2009-12-29 20:22:02 +01:00
if (last_track_.isNull()) {
emit StreamFinished();
return;
}
Song metadata;
2009-12-29 20:22:02 +01:00
metadata.InitFromLastFM(last_track_);
emit StreamMetadataFound(last_url_, metadata);
2009-12-29 20:22:02 +01:00
emit StreamReady(last_url_, last_track_.url());
}
2009-12-26 22:35:45 +01:00
void LastFMService::TunerError(lastfm::ws::Error error) {
qDebug() << "Last.fm error" << error;
if (!initial_tune_)
return;
2010-01-18 03:23:55 +01:00
emit TaskFinished(kLoadingText);
2009-12-26 22:35:45 +01:00
if (error == lastfm::ws::NotEnoughContent) {
emit StreamFinished();
return;
}
emit StreamError(ErrorString(error));
}
QString LastFMService::ErrorString(lastfm::ws::Error error) const {
switch (error) {
case lastfm::ws::InvalidService: return "Invalid service";
case lastfm::ws::InvalidMethod: return "Invalid method";
case lastfm::ws::AuthenticationFailed: return "Authentication failed";
case lastfm::ws::InvalidFormat: return "Invalid format";
case lastfm::ws::InvalidParameters: return "Invalid parameters";
case lastfm::ws::InvalidResourceSpecified: return "Invalid resource specified";
case lastfm::ws::OperationFailed: return "Operation failed";
case lastfm::ws::InvalidSessionKey: return "Invalid session key";
case lastfm::ws::InvalidApiKey: return "Invalid API key";
case lastfm::ws::ServiceOffline: return "Service offline";
case lastfm::ws::SubscribersOnly: return "This stream is for paid subscribers only";
case lastfm::ws::TryAgainLater: return "Last.fm is currently busy, please try again in a few minutes";
case lastfm::ws::NotEnoughContent: return "Not enough content";
case lastfm::ws::NotEnoughMembers: return "Not enough members";
case lastfm::ws::NotEnoughFans: return "Not enough fans";
case lastfm::ws::NotEnoughNeighbours: return "Not enough neighbours";
case lastfm::ws::MalformedResponse: return "Malformed response";
case lastfm::ws::UnknownError:
default:
return "Unknown error";
}
}
void LastFMService::TunerTrackAvailable() {
if (initial_tune_) {
2010-01-18 03:23:55 +01:00
emit TaskFinished(kLoadingText);
2009-12-26 22:35:45 +01:00
LoadNext(last_url_);
initial_tune_ = false;
}
2009-12-26 22:35:45 +01:00
}
2009-12-29 20:22:02 +01:00
bool LastFMService::InitScrobbler() {
2009-12-29 21:48:50 +01:00
if (!IsAuthenticated() || !IsScrobblingEnabled())
2009-12-29 20:22:02 +01:00
return false;
2009-12-29 20:57:33 +01:00
if (!scrobbler_)
2009-12-29 20:22:02 +01:00
scrobbler_ = new lastfm::Audioscrobbler(kAudioscrobblerClientId);
return true;
}
lastfm::Track LastFMService::TrackFromSong(const Song &song) const {
if (song.title() == last_track_.title() &&
song.artist() == last_track_.artist() &&
song.album() == last_track_.album())
return last_track_;
lastfm::Track ret;
song.ToLastFM(&ret);
return ret;
}
void LastFMService::NowPlaying(const Song &song) {
if (!InitScrobbler())
return;
2009-12-29 21:11:03 +01:00
last_track_ = TrackFromSong(song);
lastfm::MutableTrack mtrack(last_track_);
mtrack.stamp();
scrobbler_->nowPlaying(last_track_);
2009-12-29 20:22:02 +01:00
}
2009-12-29 21:11:03 +01:00
void LastFMService::Scrobble() {
2009-12-29 20:22:02 +01:00
if (!InitScrobbler())
return;
2009-12-29 21:11:03 +01:00
scrobbler_->cache(last_track_);
scrobbler_->submit();
2009-12-29 20:22:02 +01:00
}
2009-12-29 21:11:03 +01:00
void LastFMService::Love() {
2009-12-29 21:48:50 +01:00
if (!IsAuthenticated())
2010-02-03 19:32:48 +01:00
ShowConfig();
2009-12-29 21:48:50 +01:00
2009-12-29 21:11:03 +01:00
lastfm::MutableTrack mtrack(last_track_);
2009-12-29 20:22:02 +01:00
mtrack.love();
}
2009-12-29 21:11:03 +01:00
void LastFMService::Ban() {
lastfm::MutableTrack mtrack(last_track_);
2009-12-29 20:22:02 +01:00
mtrack.ban();
2009-12-29 21:48:50 +01:00
Scrobble();
LoadNext(last_url_);
2009-12-29 20:22:02 +01:00
}
2009-12-30 00:01:07 +01:00
2009-12-30 02:41:37 +01:00
void LastFMService::ShowContextMenu(RadioItem* item, const QPoint &global_pos) {
context_item_ = item;
switch (item->type) {
case Type_Artist:
case Type_Tag:
remove_action_->setEnabled(true);
break;
default:
remove_action_->setEnabled(false);
break;
}
2009-12-30 02:41:37 +01:00
play_action_->setEnabled(item->playable);
2009-12-30 00:01:07 +01:00
context_menu_->popup(global_pos);
}
2009-12-30 01:31:00 +01:00
void LastFMService::RefreshFriends() {
if (!friends_list_ || !IsAuthenticated())
return;
friends_list_->ClearNotify();
lastfm::AuthenticatedUser user;
QNetworkReply* reply = user.getFriends();
connect(reply, SIGNAL(finished()), SLOT(RefreshFriendsFinished()));
}
void LastFMService::RefreshNeighbours() {
if (!friends_list_ || !IsAuthenticated())
return;
neighbours_list_->ClearNotify();
lastfm::AuthenticatedUser user;
QNetworkReply* reply = user.getNeighbours();
connect(reply, SIGNAL(finished()), SLOT(RefreshNeighboursFinished()));
}
void LastFMService::RefreshFriendsFinished() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if (!reply)
return;
QList<lastfm::User> friends;
try {
friends = lastfm::User::list(reply);
} catch (std::runtime_error& e) {
qDebug() << e.what();
return;
}
foreach (const lastfm::User& f, friends) {
RadioItem* item = new RadioItem(this, Type_OtherUser, f);
2009-12-30 01:31:00 +01:00
item->icon = QIcon(":last.fm/icon_user.png");
item->playable = true;
item->InsertNotify(friends_list_);
}
}
void LastFMService::RefreshNeighboursFinished() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if (!reply)
return;
QList<lastfm::User> neighbours;
try {
neighbours = lastfm::User::list(reply);
} catch (std::runtime_error& e) {
qDebug() << e.what();
return;
}
foreach (const lastfm::User& n, neighbours) {
RadioItem* item = new RadioItem(this, Type_OtherUser, n);
2009-12-30 01:31:00 +01:00
item->icon = QIcon(":last.fm/user_purple.png");
item->playable = true;
item->InsertNotify(neighbours_list_);
}
}
2009-12-30 02:41:37 +01:00
void LastFMService::AddToPlaylist() {
emit AddItemToPlaylist(context_item_);
}
2009-12-30 03:15:38 +01:00
void LastFMService::AddArtistRadio() {
AddArtistOrTag("artists", LastFMStationDialog::Artist, Type_Artist, QIcon(":last.fm/icon_radio.png"), artist_list_);
}
void LastFMService::AddTagRadio() {
AddArtistOrTag("tags", LastFMStationDialog::Tag, Type_Tag, QIcon(":last.fm/icon_tag.png"), tag_list_);
}
void LastFMService::AddArtistOrTag(const QString& name,
LastFMStationDialog::Type dialog_type, ItemType item_type,
const QIcon& icon, RadioItem* list) {
station_dialog_->SetType(dialog_type);
if (station_dialog_->exec() == QDialog::Rejected)
return;
if (station_dialog_->content().isEmpty())
return;
RadioItem* item = new RadioItem(this, item_type, station_dialog_->content());
item->icon = icon;
item->playable = true;
item->lazy_loaded = true;
item->InsertNotify(list);
emit AddItemToPlaylist(item);
SaveList(name, list);
}
void LastFMService::SaveList(const QString& name, RadioItem* list) const {
QSettings settings;
settings.beginGroup(kSettingsGroup);
settings.beginWriteArray(name, list->children.count());
for (int i=0 ; i<list->children.count() ; ++i) {
settings.setArrayIndex(i);
settings.setValue("key", list->children[i]->key);
}
settings.endArray();
}
void LastFMService::RestoreList(const QString &name, ItemType item_type,
const QIcon& icon, RadioItem *list) {
QSettings settings;
settings.beginGroup(kSettingsGroup);
list->ClearNotify();
int count = settings.beginReadArray(name);
for (int i=0 ; i<count ; ++i) {
settings.setArrayIndex(i);
RadioItem* item = new RadioItem(this, item_type,
settings.value("key").toString(), list);
item->icon = icon;
item->playable = true;
item->lazy_loaded = true;
}
settings.endArray();
}
void LastFMService::Remove() {
int type = context_item_->type;
context_item_->parent->DeleteNotify(context_item_->row);
if (type == Type_Artist)
SaveList("artists", artist_list_);
else if (type == Type_Tag)
SaveList("tags", tag_list_);
}