Lazy load lyrics.xml

This commit is contained in:
craig.p.drummond
2013-03-16 07:45:59 +00:00
committed by craig.p.drummond
parent 674efaacff
commit b2fb415aed
15 changed files with 545 additions and 676 deletions

View File

@@ -160,8 +160,7 @@ SET( CANTATA_SRCS
lyrics/lyricspage.cpp
lyrics/lyricsettings.cpp
lyrics/ultimatelyricsprovider.cpp
lyrics/ultimatelyricsreader.cpp
lyrics/songinfoprovider.cpp
lyrics/ultimatelyrics.cpp
lyrics/lyricsdialog.cpp
network/networkaccessmanager.cpp
online/onlineservicespage.cpp
@@ -229,8 +228,8 @@ SET( CANTATA_MOC_HDRS
widgets/toolbar.h
lyrics/lyricspage.h
lyrics/lyricsettings.h
lyrics/ultimatelyrics.h
lyrics/ultimatelyricsprovider.h
lyrics/songinfoprovider.h
lyrics/lyricsdialog.h
network/networkaccessmanager.h
online/onlineservice.h

View File

@@ -961,7 +961,7 @@ void MainWindow::mpdConnectionStateChanged(bool connected)
emit playListInfo();
emit outputs();
if (CS_Init!=connectedState) {
loaded=loaded&TAB_STREAMS ? TAB_STREAMS : 0;
loaded=(loaded&TAB_STREAMS);
currentTabChanged(tabWidget->current_index());
}
connectedState=CS_Connected;
@@ -971,7 +971,7 @@ void MainWindow::mpdConnectionStateChanged(bool connected)
updateWindowTitle();
}
} else {
loaded=loaded&TAB_STREAMS ? TAB_STREAMS : 0;
loaded=(loaded&TAB_STREAMS);
libraryPage->clear();
albumsPage->clear();
folderPage->clear();
@@ -1101,7 +1101,7 @@ void MainWindow::showPreferencesDialog()
}
#endif
PreferencesDialog *pref=new PreferencesDialog(this, lyricsPage);
PreferencesDialog *pref=new PreferencesDialog(this);
controlConnectionsMenu(false);
connect(pref, SIGNAL(settingsSaved()), this, SLOT(updateSettings()));
connect(pref, SIGNAL(connectTo(const MPDConnectionDetails &)), this, SLOT(connectToMpd(const MPDConnectionDetails &)));
@@ -1270,7 +1270,6 @@ void MainWindow::readSettings()
#ifdef ENABLE_DEVICES_SUPPORT
StdActions::self()->deleteSongsAction->setVisible(Settings::self()->showDeleteAction());
#endif
lyricsPage->setEnabledProviders(Settings::self()->lyricProviders());
MPDParseUtils::setGroupSingle(Settings::self()->groupSingle());
MPDParseUtils::setGroupMultiple(Settings::self()->groupMultiple());
albumsPage->setView(Settings::self()->albumsView());

View File

@@ -34,7 +34,6 @@
#include "httpserversettings.h"
#endif
#include "lyricsettings.h"
#include "lyricspage.h"
#include "cachesettings.h"
#include "localize.h"
#include "mpdconnection.h"
@@ -55,7 +54,7 @@ int PreferencesDialog::instanceCount()
return iCount;
}
PreferencesDialog::PreferencesDialog(QWidget *parent, LyricsPage *lp)
PreferencesDialog::PreferencesDialog(QWidget *parent)
: Dialog(parent)
{
iCount++;
@@ -74,8 +73,7 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, LyricsPage *lp)
playback->load();
files->load();
interface->load();
const QList<UltimateLyricsProvider *> &lprov=lp->getProviders();
lyrics->Load(lprov);
lyrics->load();
widget->addPage(server, i18n("Connection"), Icons::libraryIcon, i18n("Connection Settings"));
widget->addPage(serverplayback, i18n("Output"), Icons::speakerIcon, i18n("Output Settings"));
widget->addPage(playback, i18n("Playback"), Icon("media-playback-start"), i18n("Playback Settings"));
@@ -134,7 +132,7 @@ void PreferencesDialog::writeSettings()
#if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND
audiocd->save();
#endif
Settings::self()->saveLyricProviders(lyrics->EnabledProviders());
lyrics->save();
Settings::self()->save();
emit settingsSaved();
}

View File

@@ -37,7 +37,6 @@ class PlaybackSettings;
class FileSettings;
class InterfaceSettings;
class LyricSettings;
class LyricsPage;
#ifdef TAGLIB_FOUND
class HttpServerSettings;
#endif
@@ -54,7 +53,7 @@ class PreferencesDialog : public Dialog
public:
static int instanceCount();
PreferencesDialog(QWidget *parent, LyricsPage *lp);
PreferencesDialog(QWidget *parent);
virtual ~PreferencesDialog();
private:

View File

