Add a SomaFM search provider
This commit is contained in:
parent
931efb1f70
commit
dac6c1bf09
|
@ -130,6 +130,7 @@ set(SOURCES
|
|||
globalsearch/librarysearchprovider.cpp
|
||||
globalsearch/searchprovider.cpp
|
||||
globalsearch/simplesearchprovider.cpp
|
||||
globalsearch/somafmsearchprovider.cpp
|
||||
globalsearch/tooltipactionwidget.cpp
|
||||
globalsearch/tooltipresultwidget.cpp
|
||||
|
||||
|
@ -377,6 +378,7 @@ set(HEADERS
|
|||
globalsearch/groovesharksearchprovider.h
|
||||
globalsearch/searchprovider.h
|
||||
globalsearch/simplesearchprovider.h
|
||||
globalsearch/somafmsearchprovider.h
|
||||
globalsearch/tooltipactionwidget.h
|
||||
globalsearch/tooltipresultwidget.h
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2011, 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 "somafmsearchprovider.h"
|
||||
#include "internet/somafmservice.h"
|
||||
|
||||
SomaFMSearchProvider::SomaFMSearchProvider(SomaFMService* service, QObject* parent)
|
||||
: SimpleSearchProvider(parent),
|
||||
service_(service)
|
||||
{
|
||||
Init("SomaFM", "somafm", QIcon(":/providers/somafm.png"));
|
||||
set_result_limit(3);
|
||||
icon_ = ScaleAndPad(QImage(":/providers/somafm.png"));
|
||||
|
||||
connect(service, SIGNAL(StreamsChanged()), SLOT(MaybeRecreateItems()));
|
||||
}
|
||||
|
||||
void SomaFMSearchProvider::LoadArtAsync(int id, const Result& result) {
|
||||
emit ArtLoaded(id, icon_);
|
||||
}
|
||||
|
||||
void SomaFMSearchProvider::RecreateItems() {
|
||||
QList<Item> items;
|
||||
|
||||
foreach (const SomaFMService::Stream& stream, service_->Streams()) {
|
||||
Item item;
|
||||
item.metadata_ = stream.ToSong();
|
||||
item.keyword_ = stream.title_;
|
||||
items << item;
|
||||
}
|
||||
|
||||
SetItems(items);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/* This file is part of Clementine.
|
||||
Copyright 2011, 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 SOMAFMSEARCHPROVIDER_H
|
||||
#define SOMAFMSEARCHPROVIDER_H
|
||||
|
||||
#include "simplesearchprovider.h"
|
||||
|
||||
class SomaFMService;
|
||||
|
||||
class SomaFMSearchProvider : public SimpleSearchProvider {
|
||||
public:
|
||||
SomaFMSearchProvider(SomaFMService* service, QObject* parent);
|
||||
|
||||
void LoadArtAsync(int id, const Result& result);
|
||||
|
||||
protected:
|
||||
void RecreateItems();
|
||||
|
||||
private:
|
||||
SomaFMService* service_;
|
||||
QImage icon_;
|
||||
};
|
||||
|
||||
#endif // SOMAFMSEARCHPROVIDER_H
|
|
@ -18,10 +18,13 @@
|
|||
#include "somafmservice.h"
|
||||
#include "somafmurlhandler.h"
|
||||
#include "internetmodel.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/player.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "globalsearch/globalsearch.h"
|
||||
#include "globalsearch/somafmsearchprovider.h"
|
||||
#include "ui/iconloader.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
@ -33,18 +36,24 @@
|
|||
#include <QtDebug>
|
||||
|
||||
const char* SomaFMService::kServiceName = "SomaFM";
|
||||
const char* SomaFMService::kSettingsGroup = "SomaFM";
|
||||
const char* SomaFMService::kChannelListUrl = "http://somafm.com/channels.xml";
|
||||
const char* SomaFMService::kHomepage = "http://somafm.com";
|
||||
const int SomaFMService::kStreamsCacheDurationSecs =
|
||||
60 * 60 * 24 * 28; // 4 weeks
|
||||
|
||||
SomaFMService::SomaFMService(InternetModel* parent)
|
||||
: InternetService(kServiceName, parent, parent),
|
||||
url_handler_(new SomaFMUrlHandler(this, this)),
|
||||
root_(NULL),
|
||||
context_menu_(NULL),
|
||||
get_channels_task_id_(0),
|
||||
network_(new NetworkAccessManager(this))
|
||||
network_(new NetworkAccessManager(this)),
|
||||
streams_(kSettingsGroup, "streams", kStreamsCacheDurationSecs)
|
||||
{
|
||||
model()->player()->RegisterUrlHandler(url_handler_);
|
||||
model()->global_search()->AddProvider(new SomaFMSearchProvider(this, this));
|
||||
|
||||
ReloadSettings();
|
||||
}
|
||||
|
||||
SomaFMService::~SomaFMService() {
|
||||
|
@ -60,7 +69,7 @@ QStandardItem* SomaFMService::CreateRootItem() {
|
|||
void SomaFMService::LazyPopulate(QStandardItem* item) {
|
||||
switch (item->data(InternetModel::Role_Type).toInt()) {
|
||||
case InternetModel::Type_Service:
|
||||
RefreshChannels();
|
||||
RefreshStreams();
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -73,25 +82,25 @@ void SomaFMService::ShowContextMenu(const QModelIndex& index, const QPoint& glob
|
|||
context_menu_ = new QMenu;
|
||||
context_menu_->addActions(GetPlaylistActions());
|
||||
context_menu_->addAction(IconLoader::Load("download"), tr("Open somafm.com in browser"), this, SLOT(Homepage()));
|
||||
context_menu_->addAction(IconLoader::Load("view-refresh"), tr("Refresh channels"), this, SLOT(RefreshChannels()));
|
||||
context_menu_->addAction(IconLoader::Load("view-refresh"), tr("Refresh channels"), this, SLOT(RefreshStreams()));
|
||||
}
|
||||
|
||||
context_item_ = model()->itemFromIndex(index);
|
||||
context_menu_->popup(global_pos);
|
||||
}
|
||||
|
||||
void SomaFMService::RefreshChannels() {
|
||||
void SomaFMService::ForceRefreshStreams() {
|
||||
QNetworkReply* reply = network_->get(QNetworkRequest(QUrl(kChannelListUrl)));
|
||||
connect(reply, SIGNAL(finished()), SLOT(RefreshChannelsFinished()));
|
||||
int task_id = model()->task_manager()->StartTask(tr("Getting channels"));
|
||||
|
||||
if (!get_channels_task_id_)
|
||||
get_channels_task_id_ = model()->task_manager()->StartTask(tr("Getting channels"));
|
||||
NewClosure(reply, SIGNAL(finished()),
|
||||
this, SLOT(RefreshStreamsFinished(QNetworkReply*,int)),
|
||||
reply, task_id);
|
||||
}
|
||||
|
||||
void SomaFMService::RefreshChannelsFinished() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
model()->task_manager()->SetTaskFinished(get_channels_task_id_);
|
||||
get_channels_task_id_ = 0;
|
||||
void SomaFMService::RefreshStreamsFinished(QNetworkReply* reply, int task_id) {
|
||||
model()->task_manager()->SetTaskFinished(task_id);
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
// TODO: Error handling
|
||||
|
@ -99,8 +108,7 @@ void SomaFMService::RefreshChannelsFinished() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (root_->hasChildren())
|
||||
root_->removeRows(0, root_->rowCount());
|
||||
StreamList list;
|
||||
|
||||
QXmlStreamReader reader(reply);
|
||||
while (!reader.atEnd()) {
|
||||
|
@ -108,39 +116,36 @@ void SomaFMService::RefreshChannelsFinished() {
|
|||
|
||||
if (reader.tokenType() == QXmlStreamReader::StartElement &&
|
||||
reader.name() == "channel") {
|
||||
ReadChannel(reader);
|
||||
ReadChannel(reader, &list);
|
||||
}
|
||||
}
|
||||
|
||||
streams_.Update(list);
|
||||
PopulateStreams();
|
||||
emit StreamsChanged();
|
||||
}
|
||||
|
||||
void SomaFMService::ReadChannel(QXmlStreamReader& reader) {
|
||||
Song song;
|
||||
void SomaFMService::ReadChannel(QXmlStreamReader& reader, StreamList* ret) {
|
||||
Stream stream;
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
switch (reader.readNext()) {
|
||||
case QXmlStreamReader::EndElement:
|
||||
if (!song.url().isEmpty()) {
|
||||
QStandardItem* item = new QStandardItem(QIcon(":last.fm/icon_radio.png"), QString());
|
||||
item->setText(song.title());
|
||||
|
||||
song.set_title("SomaFM " + song.title());
|
||||
item->setData(QVariant::fromValue(song), InternetModel::Role_SongMetadata);
|
||||
item->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour);
|
||||
|
||||
root_->appendRow(item);
|
||||
if (!stream.url_.isEmpty()) {
|
||||
ret->append(stream);
|
||||
}
|
||||
return;
|
||||
|
||||
case QXmlStreamReader::StartElement:
|
||||
if (reader.name() == "title") {
|
||||
song.set_title(reader.readElementText());
|
||||
stream.title_ = reader.readElementText();
|
||||
} else if (reader.name() == "dj") {
|
||||
song.set_artist(reader.readElementText());
|
||||
stream.dj_ = reader.readElementText();
|
||||
} else if (reader.name() == "fastpls" && reader.attributes().value("format") == "mp3") {
|
||||
QUrl url(reader.readElementText());
|
||||
url.setScheme("somafm");
|
||||
|
||||
song.set_url(url);
|
||||
stream.url_ = url;
|
||||
} else {
|
||||
ConsumeElement(reader);
|
||||
}
|
||||
|
@ -152,6 +157,15 @@ void SomaFMService::ReadChannel(QXmlStreamReader& reader) {
|
|||
}
|
||||
}
|
||||
|
||||
Song SomaFMService::Stream::ToSong() const {
|
||||
Song ret;
|
||||
ret.set_valid(true);
|
||||
ret.set_title("SomaFM " + title_);
|
||||
ret.set_artist(dj_);
|
||||
ret.set_url(url_);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SomaFMService::ConsumeElement(QXmlStreamReader& reader) {
|
||||
int level = 1;
|
||||
while (!reader.atEnd()) {
|
||||
|
@ -177,3 +191,50 @@ QModelIndex SomaFMService::GetCurrentIndex() {
|
|||
PlaylistItem::Options SomaFMService::playlistitem_options() const {
|
||||
return PlaylistItem::PauseDisabled;
|
||||
}
|
||||
|
||||
SomaFMService::StreamList SomaFMService::Streams() {
|
||||
if (IsStreamListStale()) {
|
||||
metaObject()->invokeMethod(this, "ForceRefreshStreams", Qt::QueuedConnection);
|
||||
}
|
||||
return streams_;
|
||||
}
|
||||
|
||||
void SomaFMService::RefreshStreams() {
|
||||
if (IsStreamListStale()) {
|
||||
ForceRefreshStreams();
|
||||
return;
|
||||
}
|
||||
PopulateStreams();
|
||||
}
|
||||
|
||||
void SomaFMService::PopulateStreams() {
|
||||
if (root_->hasChildren())
|
||||
root_->removeRows(0, root_->rowCount());
|
||||
|
||||
foreach (const Stream& stream, streams_) {
|
||||
QStandardItem* item = new QStandardItem(QIcon(":last.fm/icon_radio.png"), QString());
|
||||
item->setText(stream.title_);
|
||||
item->setData(QVariant::fromValue(stream.ToSong()), InternetModel::Role_SongMetadata);
|
||||
item->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour);
|
||||
|
||||
root_->appendRow(item);
|
||||
}
|
||||
}
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const SomaFMService::Stream& stream) {
|
||||
out << stream.title_
|
||||
<< stream.dj_
|
||||
<< stream.url_;
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream& in, SomaFMService::Stream& stream) {
|
||||
in >> stream.title_
|
||||
>> stream.dj_
|
||||
>> stream.url_;
|
||||
return in;
|
||||
}
|
||||
|
||||
void SomaFMService::ReloadSettings() {
|
||||
streams_.Load();
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <QXmlStreamReader>
|
||||
|
||||
#include "internetservice.h"
|
||||
#include "core/cachedlist.h"
|
||||
|
||||
class SomaFMUrlHandler;
|
||||
|
||||
|
@ -30,7 +31,7 @@ class QMenu;
|
|||
class SomaFMService : public InternetService {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
public:
|
||||
SomaFMService(InternetModel* parent);
|
||||
~SomaFMService();
|
||||
|
||||
|
@ -38,42 +39,65 @@ class SomaFMService : public InternetService {
|
|||
Type_Stream = 2000,
|
||||
};
|
||||
|
||||
struct Stream {
|
||||
QString title_;
|
||||
QString dj_;
|
||||
QUrl url_;
|
||||
|
||||
Song ToSong() const;
|
||||
};
|
||||
typedef QList<Stream> StreamList;
|
||||
|
||||
static const char* kServiceName;
|
||||
static const char* kSettingsGroup;
|
||||
static const char* kChannelListUrl;
|
||||
static const char* kHomepage;
|
||||
static const int kStreamsCacheDurationSecs;
|
||||
|
||||
QStandardItem* CreateRootItem();
|
||||
void LazyPopulate(QStandardItem* item);
|
||||
|
||||
void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos);
|
||||
|
||||
PlaylistItem::Options playlistitem_options() const;
|
||||
|
||||
QNetworkAccessManager* network() const { return network_; }
|
||||
|
||||
protected:
|
||||
void ReloadSettings();
|
||||
|
||||
bool IsStreamListStale() const { return streams_.IsStale(); }
|
||||
StreamList Streams();
|
||||
|
||||
signals:
|
||||
void StreamsChanged();
|
||||
|
||||
protected:
|
||||
QModelIndex GetCurrentIndex();
|
||||
|
||||
private slots:
|
||||
void RefreshChannels();
|
||||
void RefreshChannelsFinished();
|
||||
private slots:
|
||||
void ForceRefreshStreams();
|
||||
void RefreshStreams();
|
||||
void RefreshStreamsFinished(QNetworkReply* reply, int task_id);
|
||||
|
||||
void Homepage();
|
||||
|
||||
private:
|
||||
void ReadChannel(QXmlStreamReader& reader);
|
||||
private:
|
||||
void ReadChannel(QXmlStreamReader& reader, StreamList* ret);
|
||||
void ConsumeElement(QXmlStreamReader& reader);
|
||||
void PopulateStreams();
|
||||
|
||||
private:
|
||||
private:
|
||||
SomaFMUrlHandler* url_handler_;
|
||||
|
||||
QStandardItem* root_;
|
||||
QMenu* context_menu_;
|
||||
QStandardItem* context_item_;
|
||||
|
||||
int get_channels_task_id_;
|
||||
|
||||
QNetworkAccessManager* network_;
|
||||
|
||||
CachedList<Stream> streams_;
|
||||
};
|
||||
|
||||
QDataStream& operator<<(QDataStream& out, const SomaFMService::Stream& stream);
|
||||
QDataStream& operator>>(QDataStream& in, SomaFMService::Stream& stream);
|
||||
Q_DECLARE_METATYPE(SomaFMService::Stream)
|
||||
|
||||
#endif // SOMAFMSERVICE_H
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
#include "globalsearch/globalsearch.h"
|
||||
#include "internet/digitallyimportedclient.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "internet/somafmservice.h"
|
||||
#include "library/directory.h"
|
||||
#include "playlist/playlist.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
|
@ -229,7 +230,9 @@ int main(int argc, char *argv[]) {
|
|||
qRegisterMetaType<SearchProvider::Result>("SearchProvider::Result");
|
||||
qRegisterMetaType<SearchProvider::ResultList>("SearchProvider::ResultList");
|
||||
qRegisterMetaType<DigitallyImportedClient::Channel>("DigitallyImportedClient::Channel");
|
||||
qRegisterMetaType<SomaFMService::Stream>("SomaFMService::Stream");
|
||||
qRegisterMetaTypeStreamOperators<DigitallyImportedClient::Channel>("DigitallyImportedClient::Channel");
|
||||
qRegisterMetaTypeStreamOperators<SomaFMService::Stream>("SomaFMService::Stream");
|
||||
|
||||
qRegisterMetaType<GstBuffer*>("GstBuffer*");
|
||||
qRegisterMetaType<GstElement*>("GstElement*");
|
||||
|
|
|
@ -1863,7 +1863,7 @@ msgstr ""
|
|||
msgid "Genre"
|
||||
msgstr ""
|
||||
|
||||
#: internet/somafmservice.cpp:88
|
||||
#: internet/somafmservice.cpp:94
|
||||
msgid "Getting channels"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2785,7 +2785,7 @@ msgstr ""
|
|||
msgid "Open magnatune.com in browser"
|
||||
msgstr ""
|
||||
|
||||
#: internet/somafmservice.cpp:75
|
||||
#: internet/somafmservice.cpp:84
|
||||
msgid "Open somafm.com in browser"
|
||||
msgstr ""
|
||||
|
||||
|
@ -3148,7 +3148,7 @@ msgstr ""
|
|||
msgid "Refresh catalogue"
|
||||
msgstr ""
|
||||
|
||||
#: internet/somafmservice.cpp:76
|
||||
#: internet/somafmservice.cpp:85
|
||||
msgid "Refresh channels"
|
||||
msgstr ""
|
||||
|
||||
|
|
Loading…
Reference in New Issue