Overhaul meta-data search

* Improve coding style
* Remove useless code comments
* Hide legacy providers by default
* Add Tekstowo to have at least one functioning provider for lyrics again
* Enable query logging only if an environment variable is set
* Use Tekstowo in example JavaScript
This commit is contained in:
Martchus 2023-08-06 02:25:53 +02:00
parent dace19b2bf
commit e69278634f
18 changed files with 341 additions and 106 deletions

View File

@ -52,6 +52,7 @@ set(WIDGETS_HEADER_FILES
dbquery/musicbrainz.h dbquery/musicbrainz.h
dbquery/makeitpersonal.h dbquery/makeitpersonal.h
dbquery/lyricswikia.h dbquery/lyricswikia.h
dbquery/tekstowo.h
gui/dbquerywidget.h gui/dbquerywidget.h
misc/networkaccessmanager.h misc/networkaccessmanager.h
renamingutility/filesystemitem.h renamingutility/filesystemitem.h
@ -82,6 +83,7 @@ set(WIDGETS_SRC_FILES
dbquery/musicbrainz.cpp dbquery/musicbrainz.cpp
dbquery/makeitpersonal.cpp dbquery/makeitpersonal.cpp
dbquery/lyricswikia.cpp dbquery/lyricswikia.cpp
dbquery/tekstowo.cpp
gui/dbquerywidget.cpp gui/dbquerywidget.cpp
misc/networkaccessmanager.cpp misc/networkaccessmanager.cpp
renamingutility/filesystemitem.cpp renamingutility/filesystemitem.cpp

View File

@ -206,6 +206,7 @@ void restore()
v.dbQuery.musicBrainzUrl = settings.value(QStringLiteral("musicbrainzurl")).toString(); v.dbQuery.musicBrainzUrl = settings.value(QStringLiteral("musicbrainzurl")).toString();
v.dbQuery.lyricsWikiaUrl = settings.value(QStringLiteral("lyricwikiurl")).toString(); v.dbQuery.lyricsWikiaUrl = settings.value(QStringLiteral("lyricwikiurl")).toString();
v.dbQuery.makeItPersonalUrl = settings.value(QStringLiteral("makeitpersonalurl")).toString(); v.dbQuery.makeItPersonalUrl = settings.value(QStringLiteral("makeitpersonalurl")).toString();
v.dbQuery.tekstowoUrl = settings.value(QStringLiteral("tekstowourl")).toString();
v.dbQuery.coverArtArchiveUrl = settings.value(QStringLiteral("coverartarchiveurl")).toString(); v.dbQuery.coverArtArchiveUrl = settings.value(QStringLiteral("coverartarchiveurl")).toString();
settings.endGroup(); settings.endGroup();
@ -302,6 +303,7 @@ void save()
settings.setValue(QStringLiteral("musicbrainzurl"), v.dbQuery.musicBrainzUrl); settings.setValue(QStringLiteral("musicbrainzurl"), v.dbQuery.musicBrainzUrl);
settings.setValue(QStringLiteral("lyricwikiurl"), v.dbQuery.lyricsWikiaUrl); settings.setValue(QStringLiteral("lyricwikiurl"), v.dbQuery.lyricsWikiaUrl);
settings.setValue(QStringLiteral("makeitpersonalurl"), v.dbQuery.makeItPersonalUrl); settings.setValue(QStringLiteral("makeitpersonalurl"), v.dbQuery.makeItPersonalUrl);
settings.setValue(QStringLiteral("tekstowourl"), v.dbQuery.tekstowoUrl);
settings.setValue(QStringLiteral("coverartarchiveurl"), v.dbQuery.coverArtArchiveUrl); settings.setValue(QStringLiteral("coverartarchiveurl"), v.dbQuery.coverArtArchiveUrl);
settings.endGroup(); settings.endGroup();

View File

@ -100,6 +100,7 @@ struct DbQuery {
QString coverArtArchiveUrl; QString coverArtArchiveUrl;
QString lyricsWikiaUrl; QString lyricsWikiaUrl;
QString makeItPersonalUrl; QString makeItPersonalUrl;
QString tekstowoUrl;
}; };
struct RenamingUtility { struct RenamingUtility {

View File

@ -142,6 +142,11 @@ QJSValue UtilityObject::queryMakeItPersonal(const QJSValue &songDescription)
return m_engine->newQObject(QtGui::queryMakeItPersonal(makeSongDescription(songDescription))); return m_engine->newQObject(QtGui::queryMakeItPersonal(makeSongDescription(songDescription)));
} }
QJSValue UtilityObject::queryTekstowo(const QJSValue &songDescription)
{
return m_engine->newQObject(QtGui::queryTekstowo(makeSongDescription(songDescription)));
}
QtGui::SongDescription UtilityObject::makeSongDescription(const QJSValue &obj) QtGui::SongDescription UtilityObject::makeSongDescription(const QJSValue &obj)
{ {
auto desc = QtGui::SongDescription(obj.property(QStringLiteral("songId")).toString()); auto desc = QtGui::SongDescription(obj.property(QStringLiteral("songId")).toString());

View File

@ -61,6 +61,7 @@ public Q_SLOTS:
QJSValue queryMusicBrainz(const QJSValue &songDescription); QJSValue queryMusicBrainz(const QJSValue &songDescription);
QJSValue queryLyricsWikia(const QJSValue &songDescription); QJSValue queryLyricsWikia(const QJSValue &songDescription);
QJSValue queryMakeItPersonal(const QJSValue &songDescription); QJSValue queryMakeItPersonal(const QJSValue &songDescription);
QJSValue queryTekstowo(const QJSValue &songDescription);
private: private:
static QtGui::SongDescription makeSongDescription(const QJSValue &obj); static QtGui::SongDescription makeSongDescription(const QJSValue &obj);

View File

@ -3,6 +3,8 @@
#include "../misc/networkaccessmanager.h" #include "../misc/networkaccessmanager.h"
#include "../misc/utility.h" #include "../misc/utility.h"
#include "resources/config.h"
#include <tagparser/signature.h> #include <tagparser/signature.h>
#include <tagparser/tag.h> #include <tagparser/tag.h>
#include <tagparser/tagvalue.h> #include <tagparser/tagvalue.h>
@ -25,7 +27,7 @@ SongDescription::SongDescription(const QString &songId)
} }
std::list<QString> QueryResultsModel::s_coverNames = std::list<QString>(); std::list<QString> QueryResultsModel::s_coverNames = std::list<QString>();
map<QString, QByteArray> QueryResultsModel::s_coverData = map<QString, QByteArray>(); std::map<QString, QByteArray> QueryResultsModel::s_coverData = std::map<QString, QByteArray>();
QueryResultsModel::QueryResultsModel(QObject *parent) QueryResultsModel::QueryResultsModel(QObject *parent)
: QAbstractTableModel(parent) : QAbstractTableModel(parent)
@ -104,7 +106,7 @@ QVariant QueryResultsModel::data(const QModelIndex &index, int role) const
if (!index.isValid() || index.row() >= m_results.size()) { if (!index.isValid() || index.row() >= m_results.size()) {
return QVariant(); return QVariant();
} }
const SongDescription &res = m_results.at(index.row()); const auto &res = m_results.at(index.row());
switch (role) { switch (role) {
case Qt::DisplayRole: case Qt::DisplayRole:
switch (index.column()) { switch (index.column()) {
@ -146,7 +148,7 @@ QVariant QueryResultsModel::data(const QModelIndex &index, int role) const
Qt::ItemFlags QueryResultsModel::flags(const QModelIndex &index) const Qt::ItemFlags QueryResultsModel::flags(const QModelIndex &index) const
{ {
Qt::ItemFlags flags = Qt::ItemNeverHasChildren | Qt::ItemIsSelectable | Qt::ItemIsEnabled; auto flags = Qt::ItemFlags(Qt::ItemNeverHasChildren | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
if (index.isValid()) { if (index.isValid()) {
flags |= Qt::ItemIsUserCheckable; flags |= Qt::ItemIsUserCheckable;
} }
@ -311,6 +313,16 @@ void HttpResultsModel::handleInitialReplyFinished()
setResultsAvailable(true); // update status, emit resultsAvailable() setResultsAvailable(true); // update status, emit resultsAvailable()
} }
#ifdef CPP_UTILITIES_DEBUG_BUILD
void HttpResultsModel::logReply(QNetworkReply *reply)
{
static const auto enableQueryLogging = qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_ENABLE_QUERY_LOGGING");
if (enableQueryLogging) {
std::cerr << "HTTP query: " << reply->url().toString().toUtf8().data() << std::endl;
}
}
#endif
QNetworkReply *HttpResultsModel::evaluateReplyResults(QNetworkReply *reply, QByteArray &data, bool alwaysFollowRedirection) QNetworkReply *HttpResultsModel::evaluateReplyResults(QNetworkReply *reply, QByteArray &data, bool alwaysFollowRedirection)
{ {
// delete reply (later) // delete reply (later)
@ -328,8 +340,10 @@ QNetworkReply *HttpResultsModel::evaluateReplyResults(QNetworkReply *reply, QByt
m_errorList << tr("Server replied no data."); m_errorList << tr("Server replied no data.");
} }
#ifdef CPP_UTILITIES_DEBUG_BUILD #ifdef CPP_UTILITIES_DEBUG_BUILD
cerr << "Results from HTTP query:" << endl; static const auto enableQueryLogging = qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_ENABLE_QUERY_LOGGING");
cerr << data.data() << endl; if (enableQueryLogging) {
std::cerr << "Results from HTTP query:\n" << data.data() << '\n';
}
#endif #endif
return nullptr; return nullptr;
} }
@ -366,7 +380,7 @@ void HttpResultsModel::abort()
void HttpResultsModel::handleCoverReplyFinished(QNetworkReply *reply, const QString &albumId, int row) void HttpResultsModel::handleCoverReplyFinished(QNetworkReply *reply, const QString &albumId, int row)
{ {
QByteArray data; auto data = QByteArray();
if (auto *const newReply = evaluateReplyResults(reply, data, true)) { if (auto *const newReply = evaluateReplyResults(reply, data, true)) {
addReply(newReply, bind(&HttpResultsModel::handleCoverReplyFinished, this, newReply, albumId, row)); addReply(newReply, bind(&HttpResultsModel::handleCoverReplyFinished, this, newReply, albumId, row));
return; return;

View File

@ -22,7 +22,7 @@ TAGEDITOR_ENUM_CLASS KnownField : unsigned int;
namespace QtGui { namespace QtGui {
struct SongDescription { struct SongDescription {
SongDescription(const QString &songId = QString()); explicit SongDescription(const QString &songId = QString());
QString songId; QString songId;
QString title; QString title;
@ -85,7 +85,7 @@ Q_SIGNALS:
void lyricsAvailable(const QModelIndex &index); void lyricsAvailable(const QModelIndex &index);
protected: protected:
QueryResultsModel(QObject *parent = nullptr); explicit QueryResultsModel(QObject *parent = nullptr);
void setResultsAvailable(bool resultsAvailable); void setResultsAvailable(bool resultsAvailable);
void setFetchingCover(bool fetchingCover); void setFetchingCover(bool fetchingCover);
@ -124,7 +124,7 @@ public:
void abort() override; void abort() override;
protected: protected:
HttpResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply); explicit HttpResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply);
template <class Object, class Function> void addReply(QNetworkReply *reply, Object object, Function handler); template <class Object, class Function> void addReply(QNetworkReply *reply, Object object, Function handler);
template <class Function> void addReply(QNetworkReply *reply, Function handler); template <class Function> void addReply(QNetworkReply *reply, Function handler);
virtual void parseInitialResults(const QByteArray &data) = 0; virtual void parseInitialResults(const QByteArray &data) = 0;
@ -135,17 +135,20 @@ protected:
private Q_SLOTS: private Q_SLOTS:
void handleInitialReplyFinished(); void handleInitialReplyFinished();
#ifdef CPP_UTILITIES_DEBUG_BUILD
void logReply(QNetworkReply *reply);
#endif
protected: protected:
QList<QNetworkReply *> m_replies; QList<QNetworkReply *> m_replies;
const SongDescription m_initialDescription; SongDescription m_initialDescription;
}; };
template <class Object, class Function> inline void HttpResultsModel::addReply(QNetworkReply *reply, Object object, Function handler) template <class Object, class Function> inline void HttpResultsModel::addReply(QNetworkReply *reply, Object object, Function handler)
{ {
(m_replies << reply), connect(reply, &QNetworkReply::finished, object, handler); (m_replies << reply), connect(reply, &QNetworkReply::finished, object, handler);
#ifdef CPP_UTILITIES_DEBUG_BUILD #ifdef CPP_UTILITIES_DEBUG_BUILD
std::cerr << "HTTP query: " << reply->url().toString().toUtf8().data() << std::endl; logReply(reply);
#endif #endif
} }
@ -157,7 +160,7 @@ template <class Function> inline void HttpResultsModel::addReply(QNetworkReply *
{ {
(m_replies << reply), connect(reply, &QNetworkReply::finished, handler); (m_replies << reply), connect(reply, &QNetworkReply::finished, handler);
#ifdef CPP_UTILITIES_DEBUG_BUILD #ifdef CPP_UTILITIES_DEBUG_BUILD
std::cerr << "HTTP query: " << reply->url().toString().toUtf8().data() << std::endl; logReply(reply);
#endif #endif
} }
@ -165,6 +168,7 @@ QueryResultsModel *queryMusicBrainz(SongDescription &&songDescription);
QueryResultsModel *queryLyricsWikia(SongDescription &&songDescription); QueryResultsModel *queryLyricsWikia(SongDescription &&songDescription);
QNetworkReply *queryCoverArtArchive(const QString &albumId); QNetworkReply *queryCoverArtArchive(const QString &albumId);
QueryResultsModel *queryMakeItPersonal(SongDescription &&songDescription); QueryResultsModel *queryMakeItPersonal(SongDescription &&songDescription);
QueryResultsModel *queryTekstowo(SongDescription &&songDescription);
} // namespace QtGui } // namespace QtGui

View File

@ -19,7 +19,7 @@ using namespace Utility;
namespace QtGui { namespace QtGui {
static const QString defaultLyricsWikiaUrl(QStringLiteral("https://lyrics.fandom.com")); static const auto defaultLyricsWikiaUrl = QStringLiteral("https://lyrics.fandom.com");
static QUrl lyricsWikiaApiUrl() static QUrl lyricsWikiaApiUrl()
{ {
@ -43,15 +43,12 @@ LyricsWikiaResultsModel::LyricsWikiaResultsModel(SongDescription &&initialSongDe
bool LyricsWikiaResultsModel::fetchCover(const QModelIndex &index) bool LyricsWikiaResultsModel::fetchCover(const QModelIndex &index)
{ {
// FIXME: avoid code duplication with musicbrainz.cpp
// find song description
if (index.parent().isValid() || index.row() >= m_results.size()) { if (index.parent().isValid() || index.row() >= m_results.size()) {
return true; return true;
} }
SongDescription &desc = m_results[index.row()];
// skip if cover is already available // skip if cover is already available
auto &desc = m_results[index.row()];
if (!desc.cover.isEmpty()) { if (!desc.cover.isEmpty()) {
return true; return true;
} }
@ -87,13 +84,12 @@ bool LyricsWikiaResultsModel::fetchCover(const QModelIndex &index)
bool LyricsWikiaResultsModel::fetchLyrics(const QModelIndex &index) bool LyricsWikiaResultsModel::fetchLyrics(const QModelIndex &index)
{ {
// find song description
if (index.parent().isValid() || index.row() >= m_results.size()) { if (index.parent().isValid() || index.row() >= m_results.size()) {
return true; return true;
} }
SongDescription &desc = m_results[index.row()];
// skip if lyrics already present // skip if lyrics already present
auto &desc = m_results[index.row()];
if (!desc.lyrics.isEmpty()) { if (!desc.lyrics.isEmpty()) {
return true; return true;
} }
@ -113,25 +109,24 @@ bool LyricsWikiaResultsModel::fetchLyrics(const QModelIndex &index)
void LyricsWikiaResultsModel::parseInitialResults(const QByteArray &data) void LyricsWikiaResultsModel::parseInitialResults(const QByteArray &data)
{ {
// prepare parsing LyricsWikia meta data
beginResetModel(); beginResetModel();
m_results.clear(); m_results.clear();
QXmlStreamReader xmlReader(data);
// parse XML tree // parse XML tree
auto xmlReader = QXmlStreamReader(data);
// clang-format off // clang-format off
#include <qtutilities/misc/xmlparsermacros.h> #include <qtutilities/misc/xmlparsermacros.h>
children { children {
iftag("getArtistResponse") { iftag("getArtistResponse") {
QString artist; auto artist = QString();
children { children {
iftag("artist") { iftag("artist") {
artist = text; artist = text;
} eliftag("albums") { } eliftag("albums") {
children { children {
iftag("albumResult") { iftag("albumResult") {
QString album, year; auto album = QString(), year = QString();
QList<SongDescription> songs; auto songs = QList<SongDescription>();
children { children {
iftag("album") { iftag("album") {
album = text; album = text;
@ -169,7 +164,7 @@ void LyricsWikiaResultsModel::parseInitialResults(const QByteArray &data)
} }
else_skip else_skip
} }
for (SongDescription &song : m_results) { for (auto &song : m_results) {
// set the arist which is the same for all results // set the arist which is the same for all results
song.artist = artist; song.artist = artist;
// set the album ID (album is identified by its artist, year and name) // set the album ID (album is identified by its artist, year and name)
@ -191,14 +186,13 @@ void LyricsWikiaResultsModel::parseInitialResults(const QByteArray &data)
m_errorList << xmlReader.errorString(); m_errorList << xmlReader.errorString();
} }
// promote changes
endResetModel(); endResetModel();
} }
QNetworkReply *LyricsWikiaResultsModel::requestSongDetails(const SongDescription &songDescription) QNetworkReply *LyricsWikiaResultsModel::requestSongDetails(const SongDescription &songDescription)
{ {
// compose URL // compose URL
QUrlQuery query; auto query = QUrlQuery();
query.addQueryItem(QStringLiteral("func"), QStringLiteral("getSong")); query.addQueryItem(QStringLiteral("func"), QStringLiteral("getSong"));
query.addQueryItem(QStringLiteral("action"), QStringLiteral("lyrics")); query.addQueryItem(QStringLiteral("action"), QStringLiteral("lyrics"));
query.addQueryItem(QStringLiteral("fmt"), QStringLiteral("xml")); query.addQueryItem(QStringLiteral("fmt"), QStringLiteral("xml"));
@ -209,22 +203,21 @@ QNetworkReply *LyricsWikiaResultsModel::requestSongDetails(const SongDescription
// specifying album seems to have no effect but also doesn't hurt // specifying album seems to have no effect but also doesn't hurt
query.addQueryItem(QStringLiteral("album"), songDescription.album); query.addQueryItem(QStringLiteral("album"), songDescription.album);
} }
QUrl url(lyricsWikiaApiUrl()); auto url = lyricsWikiaApiUrl();
url.setQuery(query); url.setQuery(query);
return Utility::networkAccessManager().get(QNetworkRequest(url)); return Utility::networkAccessManager().get(QNetworkRequest(url));
} }
QNetworkReply *LyricsWikiaResultsModel::requestAlbumDetails(const SongDescription &songDescription) QNetworkReply *LyricsWikiaResultsModel::requestAlbumDetails(const SongDescription &songDescription)
{ {
QUrl url(lyricsWikiaApiUrl()); auto url = lyricsWikiaApiUrl();
url.setPath(QStringLiteral("/wiki/") + songDescription.albumId); url.setPath(QStringLiteral("/wiki/") + songDescription.albumId);
return Utility::networkAccessManager().get(QNetworkRequest(url)); return Utility::networkAccessManager().get(QNetworkRequest(url));
} }
void LyricsWikiaResultsModel::handleSongDetailsFinished(QNetworkReply *reply, int row) void LyricsWikiaResultsModel::handleSongDetailsFinished(QNetworkReply *reply, int row)
{ {
QByteArray data; auto data = QByteArray();
if (auto *newReply = evaluateReplyResults(reply, data, true)) { if (auto *newReply = evaluateReplyResults(reply, data, true)) {
addReply(newReply, bind(&LyricsWikiaResultsModel::handleSongDetailsFinished, this, newReply, row)); addReply(newReply, bind(&LyricsWikiaResultsModel::handleSongDetailsFinished, this, newReply, row));
} else if (!data.isEmpty()) { } else if (!data.isEmpty()) {
@ -234,23 +227,21 @@ void LyricsWikiaResultsModel::handleSongDetailsFinished(QNetworkReply *reply, in
void LyricsWikiaResultsModel::parseSongDetails(int row, const QByteArray &data) void LyricsWikiaResultsModel::parseSongDetails(int row, const QByteArray &data)
{ {
// find associated result/desc
if (row >= m_results.size()) { if (row >= m_results.size()) {
m_errorList << tr("Internal error: context for song details reply invalid"); m_errorList << tr("Internal error: context for song details reply invalid");
setResultsAvailable(true); setResultsAvailable(true);
return; return;
} }
SongDescription &assocDesc = m_results[row];
QUrl parsedUrl;
// parse XML tree // parse XML tree
auto &assocDesc = m_results[row];
auto parsedUrl = QUrl();
auto xmlReader = QXmlStreamReader(data);
// clang-format off // clang-format off
QXmlStreamReader xmlReader(data);
#include <qtutilities/misc/xmlparsermacros.h> #include <qtutilities/misc/xmlparsermacros.h>
children { children {
iftag("LyricsResult") { iftag("LyricsResult") {
SongDescription parsedDesc; auto parsedDesc = SongDescription();
children { children {
iftag("artist") { iftag("artist") {
parsedDesc.artist = text; parsedDesc.artist = text;
@ -297,7 +288,7 @@ void LyricsWikiaResultsModel::parseSongDetails(int row, const QByteArray &data)
.arg(assocDesc.artist, assocDesc.title); .arg(assocDesc.artist, assocDesc.title);
} }
// -> do not use parsed URL "as-is" in any case to avoid unintended requests // -> do not use parsed URL "as-is" in any case to avoid unintended requests
QUrl requestUrl(lyricsWikiaApiUrl()); auto requestUrl = lyricsWikiaApiUrl();
requestUrl.setPath(parsedUrl.path()); requestUrl.setPath(parsedUrl.path());
// -> initialize the actual request // -> initialize the actual request
auto *const reply = Utility::networkAccessManager().get(QNetworkRequest(requestUrl)); auto *const reply = Utility::networkAccessManager().get(QNetworkRequest(requestUrl));
@ -306,7 +297,7 @@ void LyricsWikiaResultsModel::parseSongDetails(int row, const QByteArray &data)
void LyricsWikiaResultsModel::handleLyricsReplyFinished(QNetworkReply *reply, int row) void LyricsWikiaResultsModel::handleLyricsReplyFinished(QNetworkReply *reply, int row)
{ {
QByteArray data; auto data = QByteArray();
if (auto *newReply = evaluateReplyResults(reply, data, true)) { if (auto *newReply = evaluateReplyResults(reply, data, true)) {
addReply(newReply, bind(&LyricsWikiaResultsModel::handleLyricsReplyFinished, this, newReply, row)); addReply(newReply, bind(&LyricsWikiaResultsModel::handleLyricsReplyFinished, this, newReply, row));
return; return;
@ -321,18 +312,15 @@ void LyricsWikiaResultsModel::handleLyricsReplyFinished(QNetworkReply *reply, in
void LyricsWikiaResultsModel::parseLyricsResults(int row, const QByteArray &data) void LyricsWikiaResultsModel::parseLyricsResults(int row, const QByteArray &data)
{ {
// find associated result/desc
if (row >= m_results.size()) { if (row >= m_results.size()) {
m_errorList << tr("Internal error: context for LyricsWikia page reply invalid"); m_errorList << tr("Internal error: context for LyricsWikia page reply invalid");
setResultsAvailable(true); setResultsAvailable(true);
return; return;
} }
SongDescription &assocDesc = m_results[row];
// convert data to QString
const QString html(data);
// parse lyrics from HTML // parse lyrics from HTML
auto &assocDesc = m_results[row];
const auto html = QString(data);
const auto lyricsStart = html.indexOf(QLatin1String("<div class='lyricbox'>")); const auto lyricsStart = html.indexOf(QLatin1String("<div class='lyricbox'>"));
if (lyricsStart < 0) { if (lyricsStart < 0) {
m_errorList << tr("Song details requested for %1/%2 do not contain lyrics").arg(assocDesc.artist, assocDesc.title); m_errorList << tr("Song details requested for %1/%2 do not contain lyrics").arg(assocDesc.artist, assocDesc.title);
@ -340,7 +328,7 @@ void LyricsWikiaResultsModel::parseLyricsResults(int row, const QByteArray &data
return; return;
} }
const auto lyricsEnd = html.indexOf(QLatin1String("<div class='lyricsbreak'></div>"), lyricsStart); const auto lyricsEnd = html.indexOf(QLatin1String("<div class='lyricsbreak'></div>"), lyricsStart);
QTextDocument textDoc; auto textDoc = QTextDocument();
textDoc.setHtml(html.mid(lyricsStart, (lyricsEnd > lyricsStart) ? (lyricsEnd - lyricsStart) : -1)); textDoc.setHtml(html.mid(lyricsStart, (lyricsEnd > lyricsStart) ? (lyricsEnd - lyricsStart) : -1));
assocDesc.lyrics = textDoc.toPlainText(); assocDesc.lyrics = textDoc.toPlainText();
@ -350,7 +338,7 @@ void LyricsWikiaResultsModel::parseLyricsResults(int row, const QByteArray &data
void LyricsWikiaResultsModel::handleAlbumDetailsReplyFinished(QNetworkReply *reply, int row) void LyricsWikiaResultsModel::handleAlbumDetailsReplyFinished(QNetworkReply *reply, int row)
{ {
QByteArray data; auto data = QByteArray();
if (auto *newReply = evaluateReplyResults(reply, data, true)) { if (auto *newReply = evaluateReplyResults(reply, data, true)) {
addReply(newReply, bind(&LyricsWikiaResultsModel::handleAlbumDetailsReplyFinished, this, newReply, row)); addReply(newReply, bind(&LyricsWikiaResultsModel::handleAlbumDetailsReplyFinished, this, newReply, row));
} else { } else {
@ -367,19 +355,16 @@ void LyricsWikiaResultsModel::parseAlbumDetailsAndFetchCover(int row, const QByt
return; return;
} }
// find associated result/desc
if (row >= m_results.size()) { if (row >= m_results.size()) {
m_errorList << tr("Internal error: context for LyricsWikia page reply invalid"); m_errorList << tr("Internal error: context for LyricsWikia page reply invalid");
setFetchingCover(false); setFetchingCover(false);
setResultsAvailable(true); setResultsAvailable(true);
return; return;
} }
SongDescription &assocDesc = m_results[row];
// convert data to QString
const auto html = QString(data);
// parse cover URL from HTML // parse cover URL from HTML
auto &assocDesc = m_results[row];
const auto html = QString(data);
const auto coverDivStart = html.indexOf(QLatin1String("<div class=\"plainlinks\" style=\"clear:right; float:right;")) + 56; const auto coverDivStart = html.indexOf(QLatin1String("<div class=\"plainlinks\" style=\"clear:right; float:right;")) + 56;
if (coverDivStart > 56) { if (coverDivStart > 56) {
const auto coverHrefStart = html.indexOf(QLatin1String("href=\""), coverDivStart) + 6; const auto coverHrefStart = html.indexOf(QLatin1String("href=\""), coverDivStart) + 6;
@ -410,31 +395,28 @@ QUrl LyricsWikiaResultsModel::webUrl(const QModelIndex &index)
return QUrl(); return QUrl();
} }
SongDescription &desc = m_results[index.row()]; auto &desc = m_results[index.row()];
lazyInitializeLyricsWikiaSongId(desc); lazyInitializeLyricsWikiaSongId(desc);
// return URL // return URL
QUrl url(lyricsWikiaApiUrl()); auto url = lyricsWikiaApiUrl();
url.setPath(QStringLiteral("/wiki/") + desc.songId); url.setPath(QStringLiteral("/wiki/") + desc.songId);
return url; return url;
} }
QueryResultsModel *queryLyricsWikia(SongDescription &&songDescription) QueryResultsModel *queryLyricsWikia(SongDescription &&songDescription)
{ {
// compose URL auto query = QUrlQuery();
QUrlQuery query;
query.addQueryItem(QStringLiteral("func"), QStringLiteral("getArtist")); query.addQueryItem(QStringLiteral("func"), QStringLiteral("getArtist"));
query.addQueryItem(QStringLiteral("fmt"), QStringLiteral("xml")); query.addQueryItem(QStringLiteral("fmt"), QStringLiteral("xml"));
query.addQueryItem(QStringLiteral("fixXML"), QString()); query.addQueryItem(QStringLiteral("fixXML"), QString());
query.addQueryItem(QStringLiteral("artist"), songDescription.artist); query.addQueryItem(QStringLiteral("artist"), songDescription.artist);
QUrl url(lyricsWikiaApiUrl()); auto url = lyricsWikiaApiUrl();
url.setQuery(query); url.setQuery(query);
return new LyricsWikiaResultsModel(std::move(songDescription), Utility::networkAccessManager().get(QNetworkRequest(url)));
// NOTE: Only getArtist seems to work, so artist must be specified and filtering must // NOTE: Only getArtist seems to work, so artist must be specified and filtering must
// be done manually when parsing results. // be done manually when parsing results.
// make request
return new LyricsWikiaResultsModel(std::move(songDescription), Utility::networkAccessManager().get(QNetworkRequest(url)));
} }
} // namespace QtGui } // namespace QtGui

View File

@ -11,7 +11,7 @@ class LyricsWikiaResultsModel : public HttpResultsModel {
Q_OBJECT Q_OBJECT
public: public:
LyricsWikiaResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply); explicit LyricsWikiaResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply);
bool fetchCover(const QModelIndex &index) override; bool fetchCover(const QModelIndex &index) override;
bool fetchLyrics(const QModelIndex &index) override; bool fetchLyrics(const QModelIndex &index) override;
QUrl webUrl(const QModelIndex &index) override; QUrl webUrl(const QModelIndex &index) override;

View File

@ -37,32 +37,25 @@ bool MakeItPersonalResultsModel::fetchLyrics(const QModelIndex &index)
void MakeItPersonalResultsModel::parseInitialResults(const QByteArray &data) void MakeItPersonalResultsModel::parseInitialResults(const QByteArray &data)
{ {
// prepare parsing meta data
beginResetModel(); beginResetModel();
m_results.clear(); m_results.clear();
auto desc = m_initialDescription;
SongDescription desc = m_initialDescription;
desc.songId = m_initialDescription.artist + m_initialDescription.title; desc.songId = m_initialDescription.artist + m_initialDescription.title;
desc.artistId = m_initialDescription.artist; desc.artistId = m_initialDescription.artist;
desc.lyrics = QString::fromUtf8(data).trimmed(); desc.lyrics = QString::fromUtf8(data).trimmed();
if (desc.lyrics != QLatin1String("Sorry, We don't have lyrics for this song yet.")) { if (desc.lyrics != QLatin1String("Sorry, We don't have lyrics for this song yet.")) {
m_results << std::move(desc); m_results << std::move(desc);
} }
// promote changes
endResetModel(); endResetModel();
} }
QueryResultsModel *queryMakeItPersonal(SongDescription &&songDescription) QueryResultsModel *queryMakeItPersonal(SongDescription &&songDescription)
{ {
// compose URL auto query = QUrlQuery();
QUrlQuery query;
query.addQueryItem(QStringLiteral("artist"), songDescription.artist); query.addQueryItem(QStringLiteral("artist"), songDescription.artist);
query.addQueryItem(QStringLiteral("title"), songDescription.title); query.addQueryItem(QStringLiteral("title"), songDescription.title);
QUrl url(makeItPersonalApiUrl()); auto url = makeItPersonalApiUrl();
url.setQuery(query); url.setQuery(query);
// make request
return new MakeItPersonalResultsModel(std::move(songDescription), Utility::networkAccessManager().get(QNetworkRequest(url))); return new MakeItPersonalResultsModel(std::move(songDescription), Utility::networkAccessManager().get(QNetworkRequest(url)));
} }

View File

@ -11,7 +11,7 @@ class MakeItPersonalResultsModel : public HttpResultsModel {
Q_OBJECT Q_OBJECT
public: public:
MakeItPersonalResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply); explicit MakeItPersonalResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply);
bool fetchLyrics(const QModelIndex &index) override; bool fetchLyrics(const QModelIndex &index) override;
protected: protected:

View File

@ -40,15 +40,12 @@ MusicBrainzResultsModel::MusicBrainzResultsModel(SongDescription &&initialSongDe
bool MusicBrainzResultsModel::fetchCover(const QModelIndex &index) bool MusicBrainzResultsModel::fetchCover(const QModelIndex &index)
{ {
// FIXME: avoid code duplication with lyricswikia.cpp
// find song description
if (index.parent().isValid() || index.row() >= m_results.size()) { if (index.parent().isValid() || index.row() >= m_results.size()) {
return true; return true;
} }
SongDescription &desc = m_results[index.row()];
// skip if cover is already available // skip if cover is already available
auto &desc = m_results[index.row()];
if (!desc.cover.isEmpty()) { if (!desc.cover.isEmpty()) {
return true; return true;
} }
@ -84,17 +81,16 @@ QUrl MusicBrainzResultsModel::webUrl(const QModelIndex &index)
void MusicBrainzResultsModel::parseInitialResults(const QByteArray &data) void MusicBrainzResultsModel::parseInitialResults(const QByteArray &data)
{ {
// prepare parsing MusicBrainz meta data
beginResetModel(); beginResetModel();
m_results.clear(); m_results.clear();
// store all song information (called recordings by MusicBrainz) // store all song information (called recordings by MusicBrainz)
vector<SongDescription> recordings; auto recordings = std::vector<SongDescription>();
// store all albums/collections (called releases by MusicBrainz) for a song // store all albums/collections (called releases by MusicBrainz) for a song
unordered_map<QString, vector<SongDescription>> releasesByRecording; auto releasesByRecording = std::unordered_map<QString, std::vector<SongDescription>>();
// parse XML tree // parse XML tree
QXmlStreamReader xmlReader(data); auto xmlReader = QXmlStreamReader(data);
// clang-format off // clang-format off
#include <qtutilities/misc/xmlparsermacros.h> #include <qtutilities/misc/xmlparsermacros.h>
children { children {
@ -103,7 +99,7 @@ void MusicBrainzResultsModel::parseInitialResults(const QByteArray &data)
iftag("recording-list") { iftag("recording-list") {
children { children {
iftag("recording") { iftag("recording") {
SongDescription currentDescription(attribute("id").toString()); auto currentDescription = SongDescription(attribute("id").toString());
children { children {
iftag("title") { iftag("title") {
currentDescription.title = text; currentDescription.title = text;
@ -130,7 +126,7 @@ void MusicBrainzResultsModel::parseInitialResults(const QByteArray &data)
} eliftag("release-list") { } eliftag("release-list") {
children { children {
iftag("release") { iftag("release") {
SongDescription releaseInfo; auto releaseInfo = SongDescription();
releaseInfo.albumId = attribute("id").toString(); releaseInfo.albumId = attribute("id").toString();
children { children {
iftag("title") { iftag("title") {
@ -220,7 +216,7 @@ void MusicBrainzResultsModel::parseInitialResults(const QByteArray &data)
// populate results // populate results
// -> create a song for each recording/release combination and group those songs by their releases sorted ascendingly from oldest to latest // -> create a song for each recording/release combination and group those songs by their releases sorted ascendingly from oldest to latest
map<QString, vector<SongDescription>> recordingsByRelease; auto recordingsByRelease = std::map<QString, std::vector<SongDescription>>();
for (const auto &recording : recordings) { for (const auto &recording : recordings) {
const auto &releases = releasesByRecording[recording.songId]; const auto &releases = releasesByRecording[recording.songId];
for (const auto &release : releases) { for (const auto &release : releases) {
@ -272,7 +268,6 @@ void MusicBrainzResultsModel::parseInitialResults(const QByteArray &data)
m_errorList << xmlReader.errorString(); m_errorList << xmlReader.errorString();
} }
// promote changes
endResetModel(); endResetModel();
} }
// clang-format on // clang-format on
@ -281,8 +276,7 @@ QueryResultsModel *queryMusicBrainz(SongDescription &&songDescription)
{ {
static const auto defaultMusicBrainzUrl(QStringLiteral("https://musicbrainz.org/ws/2/recording/")); static const auto defaultMusicBrainzUrl(QStringLiteral("https://musicbrainz.org/ws/2/recording/"));
// compose parts auto parts = QStringList();
QStringList parts;
parts.reserve(4); parts.reserve(4);
if (!songDescription.title.isEmpty()) { if (!songDescription.title.isEmpty()) {
parts << QChar('\"') % songDescription.title % QChar('\"'); parts << QChar('\"') % songDescription.title % QChar('\"');
@ -297,15 +291,12 @@ QueryResultsModel *queryMusicBrainz(SongDescription &&songDescription)
parts << QStringLiteral("number:") + QString::number(songDescription.track); parts << QStringLiteral("number:") + QString::number(songDescription.track);
} }
// compose URL
const auto &musicBrainzUrl = Settings::values().dbQuery.musicBrainzUrl; const auto &musicBrainzUrl = Settings::values().dbQuery.musicBrainzUrl;
QUrl url(musicBrainzUrl.isEmpty() ? defaultMusicBrainzUrl : (musicBrainzUrl + QStringLiteral("/recording/"))); auto url = QUrl(musicBrainzUrl.isEmpty() ? defaultMusicBrainzUrl : (musicBrainzUrl + QStringLiteral("/recording/")));
QUrlQuery query; auto query = QUrlQuery();
query.addQueryItem(QStringLiteral("query"), parts.join(QStringLiteral(" AND "))); query.addQueryItem(QStringLiteral("query"), parts.join(QStringLiteral(" AND ")));
url.setQuery(query); url.setQuery(query);
auto request = QNetworkRequest(url);
// make request
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("Mozilla/5.0 (X11; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0")); request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("Mozilla/5.0 (X11; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0"));
return new MusicBrainzResultsModel(std::move(songDescription), Utility::networkAccessManager().get(request)); return new MusicBrainzResultsModel(std::move(songDescription), Utility::networkAccessManager().get(request));
} }

View File

@ -15,7 +15,7 @@ private:
enum What { MusicBrainzMetaData, CoverArt }; enum What { MusicBrainzMetaData, CoverArt };
public: public:
MusicBrainzResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply); explicit MusicBrainzResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply);
bool fetchCover(const QModelIndex &index) override; bool fetchCover(const QModelIndex &index) override;
QUrl webUrl(const QModelIndex &index) override; QUrl webUrl(const QModelIndex &index) override;

156
dbquery/tekstowo.cpp Normal file
View File

@ -0,0 +1,156 @@
#include "./tekstowo.h"
#include "../application/settings.h"
#include "../misc/networkaccessmanager.h"
#include "../misc/utility.h"
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QTextDocumentFragment>
#include <QUrl>
#include <functional>
using namespace std;
using namespace std::placeholders;
using namespace Utility;
namespace QtGui {
static const auto defaultTekstowoUrl = QStringLiteral("https://www.tekstowo.pl");
static QUrl tekstowoUrl()
{
const auto &url = Settings::values().dbQuery.tekstowoUrl;
return QUrl(url.isEmpty() ? defaultTekstowoUrl : url);
}
TekstowoResultsModel::TekstowoResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply)
: HttpResultsModel(std::move(initialSongDescription), reply)
{
}
bool TekstowoResultsModel::fetchLyrics(const QModelIndex &index)
{
if ((index.parent().isValid() || index.row() >= m_results.size()) && !m_results[index.row()].lyrics.isEmpty()) {
return true;
}
const auto url = webUrl(index);
if (url.isEmpty()) {
m_errorList << tr("Unable to fetch lyrics: web URL is unknown.");
emit resultsAvailable();
return true;
}
auto *reply = Utility::networkAccessManager().get(QNetworkRequest(url));
addReply(reply, bind(&TekstowoResultsModel::handleLyricsReplyFinished, this, reply, index.row()));
return false;
}
void TekstowoResultsModel::parseInitialResults(const QByteArray &data)
{
beginResetModel();
m_results.clear();
auto dropLast = false;
auto hasExactMatch = false;
auto exactMatch = QList<SongDescription>::size_type();
for (auto index = exactMatch; index >= 0;) {
const auto linkStart = data.indexOf("<a href=\"/piosenka,", index);
if (linkStart < 0) {
break;
}
const auto hrefStart = linkStart + 9;
const auto hrefEnd = data.indexOf("\"", hrefStart + 1);
if (hrefEnd <= hrefStart) {
break;
}
const auto linkEnd = data.indexOf("</a>", hrefEnd);
if (linkEnd < linkStart) {
break;
}
index = linkEnd + 4;
auto linkText = QTextDocumentFragment::fromHtml(QString::fromUtf8(data.begin() + linkStart, linkEnd + 3 - linkStart)).toPlainText().trimmed();
auto titleStart = linkText.indexOf(QLatin1String(" - "));
auto &songDetails = dropLast ? m_results.back() : m_results.emplace_back();
songDetails.songId = QTextDocumentFragment::fromHtml(QString::fromUtf8(data.begin() + hrefStart, hrefEnd - hrefStart)).toPlainText();
if (titleStart > -1) {
songDetails.artist = linkText.mid(0, titleStart);
if (songDetails.artist != m_initialDescription.artist) {
dropLast = true;
continue;
}
songDetails.title = linkText.mid(titleStart + 3);
} else {
songDetails.title = std::move(linkText);
}
if (!hasExactMatch && songDetails.title == m_initialDescription.title) {
hasExactMatch = true;
exactMatch = m_results.size() - 1;
}
dropLast = false;
}
if (dropLast) {
m_results.pop_back();
}
// ensure the first exact match for the song title is placed first
if (hasExactMatch && exactMatch != 0) {
std::swap(m_results[exactMatch], m_results[0]);
}
endResetModel();
}
void TekstowoResultsModel::handleLyricsReplyFinished(QNetworkReply *reply, int row)
{
auto data = QByteArray();
if (auto *newReply = evaluateReplyResults(reply, data, true)) {
addReply(newReply, bind(&TekstowoResultsModel::handleLyricsReplyFinished, this, newReply, row));
return;
}
if (!data.isEmpty()) {
parseLyricsResults(row, data);
}
if (!m_resultsAvailable) {
setResultsAvailable(true);
}
}
void TekstowoResultsModel::parseLyricsResults(int row, const QByteArray &data)
{
if (row >= m_results.size()) {
m_errorList << tr("Internal error: context for Tekstowo page reply invalid");
setResultsAvailable(true);
return;
}
auto lyricsStart = data.indexOf("<div class=\"inner-text\">");
if (lyricsStart < 0) {
const auto &assocDesc = m_results[row];
m_errorList << tr("Song details requested for %1/%2 do not contain lyrics").arg(assocDesc.artist, assocDesc.title);
setResultsAvailable(true);
return;
}
const auto lyricsEnd = data.indexOf("</div>", lyricsStart += 24); // hopefully lyrics don't contain nested </div>
m_results[row].lyrics = QTextDocumentFragment::fromHtml(
QString::fromUtf8(data.data() + lyricsStart, lyricsEnd > -1 ? lyricsEnd - lyricsStart : data.size() - lyricsStart))
.toPlainText()
.trimmed();
setResultsAvailable(true);
emit lyricsAvailable(index(row, 0));
}
QUrl TekstowoResultsModel::webUrl(const QModelIndex &index)
{
if (index.parent().isValid() || index.row() >= results().size()) {
return QUrl();
}
auto url = tekstowoUrl();
url.setPath(m_results[index.row()].songId);
return url;
}
QueryResultsModel *queryTekstowo(SongDescription &&songDescription)
{
auto url = tekstowoUrl();
url.setPath(QStringLiteral("/szukaj,wykonawca,%1,tytul,%2.html").arg(songDescription.artist, songDescription.title));
return new TekstowoResultsModel(std::move(songDescription), Utility::networkAccessManager().get(QNetworkRequest(url)));
}
} // namespace QtGui

28
dbquery/tekstowo.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef QTGUI_TEKSTOWO_H
#define QTGUI_TEKSTOWO_H
#include "./dbquery.h"
#include <map>
namespace QtGui {
class TekstowoResultsModel : public HttpResultsModel {
Q_OBJECT
public:
explicit TekstowoResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply);
bool fetchLyrics(const QModelIndex &index) override;
QUrl webUrl(const QModelIndex &index) override;
protected:
void parseInitialResults(const QByteArray &data) override;
private:
void handleLyricsReplyFinished(QNetworkReply *reply, int row);
void parseLyricsResults(int row, const QByteArray &data);
};
} // namespace QtGui
#endif // QTGUI_TEKSTOWO_H

View File

@ -7,6 +7,7 @@
#include "../dbquery/dbquery.h" #include "../dbquery/dbquery.h"
#include "../misc/utility.h" #include "../misc/utility.h"
#include "resources/config.h"
#include "ui_dbquerywidget.h" #include "ui_dbquerywidget.h"
#include <tagparser/tag.h> #include <tagparser/tag.h>
@ -47,6 +48,13 @@ DbQueryWidget::DbQueryWidget(TagEditorWidget *tagEditorWidget, QWidget *parent)
, m_coverIndex(-1) , m_coverIndex(-1)
, m_lyricsIndex(-1) , m_lyricsIndex(-1)
, m_menu(new QMenu(parent)) , m_menu(new QMenu(parent))
, m_insertPresentDataAction(nullptr)
, m_searchMusicBrainzAction(nullptr)
, m_searchLyricsWikiaAction(nullptr)
, m_searchMakeItPersonalAction(nullptr)
, m_searchTekstowoAction(nullptr)
, m_lastSearchAction(nullptr)
, m_refreshAutomaticallyAction(nullptr)
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
updateStyleSheet(); updateStyleSheet();
@ -72,20 +80,27 @@ DbQueryWidget::DbQueryWidget(TagEditorWidget *tagEditorWidget, QWidget *parent)
// setup menu // setup menu
const auto searchIcon = QIcon::fromTheme(QStringLiteral("search")); const auto searchIcon = QIcon::fromTheme(QStringLiteral("search"));
const auto enableLegacyProvider = qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_ENABLE_LEGACY_METADATA_PROVIDERS");
m_menu->setTitle(tr("New search")); m_menu->setTitle(tr("New search"));
m_menu->setIcon(searchIcon); m_menu->setIcon(searchIcon);
m_searchMusicBrainzAction = m_lastSearchAction = m_menu->addAction(tr("Query MusicBrainz")); m_searchMusicBrainzAction = m_lastSearchAction = m_menu->addAction(tr("Query MusicBrainz"));
m_searchMusicBrainzAction->setIcon(searchIcon); m_searchMusicBrainzAction->setIcon(searchIcon);
m_searchMusicBrainzAction->setShortcut(QKeySequence(Qt::CTRL, Qt::Key_M)); m_searchMusicBrainzAction->setShortcut(QKeySequence(Qt::CTRL, Qt::Key_M));
connect(m_searchMusicBrainzAction, &QAction::triggered, this, &DbQueryWidget::searchMusicBrainz); connect(m_searchMusicBrainzAction, &QAction::triggered, this, &DbQueryWidget::searchMusicBrainz);
m_searchLyricsWikiaAction = m_menu->addAction(tr("Query LyricsWikia")); if (enableLegacyProvider) {
m_searchLyricsWikiaAction->setIcon(searchIcon); m_searchLyricsWikiaAction = m_menu->addAction(tr("Query LyricsWikia"));
m_searchLyricsWikiaAction->setShortcut(QKeySequence(Qt::CTRL, Qt::Key_L)); m_searchLyricsWikiaAction->setIcon(searchIcon);
connect(m_searchLyricsWikiaAction, &QAction::triggered, this, &DbQueryWidget::searchLyricsWikia); m_searchLyricsWikiaAction->setShortcut(QKeySequence(Qt::CTRL, Qt::Key_L));
m_searchMakeItPersonalAction = m_menu->addAction(tr("Query makeitpersonal")); connect(m_searchLyricsWikiaAction, &QAction::triggered, this, &DbQueryWidget::searchLyricsWikia);
m_searchMakeItPersonalAction->setIcon(searchIcon); m_searchMakeItPersonalAction = m_menu->addAction(tr("Query makeitpersonal"));
m_searchMakeItPersonalAction->setShortcut(QKeySequence(Qt::CTRL, Qt::Key_K)); m_searchMakeItPersonalAction->setIcon(searchIcon);
connect(m_searchMakeItPersonalAction, &QAction::triggered, this, &DbQueryWidget::searchMakeItPersonal); m_searchMakeItPersonalAction->setShortcut(QKeySequence(Qt::CTRL, Qt::Key_K));
connect(m_searchMakeItPersonalAction, &QAction::triggered, this, &DbQueryWidget::searchMakeItPersonal);
}
m_searchTekstowoAction = m_menu->addAction(tr("Query Tekstowo"));
m_searchTekstowoAction->setIcon(searchIcon);
m_searchTekstowoAction->setShortcut(QKeySequence(Qt::CTRL, Qt::Key_T));
connect(m_searchTekstowoAction, &QAction::triggered, this, &DbQueryWidget::searchTekstowo);
m_menu->addSeparator(); m_menu->addSeparator();
m_insertPresentDataAction = m_menu->addAction(tr("Use present data as search criteria")); m_insertPresentDataAction = m_menu->addAction(tr("Use present data as search criteria"));
m_insertPresentDataAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); m_insertPresentDataAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
@ -150,7 +165,7 @@ void DbQueryWidget::insertSearchTermsFromTagEdit(TagEdit *tagEdit, bool songSpec
m_ui->titleLineEdit->setText(newTitle); m_ui->titleLineEdit->setText(newTitle);
somethingChanged = true; somethingChanged = true;
} }
if (m_lastSearchAction != m_searchMakeItPersonalAction) { if (m_lastSearchAction != m_searchTekstowoAction && m_lastSearchAction != m_searchMakeItPersonalAction) {
const auto newTrackNumber = tagEdit->trackNumber(); const auto newTrackNumber = tagEdit->trackNumber();
if (m_ui->trackSpinBox->value() != newTrackNumber) { if (m_ui->trackSpinBox->value() != newTrackNumber) {
m_ui->trackSpinBox->setValue(newTrackNumber); m_ui->trackSpinBox->setValue(newTrackNumber);
@ -246,6 +261,30 @@ void DbQueryWidget::searchMakeItPersonal()
useQueryResults(queryMakeItPersonal(currentSongDescription())); useQueryResults(queryMakeItPersonal(currentSongDescription()));
} }
void DbQueryWidget::searchTekstowo()
{
m_lastSearchAction = m_searchMakeItPersonalAction;
// check whether enough search terms are supplied
if (m_ui->artistLineEdit->text().isEmpty() || m_ui->titleLineEdit->text().isEmpty()) {
m_ui->notificationLabel->setNotificationType(NotificationType::Critical);
m_ui->notificationLabel->setText(tr("Insufficient search criteria supplied - artist and title are mandatory"));
return;
}
// delete current model
m_ui->resultsTreeView->setModel(nullptr);
delete m_model;
// show status
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
m_ui->notificationLabel->setText(tr("Retrieving lyrics from Tekstowo ..."));
setStatus(false);
// do actual query
useQueryResults(queryTekstowo(currentSongDescription()));
}
void DbQueryWidget::abortSearch() void DbQueryWidget::abortSearch()
{ {
if (!m_model) { if (!m_model) {
@ -331,7 +370,9 @@ void DbQueryWidget::setStatus(bool aborted)
{ {
m_ui->abortPushButton->setVisible(!aborted); m_ui->abortPushButton->setVisible(!aborted);
m_searchMusicBrainzAction->setEnabled(aborted); m_searchMusicBrainzAction->setEnabled(aborted);
m_searchLyricsWikiaAction->setEnabled(aborted); if (m_searchLyricsWikiaAction) {
m_searchLyricsWikiaAction->setEnabled(aborted);
}
m_ui->applyPushButton->setVisible(aborted); m_ui->applyPushButton->setVisible(aborted);
} }

View File

@ -39,6 +39,7 @@ public Q_SLOTS:
void searchMusicBrainz(); void searchMusicBrainz();
void searchLyricsWikia(); void searchLyricsWikia();
void searchMakeItPersonal(); void searchMakeItPersonal();
void searchTekstowo();
void abortSearch(); void abortSearch();
void applySelectedResults(); void applySelectedResults();
void applySpecifiedResults(const QModelIndex &modelIndex); void applySpecifiedResults(const QModelIndex &modelIndex);
@ -82,6 +83,7 @@ private:
QAction *m_searchMusicBrainzAction; QAction *m_searchMusicBrainzAction;
QAction *m_searchLyricsWikiaAction; QAction *m_searchLyricsWikiaAction;
QAction *m_searchMakeItPersonalAction; QAction *m_searchMakeItPersonalAction;
QAction *m_searchTekstowoAction;
QAction *m_lastSearchAction; QAction *m_lastSearchAction;
QAction *m_refreshAutomaticallyAction; QAction *m_refreshAutomaticallyAction;
QPoint m_contextMenuPos; QPoint m_contextMenuPos;

View File

@ -1,12 +1,12 @@
import * as http from "http.js" const cache = {};
function waitFor(signal) { function waitFor(signal) {
signal.connect(() => { utility.exit(); }); signal.connect(() => { utility.exit(); });
utility.exec(); utility.exec();
} }
function queryMakeItPersonal(searchCriteria) { function queryProvider(provider, searchCriteria) {
const lyricsModel = utility.queryMakeItPersonal(searchCriteria); const lyricsModel = utility["query" + provider](searchCriteria);
if (!lyricsModel.areResultsAvailable) { if (!lyricsModel.areResultsAvailable) {
waitFor(lyricsModel.resultsAvailable); waitFor(lyricsModel.resultsAvailable);
} }
@ -20,8 +20,21 @@ function queryMakeItPersonal(searchCriteria) {
return lyrics; return lyrics;
} }
function queryProviders(providers, searchCriteria) {
for (const provider of providers) {
const res = queryProvider(provider, searchCriteria);
if (res) {
return res;
}
}
}
export function queryLyrics(searchCriteria) { export function queryLyrics(searchCriteria) {
return queryMakeItPersonal(searchCriteria); const cacheKey = searchCriteria.title + "_" + searchCriteria.artist;
const cachedValue = cache[cacheKey];
return cachedValue
? cachedValue
: cache[cacheKey] = queryProviders(["Tekstowo", "MakeItPersonal"], searchCriteria);
} }