Last.fm global search provider

This commit is contained in:
David Sansome 2011-09-24 17:01:18 +01:00
parent d83736acc0
commit cc20d90a7a
13 changed files with 310 additions and 23 deletions

View File

@ -659,6 +659,7 @@ endif(ENABLE_VISUALISATIONS)
if(HAVE_LIBLASTFM)
list(APPEND SOURCES
covers/lastfmcoverprovider.cpp
globalsearch/lastfmsearchprovider.cpp
internet/fixlastfm.cpp
internet/lastfmservice.cpp
internet/lastfmsettingspage.cpp
@ -672,6 +673,7 @@ if(HAVE_LIBLASTFM)
)
list(APPEND HEADERS
covers/lastfmcoverprovider.h
globalsearch/lastfmsearchprovider.h
internet/lastfmservice.h
internet/lastfmsettingspage.h
internet/lastfmstationdialog.h

View File

@ -91,6 +91,10 @@ void GlobalSearchItemDelegate::paint(QPainter* p,
case SearchProvider::Result::Type_Track:
break;
case SearchProvider::Result::Type_Stream:
count = QString::fromUtf8("");
break;
case SearchProvider::Result::Type_Album:
if (result.album_size_ <= 0)
count = "-";
@ -126,7 +130,8 @@ void GlobalSearchItemDelegate::paint(QPainter* p,
// The text we draw depends on the type of result.
switch (result.type_) {
case SearchProvider::Result::Type_Track: {
case SearchProvider::Result::Type_Track:
case SearchProvider::Result::Type_Stream: {
// Title
line_1 += m.title() + " ";

View File

@ -50,6 +50,7 @@ bool GlobalSearchSortModel::lessThan(const QModelIndex& left, const QModelIndex&
// Then compare title, artist and album
switch (r1.type_) {
case SearchProvider::Result::Type_Track:
case SearchProvider::Result::Type_Stream:
CompareString(title);
// fallthrough
case SearchProvider::Result::Type_Album:

View File

@ -112,8 +112,12 @@ GlobalSearchWidget::~GlobalSearchWidget() {
void GlobalSearchWidget::Init(GlobalSearch* engine) {
engine_ = engine;
// These have to be queued connections because they may get emitted before
// our call to Search() (or whatever) returns and we add the ID to the map.
connect(engine_, SIGNAL(ResultsAvailable(int,SearchProvider::ResultList)),
SLOT(AddResults(int,SearchProvider::ResultList)));
SLOT(AddResults(int,SearchProvider::ResultList)),
Qt::QueuedConnection);
connect(engine_, SIGNAL(SearchFinished(int)), SLOT(SearchFinished(int)),
Qt::QueuedConnection);
connect(engine_, SIGNAL(ArtLoaded(int,QPixmap)), SLOT(ArtLoaded(int,QPixmap)),
@ -555,6 +559,10 @@ GlobalSearchWidget::CombineAction GlobalSearchWidget::CanCombineResults(
if (StringsDiffer(album) || StringsDiffer(artist))
return CannotCombine;
break;
case SearchProvider::Result::Type_Stream:
if (StringsDiffer(url().toString))
return CannotCombine;
break;
}
#undef StringsDiffer

View File

@ -0,0 +1,148 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "lastfmsearchprovider.h"
#include "core/logging.h"
#include "internet/lastfmservice.h"
#include "playlist/songmimedata.h"
const int LastFMSearchProvider::kResultLimit = 6;
LastFMSearchProvider::LastFMSearchProvider(LastFMService* service, QObject* parent)
: BlockingSearchProvider(parent),
service_(service) {
Init("Last.fm", "lastfm", QIcon(":last.fm/as.png"), false, true);
icon_ = ScaleAndPad(QImage(":last.fm/as.png"));
connect(service, SIGNAL(SavedItemsChanged()), SLOT(RecreateItems()));
RecreateItems();
}
SearchProvider::ResultList LastFMSearchProvider::Search(int id, const QString& query) {
ResultList ret;
const QStringList tokens = TokenizeQuery(query);
QMutexLocker l(&items_mutex_);
foreach (const Item& item, items_) {
Result result(this);
result.type_ = Result::Type_Stream;
result.match_quality_ = Result::Quality_None;
foreach (const QString& token, tokens) {
if (item.keyword_.startsWith(token, Qt::CaseInsensitive)) {
result.match_quality_ = Result::Quality_AtStart;
result.metadata_ = item.metadata_;
break; // Next item
}
Result::MatchQuality quality = MatchQuality(tokens, item.metadata_.title());
if (quality == Result::Quality_None)
continue;
result.match_quality_ = qMin(quality, result.match_quality_);
result.metadata_ = item.metadata_;
}
if (result.match_quality_ != Result::Quality_None) {
ret << result;
}
if (ret.count() >= kResultLimit)
break;
}
return ret;
}
void LastFMSearchProvider::LoadArtAsync(int id, const Result& result) {
// TODO: Maybe we should try to get user pictures for friends?
emit ArtLoaded(id, icon_);
}
void LastFMSearchProvider::LoadTracksAsync(int id, const Result& result) {
Song metadata = result.metadata_;
metadata.set_filetype(Song::Type_Stream);
SongMimeData* mime_data = new SongMimeData;
mime_data->songs = SongList() << metadata;
emit TracksLoaded(id, mime_data);
}
void LastFMSearchProvider::RecreateItems() {
QList<Item> items;
Item item;
item.keyword_ = "recommended";
item.metadata_.set_title(tr("My Last.fm Recommended Radio"));
item.metadata_.set_url(QUrl("lastfm://user/USERNAME/recommended"));
items << item;
item.keyword_ = "radio";
item.metadata_.set_title(tr("My Last.fm Library"));
item.metadata_.set_url(QUrl("lastfm://user/USERNAME/library"));
items << item;
item.keyword_ = "mix";
item.metadata_.set_title(tr("My Last.fm Mix Radio"));
item.metadata_.set_url(QUrl("lastfm://user/USERNAME/mix"));
items << item;
item.keyword_ = "neighborhood";
item.metadata_.set_title(tr("My Last.fm Neighborhood"));
item.metadata_.set_url(QUrl("lastfm://user/USERNAME/neighbours"));
items << item;
const QStringList artists = service_->SavedArtistRadioNames();
const QStringList tags = service_->SavedTagRadioNames();
const QStringList friends = service_->FriendNames();
foreach (const QString& name, artists) {
item.keyword_ = name;
item.metadata_.set_title(tr(LastFMService::kTitleArtist).arg(name));
item.metadata_.set_url(QUrl(QString(LastFMService::kUrlArtist).arg(name)));
items << item;
}
foreach (const QString& name, tags) {
item.keyword_ = name;
item.metadata_.set_title(tr(LastFMService::kTitleTag).arg(name));
item.metadata_.set_url(QUrl(QString(LastFMService::kUrlTag).arg(name)));
items << item;
}
foreach (const QString& name, friends) {
item.keyword_ = name;
item.metadata_.set_title(tr("Last.fm Radio Station - %1").arg(name));
item.metadata_.set_url(QUrl("lastfm://user/" + name + "/library"));
items << item;
item.metadata_.set_title(tr("Last.fm Mix Radio - %1").arg(name));
item.metadata_.set_url(QUrl("lastfm://user/" + name + "/mix"));
items << item;
item.metadata_.set_title(tr("Last.fm Neighbor Radio - %1").arg(name));
item.metadata_.set_url(QUrl("lastfm://user/" + name + "/neighbours"));
items << item;
}
QMutexLocker l(&items_mutex_);
items_ = items;
}

View File

@ -0,0 +1,56 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LASTFMSEARCHPROVIDER_H
#define LASTFMSEARCHPROVIDER_H
#include "searchprovider.h"
class LastFMService;
class LastFMSearchProvider : public BlockingSearchProvider {
Q_OBJECT
public:
LastFMSearchProvider(LastFMService* service, QObject* parent);
static const int kResultLimit;
void LoadArtAsync(int id, const Result& result);
void LoadTracksAsync(int id, const Result& result);
protected:
ResultList Search(int id, const QString& query);
private slots:
void RecreateItems();
private:
LastFMService* service_;
QImage icon_;
struct Item {
QString keyword_;
Song metadata_;
};
QMutex items_mutex_;
QList<Item> items_;
};
#endif // LASTFMSEARCHPROVIDER_H

View File

@ -136,7 +136,7 @@ void LibrarySearchProvider::LoadTracksAsync(int id, const Result& result) {
ret << result.metadata_;
break;
case Result::Type_Album: {
case Result::Type_Album: {
// Find all the songs in this album.
LibraryQuery query;
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
@ -156,6 +156,9 @@ void LibrarySearchProvider::LoadTracksAsync(int id, const Result& result) {
ret << song;
}
}
default:
break;
}
SortSongs(&ret);

View File

@ -42,6 +42,7 @@ public:
// The order of types here is the order they'll appear in the UI.
enum Type {
Type_Track = 0,
Type_Stream,
Type_Album
};
@ -113,7 +114,7 @@ protected:
// Sorts a list of songs by disc, then by track.
static void SortSongs(SongList* list);
// Subclasses must call this from their constructor
// Subclasses must call this from their constructors.
void Init(const QString& name, const QString& id, const QIcon& icon,
bool delay_searches, bool serialised_art);

View File

@ -166,6 +166,9 @@ void SpotifySearchProvider::LoadTracksAsync(int id, const Result& result) {
s->AlbumBrowse(uri);
break;
}
default:
break;
}
}

View File

@ -59,6 +59,7 @@ QSize TooltipResultWidget::CalculateSizeHint() const {
switch (result_.type_) {
case SearchProvider::Result::Type_Track:
case SearchProvider::Result::Type_Stream:
break;
case SearchProvider::Result::Type_Album:
@ -119,6 +120,7 @@ void TooltipResultWidget::paintEvent(QPaintEvent*) {
switch (result_.type_) {
case SearchProvider::Result::Type_Track:
case SearchProvider::Result::Type_Stream:
break;
case SearchProvider::Result::Type_Album:

View File

@ -20,6 +20,8 @@
#include "lastfmurlhandler.h"
#include "internetmodel.h"
#include "internetplaylistitem.h"
#include "globalsearch/globalsearch.h"
#include "globalsearch/lastfmsearchprovider.h"
#include "core/logging.h"
#include "core/player.h"
#include "core/song.h"
@ -79,6 +81,7 @@ LastFMService::LastFMService(InternetModel* parent)
initial_tune_(false),
tune_task_id_(0),
scrobbling_enabled_(false),
root_item_(NULL),
artist_list_(NULL),
tag_list_(NULL),
custom_list_(NULL),
@ -111,6 +114,8 @@ LastFMService::LastFMService(InternetModel* parent)
model()->player()->RegisterUrlHandler(url_handler_);
model()->cover_providers()->AddProvider(new LastFmCoverProvider(this));
model()->global_search()->AddProvider(new LastFMSearchProvider(this, this));
}
LastFMService::~LastFMService() {
@ -126,8 +131,8 @@ void LastFMService::ReloadSettings() {
buttons_visible_ = settings.value("ShowLoveBanButtons", true).toBool();
scrobble_button_visible_ = settings.value("ShowScrobbleButton", true).toBool();
friend_names_ = settings.value("FriendNames").toStringList();
last_refreshed_friends_ = settings.value("LastRefreshedFriends").toDateTime();
friend_names_ = settings.value("FriendNames").toStringList();
//avoid emitting signal if it's not changed
if(scrobbling_enabled_old != scrobbling_enabled_)
@ -151,9 +156,9 @@ bool LastFMService::IsSubscriber() const {
}
QStandardItem* LastFMService::CreateRootItem() {
QStandardItem* item = new QStandardItem(QIcon(":last.fm/as.png"), kServiceName);
item->setData(true, InternetModel::Role_CanLazyLoad);
return item;
root_item_ = new QStandardItem(QIcon(":last.fm/as.png"), kServiceName);
root_item_->setData(true, InternetModel::Role_CanLazyLoad);
return root_item_;
}
void LastFMService::LazyPopulate(QStandardItem* parent) {
@ -281,6 +286,7 @@ void LastFMService::SignOut() {
QSettings settings;
settings.beginGroup(kSettingsGroup);
settings.setValue("Username", QString());
settings.setValue("Session", QString());
settings.setValue("FriendNames", friend_names_);
@ -589,13 +595,55 @@ bool LastFMService::IsFriendsListStale() const {
kFriendsCacheDurationSecs;
}
QStringList LastFMService::FriendNames() {
// Update the list for next time, in the main thread.
if (IsFriendsListStale())
metaObject()->invokeMethod(this, "RefreshFriends", Qt::QueuedConnection);
QSettings s;
s.beginGroup(LastFMService::kSettingsGroup);
return s.value("FriendNames").toStringList();
}
static QStringList SavedArtistOrTagRadioNames(const QString& name) {
QStringList ret;
QSettings s;
s.beginGroup(LastFMService::kSettingsGroup);
int count = s.beginReadArray(name);
for (int i=0 ; i<count ; ++i) {
ret << s.value("key").toString();
}
s.endArray();
return ret;
}
QStringList LastFMService::SavedArtistRadioNames() const {
return SavedArtistOrTagRadioNames("artists");
}
QStringList LastFMService::SavedTagRadioNames() const {
return SavedArtistOrTagRadioNames("tags");
}
void LastFMService::RefreshFriends() {
RefreshFriends(false);
}
void LastFMService::ForceRefreshFriends() {
RefreshFriends(true);
}
void LastFMService::RefreshFriends(bool force) {
if (!friends_list_ || !IsAuthenticated())
if (!IsAuthenticated()) {
return;
}
if (!friends_list_) {
root_item_->setData(false, InternetModel::Role_CanLazyLoad);
LazyPopulate(root_item_);
}
if (!force && !IsFriendsListStale()) {
PopulateFriendsList();
@ -635,6 +683,7 @@ void LastFMService::RefreshFriendsFinished() {
}
last_refreshed_friends_ = QDateTime::currentDateTime();
friend_names_ = QStringList();
foreach (const lastfm::User& f, friends) {
friend_names_ << f.name();
@ -646,6 +695,8 @@ void LastFMService::RefreshFriendsFinished() {
s.setValue("LastRefreshedFriends", last_refreshed_friends_);
PopulateFriendsList();
emit SavedItemsChanged();
}
void LastFMService::PopulateFriendsList() {
@ -756,6 +807,8 @@ void LastFMService::AddArtistOrTag(const QString& name,
emit AddItemToPlaylist(item->index(), AddMode_Append);
SaveList(name, list);
emit SavedItemsChanged();
}
void LastFMService::SaveList(const QString& name, QStandardItem* list) const {
@ -774,35 +827,30 @@ void LastFMService::RestoreList(const QString& name,
const QString& url_pattern,
const QString& title_pattern,
const QIcon& icon, QStandardItem* parent) {
QSettings settings;
settings.beginGroup(kSettingsGroup);
if (parent->hasChildren())
parent->removeRows(0, parent->rowCount());
int count = settings.beginReadArray(name);
for (int i=0 ; i<count ; ++i) {
settings.setArrayIndex(i);
QString content = settings.value("key").toString();
const QStringList keys = SavedArtistOrTagRadioNames(name);
foreach (const QString& key, keys) {
QString url;
if (name == "custom" && content.startsWith("lastfm://")) {
url = content;
if (name == "custom" && key.startsWith("lastfm://")) {
url = key;
} else if (name == "custom") {
url = url_pattern.arg(QString(content.toUtf8().toBase64()));
url = url_pattern.arg(QString(key.toUtf8().toBase64()));
} else {
url = url_pattern.arg(content);
url = url_pattern.arg(key);
}
Song song;
song.set_url(QUrl(url));
song.set_title(title_pattern.arg(content));
song.set_title(title_pattern.arg(key));
QStandardItem* item = new QStandardItem(icon, content);
QStandardItem* item = new QStandardItem(icon, key);
item->setData(QVariant::fromValue(song), InternetModel::Role_SongMetadata);
item->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour);
parent->appendRow(item);
}
settings.endArray();
}
void LastFMService::Remove() {

View File

@ -113,6 +113,11 @@ class LastFMService : public InternetService {
bool IsFriendsListStale() const;
// Thread safe
QStringList FriendNames();
QStringList SavedArtistRadioNames() const;
QStringList SavedTagRadioNames() const;
public slots:
void NowPlaying(const Song& song);
void Scrobble();
@ -130,6 +135,8 @@ class LastFMService : public InternetService {
void UpdatedSubscriberStatus(bool is_subscriber);
void ScrobbledRadioStream();
void SavedItemsChanged();
protected:
QModelIndex GetCurrentIndex();
@ -146,6 +153,7 @@ class LastFMService : public InternetService {
void AddTagRadio();
void AddCustomRadio();
void ForceRefreshFriends();
void RefreshFriends();
void Remove();
// Radio tuner.
@ -208,6 +216,7 @@ class LastFMService : public InternetService {
bool buttons_visible_;
bool scrobble_button_visible_;
QStandardItem* root_item_;
QStandardItem* artist_list_;
QStandardItem* tag_list_;
QStandardItem* custom_list_;

View File

@ -220,6 +220,7 @@ int main(int argc, char *argv[]) {
qRegisterMetaTypeStreamOperators<QMap<int, int> >("ColumnAlignmentMap");
qRegisterMetaType<QNetworkCookie>("QNetworkCookie");
qRegisterMetaType<QList<QNetworkCookie> >("QList<QNetworkCookie>");
qRegisterMetaType<SearchProvider::ResultList>("SearchProvider::ResultList");
qRegisterMetaType<GstBuffer*>("GstBuffer*");
qRegisterMetaType<GstElement*>("GstElement*");