/************************************************************************** * Converted to C++ from the Amarok lyrics script... * * Amarok 2 lyrics script to fetch lyrics from lyrics.wikia.com * * (formerly lyricwiki.org) * * * * Copyright * * (C) 2008 Aaron Reichman * * (C) 2008 Leo Franchi * * (C) 2008 Mark Kretschmann * * (C) 2008 Peter ZHOU * * (C) 2009 Jakob Kummerow * * * * This program 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 2 of the License, or * * (at your option) any later version. * * * * This program 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 this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * **************************************************************************/ #include "lyrics.h" #include "musiclibrarymodel.h" #include "lib/song.h" #ifdef ENABLE_KDE_SUPPORT #include #include #include #endif #include #include #include #include #include #include #include #include // url to get lyrics using mediawiki API static const QLatin1String constApiUrl("http://lyrics.wikia.com/api.php?action=query&prop=revisions&rvprop=content&format=xml&titles="); static const int constMaxRedirects=3; static QString changeExt(const QString &f, const QString &newExt) { QString newStr(f); int dotPos(newStr.lastIndexOf('.')); if (-1==dotPos) { newStr+=newExt; } else { newStr.remove(dotPos, newStr.length()); newStr+=newExt; } return newStr; } struct Decoded { enum Type { Empty, Lyric, Redirect }; Type type; QString value; Decoded() : type(Empty) { } Decoded(Type t, const QString &v) : type(t), value(v) { } }; static Decoded decode(const QByteArray &data) { QDomDocument doc; doc.setContent(data); QString response = doc.elementsByTagName("rev").at(0).toElement().text(); qDebug() << "DECODE:" << response; int open=response.indexOf(" upper && words[i].at(upper).toUpper() == QChar('I') ) { upper++; } } // if the word starts with an apostrophe or parenthesis, the next character has to be uppercase if ( words[i].at(0) == QChar('\'') || words[i].at(0) == QChar('(') ) { upper++; } // finally, perform the capitalization if ( upper < words[i].length() ) { words[i] = words[i].mid( 0, upper ).toUpper() + words[i].mid( upper ); } else { words[i] = words[i].toUpper(); } // now take care of more special cases // names like "McSomething" if ( words[i].mid( 0, 2 ) == QLatin1String("Mc") ) { words[i] = QLatin1String("Mc") + words[i][2].toUpper() + words[i].mid( 3 ); } // URI-encode the word words[i] = QUrl::toPercentEncoding(words[i]); } // join the words back together and return the result return words.join( "_" ); } // convert all HTML entities to their applicable characters static QString entityDecode(const QString &string) { QString convertXml = QLatin1String("") + string + QLatin1String(""); QDomDocument doc; if (doc.setContent(convertXml)) { // xml is valid return doc.elementsByTagName( "entity" ).at( 0 ).toElement().text(); } return string; } #ifdef ENABLE_KDE_SUPPORT K_GLOBAL_STATIC(Lyrics, instance) #endif Lyrics * Lyrics::self() { #ifdef ENABLE_KDE_SUPPORT return instance; #else static Lyrics *instance=0;; if(!instance) { instance=new Lyrics; } return instance; #endif } Lyrics::Lyrics() : manager(0) { } static const QLatin1String constKeySep("<<##>>"); static const QLatin1String constLyricsDir("lyrics/"); static const QLatin1String constExtension(".lyrics"); QString cacheFile(QString artist, QString title, bool createDir=false) { title.replace("/", "_"); artist.replace("/", "_"); return QDir::toNativeSeparators(MusicLibraryModel::cacheDir(constLyricsDir+artist+'/', createDir))+title+constExtension; } void Lyrics::get(const Song &song) { if (song.artist.isEmpty() || song.title.isEmpty()) { return; } if (!mpdDir.isEmpty()) { // Check for MPD file... QString mpdLyrics=changeExt(mpdDir+song.file, constExtension); if (QFile::exists(mpdLyrics)) { QFile f(mpdLyrics); if (f.open(QIODevice::ReadOnly)) { emit lyrics(song.artist, song.title, f.readAll()); f.close(); return; } } } // Check for cached file... QString file=cacheFile(song.artist, song.title); if (QFile::exists(file)) { QFile f(file); if (f.open(QIODevice::ReadOnly)) { emit lyrics(song.artist, song.title, f.readAll()); f.close(); return; } } // Check we are not aleady downloading... QString key=song.artist+constKeySep+song.title; if (jobs.contains(key)) { return; } // strip "featuring " from the song.artist QString artistFixed=song.artist; QStringList toStrip; toStrip << QLatin1String(" ft. ") << QLatin1String(" feat. ") << QLatin1String(" featuring "); foreach (const QString s, toStrip) { int strip = artistFixed.toLower().indexOf( " ft. "); if ( strip != -1 ) { artistFixed = artistFixed.mid( 0, strip ); } } if (!manager) { // QNetworkProxy proxy; // proxy.setType(QNetworkProxy::HttpProxy); // proxy.setHostName("proxy.fr.nds.com"); // proxy.setPort(8080); // QNetworkProxy::setApplicationProxy(proxy); #ifdef ENABLE_KDE_SUPPORT manager = new KIO::Integration::AccessManager(this); #else manager=new QNetworkAccessManager(this); #endif connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(jobFinished(QNetworkReply *))); } QUrl url; url.setEncodedUrl(QString(constApiUrl + convertToUrl(entityDecode(artistFixed))+":"+convertToUrl(entityDecode(song.title))).toLatin1()); qDebug() << "URL" << url.toString(); QNetworkReply *reply=manager->get(QNetworkRequest(url)); jobs.insert(key, Job(reply)); } void Lyrics::jobFinished(QNetworkReply *reply) { // TODO: Handle redirects: QNetworkRequest::RedirectionTargetAttribute QMap::Iterator it(jobs.begin()); QMap::Iterator end(jobs.end()); for (; it!=end; ++it) { if (it.value().reply==reply) { QStringList parts=it.key().split(constKeySep); Decoded decoded=QNetworkReply::NoError==reply->error() ? decode(reply->readAll()) : Decoded(); switch(decoded.type) { case Decoded::Empty: #ifdef ENABLE_KDE_SUPPORT emit lyrics(parts[0], parts[1], i18n("No lyrics found")); #else emit lyrics(parts[0], parts[1], tr("No lyrics found")); #endif jobs.remove(it.key()); break; case Decoded::Lyric: { QFile f(cacheFile(parts[0], parts[1], true)); if (f.open(QIODevice::WriteOnly)) { QTextStream(&f) << decoded.value; f.close(); } emit lyrics(parts[0], parts[1], decoded.value); jobs.remove(it.key()); break; } case Decoded::Redirect: { if(++(it.value().redirects) < constMaxRedirects) { qDebug() << "Redirect URL" << decoded.value; QNetworkReply *reply=manager->get(QNetworkRequest(QUrl(decoded.value))); it.value().reply=reply; } else { #ifdef ENABLE_KDE_SUPPORT emit lyrics(parts[0], parts[1], i18n("No lyrics found")); #else emit lyrics(parts[0], parts[1], tr("No lyrics found")); #endif jobs.remove(it.key()); } break; } } } } reply->deleteLater(); }