@@ -22,86 +22,80 @@
*/
#include "lyricsettings.h"
//#include "songinfoview.h"
#include "ultimatelyricsprovider.h"
#include "ultimatelyrics.h"
#include "ui_lyricsettings.h"
#include "localize.h"
#include "icon.h"
#include "config.h"
#include "settings.h"
LyricSettings::LyricSettings(QWidget *parent)
: QWidget(parent),
ui_(new Ui_LyricSettings)
LyricSettings::LyricSettings(QWidget *p)
: QWidget(p)
{
ui_->setupUi(this);
connect(ui_->up, SIGNAL(clicked()), SLOT(MoveUp()));
connect(ui_->down, SIGNAL(clicked()), SLOT(MoveDown()));
connect(ui_->providers, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
SLOT(CurrentItemChanged(QListWidgetItem*)));
// connect(ui_->providers, SIGNAL(itemChanged(QListWidgetItem*)),
// SLOT(ItemChanged(QListWidgetItem*)));
ui_->up->setIcon(Icon("arrow-up"));
ui_->down->setIcon(Icon("arrow-down"));
setupUi(this);
connect(up, SIGNAL(clicked()), SLOT(moveUp()));
connect(down, SIGNAL(clicked()), SLOT(moveDown()));
connect(providers, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
SLOT(currentItemChanged(QListWidgetItem*)));
up->setIcon(Icon("arrow-up"));
down->setIcon(Icon("arrow-down"));
}
LyricSettings::~LyricSettings() {
delete ui_;
void LyricSettings::load()
{
const QList<UltimateLyricsProvider *> &lprov=UltimateLyrics::self()->getProviders();
providers->clear();
foreach (const UltimateLyricsProvider *provider, lprov) {
QListWidgetItem *item = new QListWidgetItem(providers);
QString name(provider->getName());
name.replace("(POLISH)", i18n("(Polish Translations)"));
name.replace("(PORTUGUESE)", i18n("(Portuguese Translations)"));
item->setText(name);
item->setData(Qt::UserRole, provider->getName());
item->setCheckState(provider->isEnabled() ? Qt::Checked : Qt::Unchecked);
}
}
void LyricSettings::Load(const QList<UltimateLyricsProvider*> &providers) {
ui_->providers->clear();
foreach (const UltimateLyricsProvider* provider, providers) {
QListWidgetItem* item = new QListWidgetItem(ui_->providers);
QString name(provider->name());
name.replace("(POLISH)", i18n("(Polish Translations)"));
name.replace("(PORTUGUESE)", i18n("(Portuguese Translations)"));
item->setText(name);
item->setData(Qt::UserRole, provider->name());
item->setCheckState(provider->is_enabled() ? Qt::Checked : Qt::Unchecked);
// item->setForeground(provider->is_enabled() ? palette().color(QPalette::Active, QPalette::Text)
// : palette().color(QPalette::Disabled, QPalette::Text));
}
void LyricSettings::save()
{
QStringList enabled;
for (int i=0 ; i<providers->count() ; ++i) {
const QListWidgetItem* item = providers->item(i);
if (Qt::Checked==item->checkState()) {
enabled << item->data(Qt::UserRole).toString();
}
}
UltimateLyrics::self()->setEnabled(enabled);
}
QStringList LyricSettings::EnabledProviders() {
QStringList providers;
for (int i=0 ; i<ui_->providers->count() ; ++i) {
const QListWidgetItem* item = ui_->providers->item(i);
if (item->checkState() == Qt::Checked)
providers << item->data(Qt::UserRole).toString();
}
return providers;
void LyricSettings::currentItemChanged(QListWidgetItem *item)
{
if (!item) {
up->setEnabled(false);
down->setEnabled(false);
} else {
const int row = providers->row(item);
up->setEnabled(row != 0);
down->setEnabled(row != providers->count() - 1);
}
}
void LyricSettings::CurrentItemChanged(QListWidgetItem* item) {
if (!item) {
ui_->up->setEnabled(false);
ui_->down->setEnabled(false);
} else {
const int row = ui_->providers->row(item);
ui_->up->setEnabled(row != 0);
ui_->down->setEnabled(row != ui_->providers->count() - 1);
}
void LyricSettings::moveUp()
{
move(-1);
}
void LyricSettings::MoveUp() {
Move(-1);
void LyricSettings::moveDown()
{
move(+1);
}
void LyricSettings::MoveDown() {
Move(+1);
void LyricSettings::move(int d)
{
const int row = providers->currentRow();
QListWidgetItem* item = providers->takeItem(row);
providers->insertItem(row + d, item);
providers->setCurrentRow(row + d);
}
void LyricSettings::Move(int d) {
const int row = ui_->providers->currentRow();
QListWidgetItem* item = ui_->providers->takeItem(row);
ui_->providers->insertItem(row + d, item);
ui_->providers->setCurrentRow(row + d);
}
// void LyricSettings::ItemChanged(QListWidgetItem* item) {
// const bool checked = item->checkState() == Qt::Checked;
// item->setForeground(checked ? palette().color(QPalette::Active, QPalette::Text)
// : palette().color(QPalette::Disabled, QPalette::Text));
// }

View File

@@ -27,34 +27,27 @@
#include <QWidget>
#include <QStringList>
#include <QList>
#include "ui_lyricsettings.h"
class Ui_LyricSettings;
class QListWidgetItem;
class UltimateLyricsProvider;
class LyricSettings : public QWidget
class LyricSettings : public QWidget, private Ui::LyricSettings
{
Q_OBJECT
public:
LyricSettings(QWidget *parent = 0);
~LyricSettings();
LyricSettings(QWidget *p);
virtual ~LyricSettings() { }
QStringList EnabledProviders();
public Q_SLOTS:
void Load(const QList<UltimateLyricsProvider*> &providers);
void load();
void save();
private Q_SLOTS:
void MoveUp();
void MoveDown();
void Move(int d);
void CurrentItemChanged(QListWidgetItem* item);
// void ItemChanged(QListWidgetItem* item);
private:
Ui_LyricSettings* ui_;
void moveUp();
void moveDown();
void move(int d);
void currentItemChanged(QListWidgetItem *item);
};
#endif // LYRICSETTINGS_H

View File

@@ -21,11 +21,10 @@
* Boston, MA 02110-1301, USA.
*/
#include "songinfoprovider.h"
#include "lyricspage.h"
#include "lyricsdialog.h"
#include "ultimatelyricsprovider.h"
#include "ultimatelyricsreader.h"
#include "ultimatelyrics.h"
#include "settings.h"
#include "squeezedtextlabel.h"
#include "utils.h"
@@ -62,32 +61,14 @@ static QString cacheFile(QString artist, QString title, bool createDir=false)
return QDir::toNativeSeparators(Utils::cacheDir(LyricsPage::constLyricsDir+artist+'/', createDir))+title+LyricsPage::constExtension;
}
typedef QList<UltimateLyricsProvider *> ProviderList;
bool CompareLyricProviders(const UltimateLyricsProvider* a, const UltimateLyricsProvider* b) {
return a->relevance() < b->relevance();
}
LyricsPage::LyricsPage(QWidget *p)
: QWidget(p)
// , reader(new UltimateLyricsReader(this))
, currentProvider(-1)
, currentRequest(0)
, mode(Mode_Display)
, job(0)
{
setupUi(this);
providers=UltimateLyricsReader().Parse(QString(":lyrics.xml"));
foreach (UltimateLyricsProvider* provider, providers) {
connect(provider, SIGNAL(InfoReady(int, const QString &)), SLOT(resultReady(int, const QString &)));
}
// Parse the ultimate lyrics xml file in the background
// QFuture<ProviderList> future = QtConcurrent::run(reader.data(), &UltimateLyricsReader::Parse,
// QString(":lyrics.xml"));
// QFutureWatcher<ProviderList> *watcher = new QFutureWatcher<ProviderList>(this);
// watcher->setFuture(future);
// connect(watcher, SIGNAL(finished()), SLOT(ultimateLyricsParsed()));
refreshAction = ActionCollection::get()->createAction("refreshlyrics", i18n("Refresh"), "view-refresh");
searchAction = ActionCollection::get()->createAction("searchlyrics", i18n("Search For Lyrics"), "edit-find");
editAction = ActionCollection::get()->createAction("editlyrics", i18n("Edit Lyrics"), Icons::editIcon);
@@ -101,6 +82,7 @@ LyricsPage::LyricsPage(QWidget *p)
connect(saveAction, SIGNAL(triggered()), SLOT(save()));
connect(cancelAction, SIGNAL(triggered()), SLOT(cancel()));
connect(delAction, SIGNAL(triggered()), SLOT(del()));
connect(UltimateLyrics::self(), SIGNAL(lyricsReady(int, const QString &)), SLOT(lyricsReady(int, const QString &)));
Icon::init(refreshBtn);
Icon::init(searchBtn);
Icon::init(editBtn);
@@ -120,9 +102,7 @@ LyricsPage::LyricsPage(QWidget *p)
LyricsPage::~LyricsPage()
{
foreach (UltimateLyricsProvider* provider, providers) {
delete provider;
}
UltimateLyrics::self()->release();
}
void LyricsPage::saveSettings()
@@ -130,25 +110,6 @@ void LyricsPage::saveSettings()
Settings::self()->saveLyricsZoom(text->zoom());
}
void LyricsPage::setEnabledProviders(const QStringList &providerList)
{
foreach (UltimateLyricsProvider* provider, providers) {
provider->set_enabled(false);
provider->set_relevance(0xFFFF);
}
int relevance=0;
foreach (const QString &p, providerList) {
UltimateLyricsProvider *provider=providerByName(p);
if (provider) {
provider->set_enabled(true);
provider->set_relevance(relevance++);
}
}
qSort(providers.begin(), providers.end(), CompareLyricProviders);
}
void LyricsPage::update()
{
QString mpdName=mpdFileName();
@@ -367,7 +328,7 @@ void LyricsPage::downloadFinished()
getLyrics();
}
void LyricsPage::resultReady(int id, const QString &lyrics)
void LyricsPage::lyricsReady(int id, const QString &lyrics)
{
if (id != currentRequest)
return;
@@ -412,38 +373,21 @@ QString LyricsPage::cacheFileName() const
return currentSong.artist.isEmpty() || currentSong.title.isEmpty() ? QString() : cacheFile(currentSong.artist, currentSong.title);
}
UltimateLyricsProvider* LyricsPage::providerByName(const QString &name) const
{
foreach (UltimateLyricsProvider* provider, providers) {
if (provider->name() == name) {
return provider;
}
}
return 0;
}
void LyricsPage::getLyrics()
{
for(;;) {
currentProvider++;
if (currentProvider<providers.count()) {
UltimateLyricsProvider *prov=providers.at(currentProvider++);
if (prov && prov->is_enabled()) {
text->setText(i18nc("<title> by <artist>\nFetching lyrics via <url>", "%1 by %2\nFetching lyrics via %3")
.arg(currentSong.title).arg(currentSong.artist, prov->name()));
prov->FetchInfo(currentRequest, currentSong);
return;
}
} else {
text->setText(i18nc("<title> by <artist>\nFailed\n", "%1 by %2\nFailed to fetch lyrics").arg(currentSong.title).arg(currentSong.artist));
currentProvider=-1;
// Set lyrics file anyway - so that editing is enabled!
lyricsFile=Settings::self()->storeLyricsInMpdDir()
? Utils::changeExtension(MPDConnection::self()->getDetails().dir+currentSong.file, constExtension)
: cacheFile(currentSong.artist, currentSong.title);
setMode(Mode_Display);
return;
}
UltimateLyricsProvider *prov=UltimateLyrics::self()->getNext(currentProvider);
if (prov) {
text->setText(i18nc("<title> by <artist>\nFetching lyrics via <url>", "%1 by %2\nFetching lyrics via %3")
.arg(currentSong.title).arg(currentSong.artist, prov->getName()));
prov->fetchInfo(currentRequest, currentSong);
} else {
text->setText(i18nc("<title> by <artist>\nFailed\n", "%1 by %2\nFailed to fetch lyrics").arg(currentSong.title).arg(currentSong.artist));
currentProvider=-1;
// Set lyrics file anyway - so that editing is enabled!
lyricsFile=Settings::self()->storeLyricsInMpdDir()
? Utils::changeExtension(MPDConnection::self()->getDetails().dir+currentSong.file, constExtension)
: cacheFile(currentSong.artist, currentSong.title);
setMode(Mode_Display);
}
}
@@ -482,18 +426,3 @@ bool LyricsPage::setLyricsFromFile(const QString &filePath) const
return success;
}
// void LyricsPage::ultimateLyricsParsed()
// {
// QFutureWatcher<ProviderList>* watcher = static_cast<QFutureWatcher<ProviderList>*>(sender());
// QStringList names;
//
// foreach (UltimateLyricsProvider* provider, watcher->result()) {
// providers.append(provider);
// connect(provider, SIGNAL(InfoReady(int, const QString &)), SLOT(resultReady(int, const QString &)));
// }
//
// watcher->deleteLater();
// reader.reset();
// emit providersUpdated();
// }

View File

@@ -25,13 +25,11 @@
#define LYRICSPAGE_H
#include <QWidget>
// #include <QScopedPointer>
#include "song.h"
#include "ui_lyricspage.h"
#include "textbrowser.h"
class UltimateLyricsProvider;
// class UltimateLyricsReader;
class UltimateLyricsProvider;
class QImage;
class Action;
@@ -55,9 +53,7 @@ public:
~LyricsPage();
void saveSettings();
void setEnabledProviders(const QStringList &providerList);
void update(const Song &song, bool force=false);
const QList<UltimateLyricsProvider *> & getProviders() { return providers; }
void setBgndImageEnabled(bool e) { text->enableImage(e); }
bool bgndImageEnabled() { return text->imageEnabled(); }
@@ -69,7 +65,7 @@ public Q_SLOTS:
protected Q_SLOTS:
void downloadFinished();
void resultReady(int id, const QString &lyrics);
void lyricsReady(int, const QString &lyrics);
void update();
void search();
void edit();
@@ -80,7 +76,6 @@ protected Q_SLOTS:
private:
QString mpdFileName() const;
QString cacheFileName() const;
UltimateLyricsProvider * providerByName(const QString &name) const;
void getLyrics();
void setMode(Mode m);
bool saveFile(const QString &fileName);
@@ -95,12 +90,7 @@ private:
*/
bool setLyricsFromFile(const QString &filePath) const;
// private Q_SLOTS:
// void ultimateLyricsParsed();
private:
// QScopedPointer<UltimateLyricsReader> reader;
QList<UltimateLyricsProvider *> providers;
int currentProvider;
int currentRequest;
Song currentSong;

View File

@@ -1,33 +0,0 @@
/*
* Cantata
*
* Copyright (c) 2011-2013 Craig Drummond <craig.p.drummond@gmail.com>
*
*/
/* 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 "songinfoprovider.h"
SongInfoProvider::SongInfoProvider()
: enabled_(true)
{
}
QString SongInfoProvider::name() const {
return metaObject()->className();
}

202
lyrics/ultimatelyrics.cpp Normal file
View File

@@ -0,0 +1,202 @@
/*
* Cantata
*
* Copyright (c) 2011-2013 Craig Drummond <craig.p.drummond@gmail.com>
*
*/
/* 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 "ultimatelyrics.h"
#include "ultimatelyricsprovider.h"
#include "settings.h"
#include <QCoreApplication>
#include <QFile>
#include <QXmlStreamReader>
#ifdef ENABLE_KDE_SUPPORT
#include <KDE/KGlobal>
K_GLOBAL_STATIC(UltimateLyrics, instance)
#endif
static bool compareLyricProviders(const UltimateLyricsProvider *a, const UltimateLyricsProvider *b)
{
return a->getRelevance() < b->getRelevance();
}
static QString parseInvalidIndicator(QXmlStreamReader *reader)
{
QString ret = reader->attributes().value("value").toString();
reader->skipCurrentElement();
return ret;
}
static UltimateLyricsProvider::Rule parseRule(QXmlStreamReader *reader)
{
UltimateLyricsProvider::Rule ret;
while (!reader->atEnd()) {
reader->readNext();
if (QXmlStreamReader::EndElement==reader->tokenType()) {
break;
}
if (QXmlStreamReader::StartElement==reader->tokenType()) {
if (QLatin1String("item")==reader->name()) {
QXmlStreamAttributes attr = reader->attributes();
if (attr.hasAttribute("tag")) {
ret << UltimateLyricsProvider::RuleItem(attr.value("tag").toString(), QString());
} else if (attr.hasAttribute("begin")) {
ret << UltimateLyricsProvider::RuleItem(attr.value("begin").toString(), attr.value("end").toString());
}
}
reader->skipCurrentElement();
}
}
return ret;
}
static UltimateLyricsProvider * parseProvider(QXmlStreamReader *reader)
{
QXmlStreamAttributes attributes = reader->attributes();
UltimateLyricsProvider* scraper = new UltimateLyricsProvider;
scraper->setName(attributes.value("name").toString());
scraper->setTitle(attributes.value("title").toString());
scraper->setCharset(attributes.value("charset").toString());
scraper->setUrl(attributes.value("url").toString());
while (!reader->atEnd()) {
reader->readNext();
if (QXmlStreamReader::EndElement==reader->tokenType()) {
break;
}
if (QXmlStreamReader::StartElement==reader->tokenType()) {
if (QLatin1String("extract")==reader->name()) {
scraper->addExtractRule(parseRule(reader));
} else if (QLatin1String("exclude")==reader->name()) {
scraper->addExcludeRule(parseRule(reader));
} else if (QLatin1String("invalidIndicator")==reader->name()) {
scraper->addInvalidIndicator(parseInvalidIndicator(reader));
} else if (QLatin1String("urlFormat")==reader->name()) {
scraper->addUrlFormat(reader->attributes().value("replace").toString(), reader->attributes().value("with").toString());
reader->skipCurrentElement();
} else {
reader->skipCurrentElement();
}
}
}
return scraper;
}
UltimateLyrics * UltimateLyrics::self()
{
#ifdef ENABLE_KDE_SUPPORT
return instance;
#else
static UltimateLyrics *instance=0;
if(!instance) {
instance=new UltimateLyrics;
}
return instance;
#endif
}
void UltimateLyrics::release()
{
foreach (UltimateLyricsProvider *provider, providers) {
delete provider;
}
providers.clear();
}
const QList<UltimateLyricsProvider *> UltimateLyrics::getProviders()
{
load();
return providers;
}
UltimateLyricsProvider * UltimateLyrics::providerByName(const QString &name) const
{
foreach (UltimateLyricsProvider *provider, providers) {
if (provider->getName() == name) {
return provider;
}
}
return 0;
}
UltimateLyricsProvider * UltimateLyrics::getNext(int &index)
{
load();
index++;
if (index>-1 && index<providers.count()) {
for (int i=index; i<providers.count(); ++i) {
if (providers.at(i)->isEnabled()) {
index=i;
return providers.at(i);
}
}
}
return 0;
}
void UltimateLyrics::load()
{
if (!providers.isEmpty()) {
return;
}
QFile file(":lyrics.xml");
if (file.open(QIODevice::ReadOnly)) {
QXmlStreamReader reader(&file);
while (!reader.atEnd()) {
reader.readNext();
if (QLatin1String("provider")==reader.name()) {
UltimateLyricsProvider *provider = parseProvider(&reader);
if (provider) {
providers << provider;
connect(provider, SIGNAL(lyricsReady(int,QString)), this, SIGNAL(lyricsReady(int,QString)));
}
}
}
}
setEnabled(Settings::self()->lyricProviders());
}
void UltimateLyrics::setEnabled(const QStringList &enabled)
{
foreach (UltimateLyricsProvider *provider, providers) {
provider->setEnabled(false);
provider->setRelevance(0xFFFF);
}
int relevance=0;
foreach (const QString &p, enabled) {
UltimateLyricsProvider *provider=providerByName(p);
if (provider) {
provider->setEnabled(true);
provider->setRelevance(relevance++);
}
}
qSort(providers.begin(), providers.end(), compareLyricProviders);
Settings::self()->saveLyricProviders(enabled);
}

View File

@@ -21,38 +21,35 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SONGINFOPROVIDER_H
#define SONGINFOPROVIDER_H
#ifndef ULTIMATELYRICS_H
#define ULTIMATELYRICS_H
#include <QObject>
#include <QUrl>
//#include "collapsibleinfopane.h"
//#include "core/song.h"
class UltimateLyricsProvider;
class Song;
class SongInfoProvider : public QObject {
Q_OBJECT
class UltimateLyrics : public QObject
{
Q_OBJECT
public:
SongInfoProvider();
static UltimateLyrics * self();
UltimateLyrics() { }
virtual void FetchInfo(int id, const Song& metadata) = 0;
// virtual void Cancel(int id) {}
UltimateLyricsProvider * getNext(int &index);
const QList<UltimateLyricsProvider *> getProviders();
void release();
void setEnabled(const QStringList &enabled);
virtual QString name() const;
bool is_enabled() const { return enabled_; }
void set_enabled(bool enabled) { enabled_ = enabled; }
signals:
void ImageReady(int id, const QUrl& url);
void InfoReady(int id, const QString& data);
void Finished(int id);
Q_SIGNALS:
void lyricsReady(int id, const QString &data);
private:
bool enabled_;
UltimateLyricsProvider * providerByName(const QString &name) const;
void load();
private:
QList<UltimateLyricsProvider *> providers;
};
#endif // SONGINFOPROVIDER_H
#endif // ULTIMATELYRICS_H

View File

@@ -21,37 +21,121 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
//#include "songinfotextview.h"
#include "ultimatelyricsprovider.h"
#include "networkaccessmanager.h"
//#include "core/network.h"
#include "song.h"
#include <QNetworkReply>
#include <QTextCodec>
//#include <boost/scoped_ptr.hpp>
const int UltimateLyricsProvider::kRedirectLimit = 5;
static const int constRedirectLimit=5;
static QString extract(const QString &source, const QString &begin, const QString &end)
{
int beginIdx = source.indexOf(begin);
if (-1==beginIdx) {
return QString();
}
beginIdx += begin.length();
int endIdx = source.indexOf(end, beginIdx);
if (-1==endIdx) {
return QString();
}
return source.mid(beginIdx, endIdx - beginIdx - 1);
}
static QString extractXmlTag(const QString &source, const QString &tag)
{
QRegExp re("<(\\w+).*>"); // ಠ_ಠ
if (-1==re.indexIn(tag)) {
return QString();
}
return extract(source, tag, "</" + re.cap(1) + ">");
}
static QString exclude(const QString &source, const QString &begin, const QString &end)
{
int beginIdx = source.indexOf(begin);
if (-1==beginIdx) {
return source;
}
int endIdx = source.indexOf(end, beginIdx + begin.length());
if (-1==endIdx) {
return source;
}
return source.left(beginIdx) + source.right(source.length() - endIdx - end.length());
}
static QString excludeXmlTag(const QString &source, const QString &tag)
{
QRegExp re("<(\\w+).*>"); // ಠ_ಠ
if (-1==re.indexIn(tag)) {
return source;
}
return exclude(source, tag, "</" + re.cap(1) + ">");
}
static void applyExtractRule(const UltimateLyricsProvider::Rule &rule, QString *content)
{
foreach (const UltimateLyricsProvider::RuleItem& item, rule) {
if (item.second.isNull()) {
*content = extractXmlTag(*content, item.first);
} else {
*content = extract(*content, item.first, item.second);
}
}
}
static void applyExcludeRule(const UltimateLyricsProvider::Rule &rule, QString *content)
{
foreach (const UltimateLyricsProvider::RuleItem& item, rule) {
if (item.second.isNull()) {
*content = excludeXmlTag(*content, item.first);
} else {
*content = exclude(*content, item.first, item.second);
}
}
}
static QString firstChar(const QString &text)
{
return text.isEmpty() ? text : text[0].toLower();
}
static QString titleCase(const QString &text)
{
if (0==text.length()) {
return QString();
}
if (1==text.length()) {
return text[0].toUpper();
}
return text[0].toUpper() + text.right(text.length() - 1).toLower();
}
UltimateLyricsProvider::UltimateLyricsProvider()
: relevance_(0),
redirect_count_(0)
: enabled(true)
, relevance(0)
, redirectCount(0)
{
}
void UltimateLyricsProvider::FetchInfo(int id, const Song& metadata) {
// Get the text codec
#if QT_VERSION < 0x050000
const QTextCodec* codec = QTextCodec::codecForName(charset_.toAscii().constData());
#else
const QTextCodec* codec = QTextCodec::codecForName(charset_.toLatin1().constData());
#endif
if (!codec) {
//qWarning() << "Invalid codec" << charset_;
//emit Finished(id);
emit InfoReady(id, QString());
return;
}
void UltimateLyricsProvider::fetchInfo(int id, const Song &metadata)
{
#if QT_VERSION < 0x050000
const QTextCodec *codec = QTextCodec::codecForName(charset.toAscii().constData());
#else
const QTextCodec *codec = QTextCodec::codecForName(charset.toLatin1().constData());
#endif
if (!codec) {
emit lyricsReady(id, QString());
return;
}
// strip "featuring <someone else>" from the song.artist
QString artistFixed=metadata.artist;
@@ -59,207 +143,116 @@ void UltimateLyricsProvider::FetchInfo(int id, const Song& metadata) {
toStrip << QLatin1String(" ft. ") << QLatin1String(" feat. ") << QLatin1String(" featuring ");
foreach (const QString s, toStrip) {
int strip = artistFixed.toLower().indexOf( " ft. ");
if ( strip != -1 ) {
if (-1!=strip) {
artistFixed = artistFixed.mid( 0, strip );
}
}
// Fill in fields in the URL
QString url_text(url_);
DoUrlReplace("{artist}", artistFixed.toLower(), &url_text);
DoUrlReplace("{album}", metadata.album.toLower(), &url_text);
DoUrlReplace("{title}", metadata.title.toLower(), &url_text);
DoUrlReplace("{Artist}", artistFixed, &url_text);
DoUrlReplace("{Album}", metadata.album, &url_text);
DoUrlReplace("{Title}", metadata.title, &url_text);
DoUrlReplace("{Title2}", TitleCase(metadata.title), &url_text);
DoUrlReplace("{a}", FirstChar(artistFixed), &url_text);
// Fill in fields in the URL
QString urlText(url);
doUrlReplace("{artist}", artistFixed.toLower(), &urlText);
doUrlReplace("{album}", metadata.album.toLower(), &urlText);
doUrlReplace("{title}", metadata.title.toLower(), &urlText);
doUrlReplace("{Artist}", artistFixed, &urlText);
doUrlReplace("{Album}", metadata.album, &urlText);
doUrlReplace("{Title}", metadata.title, &urlText);
doUrlReplace("{Title2}", titleCase(metadata.title), &urlText);
doUrlReplace("{a}", firstChar(artistFixed), &urlText);
QUrl url(url_text);
// Fetch the URL, follow redirects
redirect_count_ = 0;
QNetworkReply* reply = NetworkAccessManager::self()->get(QNetworkRequest(url));
requests_[reply] = id;
connect(reply, SIGNAL(finished()), SLOT(LyricsFetched()));
// Fetch the URL, follow redirects
redirectCount = 0;
QNetworkReply *reply = NetworkAccessManager::self()->get(QNetworkRequest(QUrl(urlText)));
requests[reply] = id;
connect(reply, SIGNAL(finished()), SLOT(lyricsFetched()));
}
void UltimateLyricsProvider::LyricsFetched() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if (!reply)
return;
void UltimateLyricsProvider::lyricsFetched()
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (!reply)
return;
int id = requests_.take(reply);
reply->deleteLater();
int id = requests.take(reply);
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) {
//emit Finished(id);
emit InfoReady(id, QString());
return;
}
// Handle redirects
QVariant redirect_target = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (redirect_target.isValid()) {
if (redirect_count_ >= kRedirectLimit) {
//emit Finished(id);
emit InfoReady(id, QString());
return;
if (QNetworkReply::NoError!=reply->error()) {
//emit Finished(id);
emit lyricsReady(id, QString());
return;
}
QUrl target = redirect_target.toUrl();
if (target.scheme().isEmpty() || target.host().isEmpty()) {
QString path = target.path();
target = reply->url();
target.setPath(path);
// Handle redirects
QVariant redirect_target = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (redirect_target.isValid()) {
if (redirectCount >= constRedirectLimit) {
//emit Finished(id);
emit lyricsReady(id, QString());
return;
}
QUrl target = redirect_target.toUrl();
if (target.scheme().isEmpty() || target.host().isEmpty()) {
QString path = target.path();
target = reply->url();
target.setPath(path);
}
redirectCount ++;
QNetworkReply* reply = NetworkAccessManager::self()->get(QNetworkRequest(target));
requests[reply] = id;
connect(reply, SIGNAL(finished()), SLOT(lyricsFetched()));
return;
}
redirect_count_ ++;
QNetworkReply* reply = NetworkAccessManager::self()->get(QNetworkRequest(target));
requests_[reply] = id;
connect(reply, SIGNAL(finished()), SLOT(LyricsFetched()));
return;
}
#if QT_VERSION < 0x050000
const QTextCodec *codec = QTextCodec::codecForName(charset.toAscii().constData());
#else
const QTextCodec *codec = QTextCodec::codecForName(charset.toLatin1().constData());
#endif
const QString original_content = codec->toUnicode(reply->readAll());
#if QT_VERSION < 0x050000
const QTextCodec* codec = QTextCodec::codecForName(charset_.toAscii().constData());
#else
const QTextCodec* codec = QTextCodec::codecForName(charset_.toLatin1().constData());
#endif
const QString original_content = codec->toUnicode(reply->readAll());
// Check for invalid indicators
foreach (const QString& indicator, invalid_indicators_) {
if (original_content.contains(indicator)) {
//emit Finished(id);
emit InfoReady(id, QString());
return;
// Check for invalid indicators
foreach (const QString &indicator, invalidIndicators) {
if (original_content.contains(indicator)) {
//emit Finished(id);
emit lyricsReady(id, QString());
return;
}
}
}
QString lyrics;
QString lyrics;
// Apply extract rules
foreach (const Rule& rule, extract_rules_) {
QString content = original_content;
ApplyExtractRule(rule, &content);
// Apply extract rules
foreach (const Rule& rule, extractRules) {
QString content = original_content;
applyExtractRule(rule, &content);
if (!content.isEmpty())
lyrics = content;
}
// Apply exclude rules
foreach (const Rule& rule, exclude_rules_) {
ApplyExcludeRule(rule, &lyrics);
}
#if 0
if (!lyrics.isEmpty()) {
CollapsibleInfoPane::Data data;
data.id_ = "ultimatelyrics/" + name_;
data.title_ = tr("Lyrics from %1").arg(name_);
data.type_ = CollapsibleInfoPane::Data::Type_Lyrics;
data.relevance_ = relevance();
SongInfoTextView* editor = new SongInfoTextView;
editor->SetHtml(lyrics);
data.contents_ = editor;
emit InfoReady(id, data);
}
emit Finished(id);
#endif
lyrics=lyrics.trimmed();
emit InfoReady(id, lyrics);
}
void UltimateLyricsProvider::ApplyExtractRule(const Rule& rule, QString* content) const {
foreach (const RuleItem& item, rule) {
if (item.second.isNull()) {
*content = ExtractXmlTag(*content, item.first);
} else {
*content = Extract(*content, item.first, item.second);
if (!content.isEmpty()) {
lyrics = content;
}
}
}
}
QString UltimateLyricsProvider::ExtractXmlTag(const QString& source, const QString& tag) {
QRegExp re("<(\\w+).*>"); // ಠ_ಠ
if (re.indexIn(tag) == -1)
return QString();
return Extract(source, tag, "</" + re.cap(1) + ">");
}
QString UltimateLyricsProvider::Extract(const QString& source, const QString& begin, const QString& end) {
int begin_idx = source.indexOf(begin);
if (begin_idx == -1)
return QString();
begin_idx += begin.length();
int end_idx = source.indexOf(end, begin_idx);
if (end_idx == -1)
return QString();
return source.mid(begin_idx, end_idx - begin_idx - 1);
}
void UltimateLyricsProvider::ApplyExcludeRule(const Rule& rule, QString* content) const {
foreach (const RuleItem& item, rule) {
if (item.second.isNull()) {
*content = ExcludeXmlTag(*content, item.first);
} else {
*content = Exclude(*content, item.first, item.second);
// Apply exclude rules
foreach (const Rule& rule, excludeRules) {
applyExcludeRule(rule, &lyrics);
}
}
lyrics=lyrics.trimmed();
emit lyricsReady(id, lyrics);
}
QString UltimateLyricsProvider::ExcludeXmlTag(const QString& source, const QString& tag) {
QRegExp re("<(\\w+).*>"); // ಠ_ಠ
if (re.indexIn(tag) == -1)
return source;
void UltimateLyricsProvider::doUrlReplace(const QString &tag, const QString &value, QString *u) const
{
if (!u->contains(tag)) {
return;
}
return Exclude(source, tag, "</" + re.cap(1) + ">");
}
QString UltimateLyricsProvider::Exclude(const QString& source, const QString& begin, const QString& end) {
int begin_idx = source.indexOf(begin);
if (begin_idx == -1)
return source;
int end_idx = source.indexOf(end, begin_idx + begin.length());
if (end_idx == -1)
return source;
return source.left(begin_idx) + source.right(source.length() - end_idx - end.length());
}
QString UltimateLyricsProvider::FirstChar(const QString& text) {
if (text.isEmpty())
return QString();
return text[0].toLower();
}
QString UltimateLyricsProvider::TitleCase(const QString& text) {
if (text.length() == 0)
return QString();
if (text.length() == 1)
return text[0].toUpper();
return text[0].toUpper() + text.right(text.length() - 1).toLower();
}
void UltimateLyricsProvider::DoUrlReplace(const QString& tag, const QString& value,
QString* url) const {
if (!url->contains(tag))
return;
// Apply URL character replacement
QString value_copy(value);
foreach (const UrlFormat& format, url_formats_) {
QRegExp re("[" + QRegExp::escape(format.first) + "]");
value_copy.replace(re, format.second);
}
url->replace(tag, value_copy, Qt::CaseInsensitive);
// Apply URL character replacement
QString valueCopy(value);
foreach (const UltimateLyricsProvider::UrlFormat& format, urlFormats) {
QRegExp re("[" + QRegExp::escape(format.first) + "]");
valueCopy.replace(re, format.second);
}
u->replace(tag, valueCopy, Qt::CaseInsensitive);
}

View File

@@ -28,71 +28,57 @@
#include <QPair>
#include <QStringList>
#include <QHash>
#include "songinfoprovider.h"
class Song;
class QNetworkReply;
class UltimateLyricsProvider : public SongInfoProvider {
Q_OBJECT
class UltimateLyricsProvider : public QObject {
Q_OBJECT
public:
UltimateLyricsProvider();
UltimateLyricsProvider();
static const int kRedirectLimit;
typedef QPair<QString, QString> RuleItem;
typedef QList<RuleItem> Rule;
typedef QPair<QString, QString> UrlFormat;
typedef QPair<QString, QString> RuleItem;
typedef QList<RuleItem> Rule;
typedef QPair<QString, QString> UrlFormat;
void setName(const QString &n) { name = n; }
void setTitle(const QString &t) { title = t; }
void setUrl(const QString &u) { url = u; }
void setCharset(const QString &c) { charset = c; }
void setRelevance(int r) { relevance = r; }
void addUrlFormat(const QString &replace, const QString &with) { urlFormats << UrlFormat(replace, with); }
void addExtractRule(const Rule &rule) { extractRules << rule; }
void addExcludeRule(const Rule &rule) { excludeRules << rule; }
void addInvalidIndicator(const QString &indicator) { invalidIndicators << indicator; }
QString getName() const { return name; }
int getRelevance() const { return relevance; }
void fetchInfo(int id, const Song &metadata);
bool isEnabled() const { return enabled; }
void setEnabled(bool e) { enabled = e; }
void set_name(const QString& name) { name_ = name; }
void set_title(const QString& title) { title_ = title; }
void set_url(const QString& url) { url_ = url; }
void set_charset(const QString& charset) { charset_ = charset; }
void set_relevance(int relevance) { relevance_ = relevance; }
Q_SIGNALS:
void lyricsReady(int id, const QString &data);
void add_url_format(const QString& replace, const QString& with) {
url_formats_ << UrlFormat(replace, with); }
void add_extract_rule(const Rule& rule) { extract_rules_ << rule; }
void add_exclude_rule(const Rule& rule) { exclude_rules_ << rule; }
void add_invalid_indicator(const QString& indicator) { invalid_indicators_ << indicator; }
QString name() const { return name_; }
int relevance() const { return relevance_; }
void FetchInfo(int id, const Song& metadata);
private slots:
void LyricsFetched();
private Q_SLOTS:
void lyricsFetched();
private:
void ApplyExtractRule(const Rule& rule, QString* content) const;
void ApplyExcludeRule(const Rule& rule, QString* content) const;
static QString ExtractXmlTag(const QString& source, const QString& tag);
static QString Extract(const QString& source, const QString& begin, const QString& end);
static QString ExcludeXmlTag(const QString& source, const QString& tag);
static QString Exclude(const QString& source, const QString& begin, const QString& end);
static QString FirstChar(const QString& text);
static QString TitleCase(const QString& text);
void DoUrlReplace(const QString& tag, const QString& value, QString* url) const;
void doUrlReplace(const QString &tag, const QString &value, QString *u) const;
private:
QHash<QNetworkReply*, int> requests_;
QString name_;
QString title_;
QString url_;
QString charset_;
int relevance_;
QList<UrlFormat> url_formats_;
QList<Rule> extract_rules_;
QList<Rule> exclude_rules_;
QStringList invalid_indicators_;
int redirect_count_;
bool enabled;
QHash<QNetworkReply*, int> requests;
QString name;
QString title;
QString url;
QString charset;
int relevance;
QList<UrlFormat> urlFormats;
QList<Rule> extractRules;
QList<Rule> excludeRules;
QStringList invalidIndicators;
int redirectCount;
};
#endif // ULTIMATELYRICSPROVIDER_H

View File

@@ -1,128 +0,0 @@
/*
* Cantata
*
* Copyright (c) 2011-2013 Craig Drummond <craig.p.drummond@gmail.com>
*
*/
/* 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 "ultimatelyricsprovider.h"
#include "ultimatelyricsreader.h"
#include <QCoreApplication>
#include <QFile>
#include <QXmlStreamReader>
UltimateLyricsReader::UltimateLyricsReader(/*QObject* parent*/)
// : QObject(parent)
{
}
QList<UltimateLyricsProvider*> UltimateLyricsReader::Parse(const QString& filename) const {
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
// qWarning() << "Error opening" << filename;
return QList<UltimateLyricsProvider*>();
}
return ParseDevice(&file);
}
QList<UltimateLyricsProvider*> UltimateLyricsReader::ParseDevice(QIODevice* device) const {
QList<UltimateLyricsProvider*> ret;
QXmlStreamReader reader(device);
while (!reader.atEnd()) {
reader.readNext();
if (reader.name() == "provider") {
UltimateLyricsProvider* provider = ParseProvider(&reader);
if (provider) {
provider->moveToThread(qApp->thread());
ret << provider;
}
}
}
return ret;
}
UltimateLyricsProvider* UltimateLyricsReader::ParseProvider(QXmlStreamReader* reader) const {
QXmlStreamAttributes attributes = reader->attributes();
UltimateLyricsProvider* scraper = new UltimateLyricsProvider;
scraper->set_name(attributes.value("name").toString());
scraper->set_title(attributes.value("title").toString());
scraper->set_charset(attributes.value("charset").toString());
scraper->set_url(attributes.value("url").toString());
while (!reader->atEnd()) {
reader->readNext();
if (reader->tokenType() == QXmlStreamReader::EndElement)
break;
if (reader->tokenType() == QXmlStreamReader::StartElement) {
if (reader->name() == "extract")
scraper->add_extract_rule(ParseRule(reader));
else if (reader->name() == "exclude")
scraper->add_exclude_rule(ParseRule(reader));
else if (reader->name() == "invalidIndicator")
scraper->add_invalid_indicator(ParseInvalidIndicator(reader));
else if (reader->name() == "urlFormat") {
scraper->add_url_format(reader->attributes().value("replace").toString(),
reader->attributes().value("with").toString());
reader->skipCurrentElement();
}
else
reader->skipCurrentElement();
}
}
return scraper;
}
UltimateLyricsProvider::Rule UltimateLyricsReader::ParseRule(QXmlStreamReader* reader) const {
UltimateLyricsProvider::Rule ret;
while (!reader->atEnd()) {
reader->readNext();
if (reader->tokenType() == QXmlStreamReader::EndElement)
break;
if (reader->tokenType() == QXmlStreamReader::StartElement) {
if (reader->name() == "item") {
QXmlStreamAttributes attr = reader->attributes();
if (attr.hasAttribute("tag"))
ret << UltimateLyricsProvider::RuleItem(attr.value("tag").toString(), QString());
else if (attr.hasAttribute("begin"))
ret << UltimateLyricsProvider::RuleItem(attr.value("begin").toString(),
attr.value("end").toString());
}
reader->skipCurrentElement();
}
}
return ret;
}
QString UltimateLyricsReader::ParseInvalidIndicator(QXmlStreamReader* reader) const {
QString ret = reader->attributes().value("value").toString();
reader->skipCurrentElement();
return ret;
}

View File

@@ -1,49 +0,0 @@
/*
* Cantata
*
* Copyright (c) 2011-2013 Craig Drummond <craig.p.drummond@gmail.com>
*
*/
/* 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 ULTIMATELYRICSREADER_H
#define ULTIMATELYRICSREADER_H
#include "ultimatelyricsprovider.h"
// #include <QObject>
#include <QXmlStreamReader>
class QIODevice;
class UltimateLyricsReader/* : public QObject*/ {
// Q_OBJECT
public:
UltimateLyricsReader(/*QObject* parent = 0*/);
QList<UltimateLyricsProvider*> Parse(const QString& filename) const;
QList<UltimateLyricsProvider*> ParseDevice(QIODevice* device) const;
private:
UltimateLyricsProvider* ParseProvider(QXmlStreamReader* reader) const;
UltimateLyricsProvider::Rule ParseRule(QXmlStreamReader* reader) const;
QString ParseInvalidIndicator(QXmlStreamReader* reader) const;
};
#endif // ULTIMATELYRICSREADER_H