Improve coding style and formatting in dbquery code

This commit is contained in:
Martchus 2018-08-19 15:11:46 +02:00
parent a32db9e33d
commit 8e59c5b24f
4 changed files with 473 additions and 504 deletions

View File

@ -59,36 +59,37 @@ QUrl QueryResultsModel::webUrl(const QModelIndex &index)
TagValue QueryResultsModel::fieldValue(int row, KnownField knownField) const TagValue QueryResultsModel::fieldValue(int row, KnownField knownField) const
{ {
if (row < m_results.size()) { if (row >= m_results.size()) {
const SongDescription &res = m_results.at(row); return TagValue();
switch (knownField) { }
case KnownField::Title: const SongDescription &res = m_results.at(row);
returnValue(title); switch (knownField) {
case KnownField::Album: case KnownField::Title:
returnValue(album); returnValue(title);
case KnownField::Artist: case KnownField::Album:
returnValue(artist); returnValue(album);
case KnownField::Genre: case KnownField::Artist:
returnValue(genre); returnValue(artist);
case KnownField::Year: case KnownField::Genre:
returnValue(year); returnValue(genre);
case KnownField::TrackPosition: case KnownField::Year:
return TagValue(PositionInSet(res.track, res.totalTracks)); returnValue(year);
case KnownField::PartNumber: case KnownField::TrackPosition:
return TagValue(res.track); return TagValue(PositionInSet(res.track, res.totalTracks));
case KnownField::TotalParts: case KnownField::PartNumber:
return TagValue(res.totalTracks); return TagValue(res.track);
case KnownField::Cover: case KnownField::TotalParts:
if (!res.cover.isEmpty()) { return TagValue(res.totalTracks);
TagValue tagValue(res.cover.data(), static_cast<size_t>(res.cover.size()), TagDataType::Picture); case KnownField::Cover:
tagValue.setMimeType(containerMimeType(parseSignature(res.cover.data(), res.cover.size()))); if (!res.cover.isEmpty()) {
return tagValue; TagValue tagValue(res.cover.data(), static_cast<size_t>(res.cover.size()), TagDataType::Picture);
} tagValue.setMimeType(containerMimeType(parseSignature(res.cover.data(), res.cover.size())));
break; return tagValue;
case KnownField::Lyrics:
returnValue(lyrics);
default:;
} }
break;
case KnownField::Lyrics:
returnValue(lyrics);
default:;
} }
return TagValue(); return TagValue();
} }
@ -97,38 +98,39 @@ TagValue QueryResultsModel::fieldValue(int row, KnownField knownField) const
QVariant QueryResultsModel::data(const QModelIndex &index, int role) const 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()) {
const SongDescription &res = m_results.at(index.row()); return QVariant();
switch (role) { }
case Qt::DisplayRole: const SongDescription &res = m_results.at(index.row());
switch (index.column()) { switch (role) {
case TitleCol: case Qt::DisplayRole:
return res.title; switch (index.column()) {
case AlbumCol: case TitleCol:
return res.album; return res.title;
case ArtistCol: case AlbumCol:
return res.artist; return res.album;
case GenreCol: case ArtistCol:
return res.genre; return res.artist;
case YearCol: case GenreCol:
return res.year; return res.genre;
case TrackCol: case YearCol:
if (res.track) { return res.year;
return res.track; case TrackCol:
} else { if (res.track) {
return QString(); return res.track;
} } else {
case TotalTracksCol: return QString();
if (res.totalTracks) { }
return res.totalTracks; case TotalTracksCol:
} else { if (res.totalTracks) {
return QString(); return res.totalTracks;
} } else {
default:; return QString();
} }
break;
default:; default:;
} }
break;
default:;
} }
return QVariant(); return QVariant();
} }
@ -186,11 +188,12 @@ int QueryResultsModel::columnCount(const QModelIndex &parent) const
const QByteArray *QueryResultsModel::cover(const QModelIndex &index) const const QByteArray *QueryResultsModel::cover(const QModelIndex &index) const
{ {
if (!index.parent().isValid() && index.row() < m_results.size()) { if (!index.isValid() || index.row() >= m_results.size()) {
const QByteArray &cover = m_results.at(index.row()).cover; return nullptr;
if (!cover.isEmpty()) { }
return &cover; const auto &cover = m_results.at(index.row()).cover;
} if (!cover.isEmpty()) {
return &cover;
} }
return nullptr; return nullptr;
} }
@ -216,11 +219,12 @@ bool QueryResultsModel::fetchCover(const QModelIndex &index)
const QString *QueryResultsModel::lyrics(const QModelIndex &index) const const QString *QueryResultsModel::lyrics(const QModelIndex &index) const
{ {
if (!index.parent().isValid() && index.row() < m_results.size()) { if (!index.isValid() || index.row() >= m_results.size()) {
const QString &lyrics = m_results.at(index.row()).lyrics; return nullptr;
if (!lyrics.isEmpty()) { }
return &lyrics; const auto &lyrics = m_results.at(index.row()).lyrics;
} if (!lyrics.isEmpty()) {
return &lyrics;
} }
return nullptr; return nullptr;
} }
@ -268,16 +272,16 @@ HttpResultsModel::~HttpResultsModel()
*/ */
void HttpResultsModel::handleInitialReplyFinished() void HttpResultsModel::handleInitialReplyFinished()
{ {
auto *reply = static_cast<QNetworkReply *>(sender()); auto *const reply = static_cast<QNetworkReply *>(sender());
QByteArray data; QByteArray data;
if (auto *newReply = evaluateReplyResults(reply, data, false)) { if (auto *const newReply = evaluateReplyResults(reply, data, false)) {
addReply(newReply, this, &HttpResultsModel::handleInitialReplyFinished); addReply(newReply, this, &HttpResultsModel::handleInitialReplyFinished);
} else { return;
if (!data.isEmpty()) {
parseInitialResults(data);
}
setResultsAvailable(true); // update status, emit resultsAvailable()
} }
if (!data.isEmpty()) {
parseInitialResults(data);
}
setResultsAvailable(true); // update status, emit resultsAvailable()
} }
QNetworkReply *HttpResultsModel::evaluateReplyResults(QNetworkReply *reply, QByteArray &data, bool alwaysFollowRedirection) QNetworkReply *HttpResultsModel::evaluateReplyResults(QNetworkReply *reply, QByteArray &data, bool alwaysFollowRedirection)
@ -286,37 +290,35 @@ QNetworkReply *HttpResultsModel::evaluateReplyResults(QNetworkReply *reply, QByt
reply->deleteLater(); reply->deleteLater();
m_replies.removeAll(reply); m_replies.removeAll(reply);
if (reply->error() == QNetworkReply::NoError) { if (reply->error() != QNetworkReply::NoError) {
const QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (!redirectionTarget.isNull()) {
// there's a redirection available
// -> resolve new URL
const QUrl newUrl = reply->url().resolved(redirectionTarget.toUrl());
// -> ask user whether to follow redirection unless alwaysFollowRedirection is true
if (!alwaysFollowRedirection) {
const QString message
= tr("<p>Do you want to redirect form <i>%1</i> to <i>%2</i>?</p>").arg(reply->url().toString(), newUrl.toString());
alwaysFollowRedirection
= QMessageBox::question(nullptr, tr("Search"), message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes;
}
if (alwaysFollowRedirection) {
return networkAccessManager().get(QNetworkRequest(newUrl));
} else {
m_errorList << tr("Redirection to: ") + newUrl.toString();
return nullptr;
}
} else {
if ((data = reply->readAll()).isEmpty()) {
m_errorList << tr("Server replied no data.");
}
#ifdef DEBUG_BUILD
cerr << "Results from HTTP query:" << endl;
cerr << data.data() << endl;
#endif
}
} else {
m_errorList << reply->errorString(); m_errorList << reply->errorString();
return nullptr;
} }
const auto redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (redirectionTarget.isNull()) {
// read all data if it is not redirection
if ((data = reply->readAll()).isEmpty()) {
m_errorList << tr("Server replied no data.");
}
#ifdef DEBUG_BUILD
cerr << "Results from HTTP query:" << endl;
cerr << data.data() << endl;
#endif
return nullptr;
}
// there's a redirection available
// -> resolve new URL
const auto newUrl = reply->url().resolved(redirectionTarget.toUrl());
// -> ask user whether to follow redirection unless alwaysFollowRedirection is true
if (!alwaysFollowRedirection) {
const auto message = tr("<p>Do you want to redirect form <i>%1</i> to <i>%2</i>?</p>").arg(reply->url().toString(), newUrl.toString());
alwaysFollowRedirection = QMessageBox::question(nullptr, tr("Search"), message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes;
}
if (alwaysFollowRedirection) {
return networkAccessManager().get(QNetworkRequest(newUrl));
}
m_errorList << tr("Redirection to: ") + newUrl.toString();
return nullptr; return nullptr;
} }
@ -325,40 +327,41 @@ QNetworkReply *HttpResultsModel::evaluateReplyResults(QNetworkReply *reply, QByt
*/ */
void HttpResultsModel::abort() void HttpResultsModel::abort()
{ {
if (!m_replies.isEmpty()) { if (m_replies.isEmpty()) {
qDeleteAll(m_replies); return;
m_replies.clear();
// must update status manually because handleReplyFinished() won't be called anymore
m_errorList << tr("Aborted by user.");
setResultsAvailable(true);
} }
qDeleteAll(m_replies);
m_replies.clear();
// must update status manually because handleReplyFinished() won't be called anymore
m_errorList << tr("Aborted by user.");
setResultsAvailable(true);
} }
void HttpResultsModel::handleCoverReplyFinished(QNetworkReply *reply, const QString &albumId, int row) void HttpResultsModel::handleCoverReplyFinished(QNetworkReply *reply, const QString &albumId, int row)
{ {
QByteArray data; QByteArray data;
if (auto *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));
} else { return;
if (!data.isEmpty()) {
parseCoverResults(albumId, row, data);
}
setResultsAvailable(true);
} }
if (!data.isEmpty()) {
parseCoverResults(albumId, row, data);
}
setResultsAvailable(true);
} }
void HttpResultsModel::parseCoverResults(const QString &albumId, int row, const QByteArray &data) void HttpResultsModel::parseCoverResults(const QString &albumId, int row, const QByteArray &data)
{ {
// add cover -> determine album ID and row // add cover -> determine album ID and row
if (!albumId.isEmpty() && row < m_results.size()) { if (albumId.isEmpty() || row >= m_results.size()) {
if (!data.isEmpty()) {
m_coverData[albumId] = data;
m_results[row].cover = data;
emit coverAvailable(index(row, 0));
}
} else {
m_errorList << tr("Internal error: context for cover reply invalid"); m_errorList << tr("Internal error: context for cover reply invalid");
setResultsAvailable(true); setResultsAvailable(true);
return;
}
if (!data.isEmpty()) {
m_coverData[albumId] = data;
m_results[row].cover = data;
emit coverAvailable(index(row, 0));
} }
setFetchingCover(false); setFetchingCover(false);
} }

View File

@ -22,7 +22,7 @@ static const QString defaultLyricsWikiaUrl(QStringLiteral("https://lyrics.wikia.
QUrl lyricsWikiaApiUrl() QUrl lyricsWikiaApiUrl()
{ {
const QString &lyricsWikiaUrl = Settings::values().dbQuery.lyricsWikiaUrl; const auto &lyricsWikiaUrl = Settings::values().dbQuery.lyricsWikiaUrl;
return QUrl((lyricsWikiaUrl.isEmpty() ? defaultLyricsWikiaUrl : lyricsWikiaUrl) + QStringLiteral("/api.php")); return QUrl((lyricsWikiaUrl.isEmpty() ? defaultLyricsWikiaUrl : lyricsWikiaUrl) + QStringLiteral("/api.php"));
} }
@ -33,33 +33,35 @@ LyricsWikiaResultsModel::LyricsWikiaResultsModel(SongDescription &&initialSongDe
bool LyricsWikiaResultsModel::fetchCover(const QModelIndex &index) bool LyricsWikiaResultsModel::fetchCover(const QModelIndex &index)
{ {
if (!index.parent().isValid() && index.row() < m_results.size()) { if (index.parent().isValid() || index.row() >= m_results.size()) {
SongDescription &desc = m_results[index.row()]; return true;
if (!desc.cover.isEmpty()) { }
// cover is already available -> nothing to do SongDescription &desc = m_results[index.row()];
} else if (!desc.albumId.isEmpty()) { if (!desc.cover.isEmpty()) {
try { // cover is already available -> nothing to do
// the item belongs to an album which cover has already been fetched return true;
desc.cover = m_coverData.at(desc.albumId); }
} catch (const out_of_range &) { if (desc.albumId.isEmpty()) {
if (desc.coverUrl.isEmpty()) { m_errorList << tr("Unable to fetch cover: Album ID unknown");
// request the cover URL emit resultsAvailable();
auto *reply = requestAlbumDetails(desc); return true;
addReply(reply, bind(&LyricsWikiaResultsModel::handleAlbumDetailsReplyFinished, this, reply, index.row())); }
setFetchingCover(true); try {
return false; // the item belongs to an album which cover has already been fetched
} else { desc.cover = m_coverData.at(desc.albumId);
// request the cover art } catch (const out_of_range &) {
auto *reply = networkAccessManager().get(QNetworkRequest(QUrl(desc.coverUrl))); if (desc.coverUrl.isEmpty()) {
addReply(reply, bind(&LyricsWikiaResultsModel::handleCoverReplyFinished, this, reply, desc.albumId, index.row())); // request the cover URL
setFetchingCover(true); auto *const reply = requestAlbumDetails(desc);
return false; addReply(reply, bind(&LyricsWikiaResultsModel::handleAlbumDetailsReplyFinished, this, reply, index.row()));
} setFetchingCover(true);
}
} else { } else {
m_errorList << tr("Unable to fetch cover: Album ID unknown"); // request the cover art
emit resultsAvailable(); auto *const reply = networkAccessManager().get(QNetworkRequest(QUrl(desc.coverUrl)));
addReply(reply, bind(&LyricsWikiaResultsModel::handleCoverReplyFinished, this, reply, desc.albumId, index.row()));
setFetchingCover(true);
} }
return false;
} }
return true; return true;
} }
@ -90,42 +92,27 @@ void LyricsWikiaResultsModel::parseInitialResults(const QByteArray &data)
QXmlStreamReader xmlReader(data); QXmlStreamReader xmlReader(data);
// parse XML tree // parse XML tree
// clang-format off
#include <qtutilities/misc/xmlparsermacros.h> #include <qtutilities/misc/xmlparsermacros.h>
children children {
{ iftag("getArtistResponse") {
iftag("getArtistResponse")
{
QString artist; QString artist;
children children {
{ iftag("artist") {
iftag("artist")
{
artist = text; artist = text;
} } eliftag("albums") {
eliftag("albums") children {
{ iftag("albumResult") {
children
{
iftag("albumResult")
{
QString album, year; QString album, year;
QList<SongDescription> songs; QList<SongDescription> songs;
children children {
{ iftag("album") {
iftag("album")
{
album = text; album = text;
} } eliftag("year") {
eliftag("year")
{
year = text; year = text;
} } eliftag("songs") {
eliftag("songs") children {
{ iftag("item") {
children
{
iftag("item")
{
songs << SongDescription(); songs << SongDescription();
songs.back().title = text; songs.back().title = text;
songs.back().track = songs.size(); songs.back().track = songs.size();
@ -139,7 +126,7 @@ void LyricsWikiaResultsModel::parseInitialResults(const QByteArray &data)
if ((m_initialDescription.album.isEmpty() || m_initialDescription.album == album) if ((m_initialDescription.album.isEmpty() || m_initialDescription.album == album)
&& (m_initialDescription.year.isEmpty() || m_initialDescription.year == year) && (m_initialDescription.year.isEmpty() || m_initialDescription.year == year)
&& (!m_initialDescription.totalTracks || m_initialDescription.totalTracks == songs.size())) { && (!m_initialDescription.totalTracks || m_initialDescription.totalTracks == songs.size())) {
for (SongDescription &song : songs) { for (auto &song : songs) {
if ((m_initialDescription.title.isEmpty() || m_initialDescription.title == song.title) if ((m_initialDescription.title.isEmpty() || m_initialDescription.title == song.title)
&& (!m_initialDescription.track || m_initialDescription.track == song.track)) { && (!m_initialDescription.track || m_initialDescription.track == song.track)) {
song.album = album; song.album = album;
@ -166,6 +153,7 @@ void LyricsWikiaResultsModel::parseInitialResults(const QByteArray &data)
else_skip else_skip
} }
#include <qtutilities/misc/undefxmlparsermacros.h> #include <qtutilities/misc/undefxmlparsermacros.h>
// clang-format on
// check for parsing errors // check for parsing errors
switch (xmlReader.error()) { switch (xmlReader.error()) {
@ -230,25 +218,18 @@ void LyricsWikiaResultsModel::parseSongDetails(int row, const QByteArray &data)
QUrl parsedUrl; QUrl parsedUrl;
// parse XML tree // parse XML tree
// clang-format off
QXmlStreamReader xmlReader(data); QXmlStreamReader xmlReader(data);
#include <qtutilities/misc/xmlparsermacros.h> #include <qtutilities/misc/xmlparsermacros.h>
children children {
{ iftag("LyricsResult") {
iftag("LyricsResult")
{
SongDescription parsedDesc; SongDescription parsedDesc;
children children {
{ iftag("artist") {
iftag("artist")
{
parsedDesc.artist = text; parsedDesc.artist = text;
} } eliftag("song") {
eliftag("song")
{
parsedDesc.title = text; parsedDesc.title = text;
} } eliftag("url") {
eliftag("url")
{
parsedUrl = text; parsedUrl = text;
} }
else_skip else_skip
@ -264,6 +245,7 @@ void LyricsWikiaResultsModel::parseSongDetails(int row, const QByteArray &data)
else_skip else_skip
} }
#include <qtutilities/misc/undefxmlparsermacros.h> #include <qtutilities/misc/undefxmlparsermacros.h>
// clang-format on
// check for parsing errors // check for parsing errors
switch (xmlReader.error()) { switch (xmlReader.error()) {
@ -296,12 +278,12 @@ void LyricsWikiaResultsModel::handleLyricsReplyFinished(QNetworkReply *reply, in
QByteArray data; QByteArray data;
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));
} else { return;
if (!data.isEmpty()) {
parseLyricsResults(row, data);
}
setResultsAvailable(true);
} }
if (!data.isEmpty()) {
parseLyricsResults(row, data);
}
setResultsAvailable(true);
} }
void LyricsWikiaResultsModel::parseLyricsResults(int row, const QByteArray &data) void LyricsWikiaResultsModel::parseLyricsResults(int row, const QByteArray &data)
@ -318,13 +300,13 @@ void LyricsWikiaResultsModel::parseLyricsResults(int row, const QByteArray &data
const QString html(data); const QString html(data);
// parse lyrics from HTML // parse lyrics from HTML
const int 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);
setResultsAvailable(true); setResultsAvailable(true);
return; return;
} }
const int lyricsEnd = html.indexOf(QLatin1String("<div class='lyricsbreak'></div>"), lyricsStart); const auto lyricsEnd = html.indexOf(QLatin1String("<div class='lyricsbreak'></div>"), lyricsStart);
QTextDocument textDoc; QTextDocument textDoc;
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();
@ -384,7 +366,7 @@ void LyricsWikiaResultsModel::parseAlbumDetailsAndFetchCover(int row, const QByt
} }
// request the cover art // request the cover art
auto *reply = networkAccessManager().get(QNetworkRequest(QUrl(assocDesc.coverUrl))); auto *const reply = networkAccessManager().get(QNetworkRequest(QUrl(assocDesc.coverUrl)));
addReply(reply, bind(&LyricsWikiaResultsModel::handleCoverReplyFinished, this, reply, assocDesc.albumId, row)); addReply(reply, bind(&LyricsWikiaResultsModel::handleCoverReplyFinished, this, reply, assocDesc.albumId, row));
} }

View File

@ -25,25 +25,29 @@ MusicBrainzResultsModel::MusicBrainzResultsModel(SongDescription &&initialSongDe
bool MusicBrainzResultsModel::fetchCover(const QModelIndex &index) bool MusicBrainzResultsModel::fetchCover(const QModelIndex &index)
{ {
if (!index.parent().isValid() && index.row() < m_results.size()) { if (index.parent().isValid() || index.row() >= m_results.size()) {
SongDescription &desc = m_results[index.row()]; return true;
if (!desc.cover.isEmpty()) { }
// cover is already available -> nothing to do SongDescription &desc = m_results[index.row()];
} else if (!desc.albumId.isEmpty()) { if (!desc.cover.isEmpty()) {
try { // cover is already available -> nothing to do
// the item belongs to an album which cover has already been fetched return true;
desc.cover = m_coverData.at(desc.albumId); }
} catch (const out_of_range &) {
// request the cover art if (desc.albumId.isEmpty()) {
auto *reply = queryCoverArtArchive(desc.albumId); m_errorList << tr("Unable to fetch cover: Album ID unknown");
addReply(reply, bind(&MusicBrainzResultsModel::handleCoverReplyFinished, this, reply, desc.albumId, index.row())); emit resultsAvailable();
setFetchingCover(true); return true;
return false; }
} try {
} else { // the item belongs to an album which cover has already been fetched
m_errorList << tr("Unable to fetch cover: Album ID unknown"); desc.cover = m_coverData.at(desc.albumId);
emit resultsAvailable(); } catch (const out_of_range &) {
} // request the cover art
auto *const reply = queryCoverArtArchive(desc.albumId);
addReply(reply, bind(&MusicBrainzResultsModel::handleCoverReplyFinished, this, reply, desc.albumId, index.row()));
setFetchingCover(true);
return false;
} }
return true; return true;
} }
@ -64,90 +68,61 @@ void MusicBrainzResultsModel::parseInitialResults(const QByteArray &data)
QXmlStreamReader xmlReader(data); QXmlStreamReader xmlReader(data);
// parse XML tree // parse XML tree
// clang-format off
#include <qtutilities/misc/xmlparsermacros.h> #include <qtutilities/misc/xmlparsermacros.h>
children children {
{ iftag("metadata") {
iftag("metadata") children {
{ iftag("recording-list") {
children children {
{ iftag("recording") {
iftag("recording-list")
{
children
{
iftag("recording")
{
SongDescription currentDescription(attribute("id").toString()); SongDescription currentDescription(attribute("id").toString());
children{ iftag("title"){ currentDescription.title = text; children {
} iftag("title") {
eliftag("artist-credit") currentDescription.title = text;
{ } eliftag("artist-credit") {
children children {
{ iftag("name-credit") {
iftag("name-credit") children {
{ iftag("artist") {
children children {
{ iftag("name") {
iftag("artist") currentDescription.artist = text;
{ }
children else_skip
{ }
iftag("name")
{
currentDescription.artist = text;
} }
else_skip else_skip
} }
} }
else_skip else_skip
} }
} } eliftag("release-list") {
else_skip children {
} iftag("release") {
} if (currentDescription.albumId.isEmpty()) {
eliftag("release-list") currentDescription.albumId = attribute("id").toString();
{ }
children children {
{ iftag("title") {
iftag("release") currentDescription.album = text;
{ } eliftag("date") {
if (currentDescription.albumId.isEmpty()) { currentDescription.year = text;
currentDescription.albumId = attribute("id").toString(); } eliftag("medium-list") {
} children {
children iftag("medium") {
{ children {
iftag("title") iftag("position") {
{ currentDescription.disk = text.toInt();
currentDescription.album = text; } eliftag("track-list") {
} currentDescription.totalTracks = attribute("count").toInt();
eliftag("date") children {
{ iftag("track") {
currentDescription.year = text; children {
} iftag("number") {
eliftag("medium-list") currentDescription.track = text.toInt();
{ } else_skip
children }
{
iftag("medium")
{
children
{
iftag("position")
{
currentDescription.disk = text.toUInt();
}
eliftag("track-list")
{
currentDescription.totalTracks = attribute("count").toUInt();
children
{
iftag("track")
{
children
{
iftag("number")
{
currentDescription.track = text.toUInt();
} }
else_skip else_skip
} }
@ -163,44 +138,36 @@ void MusicBrainzResultsModel::parseInitialResults(const QByteArray &data)
} }
else_skip else_skip
} }
} } eliftag("tag-list") {
else_skip children {
} iftag("tag") {
} children {
eliftag("tag-list") iftag("name") {
{ if (!currentDescription.genre.isEmpty()) {
children currentDescription.genre.append(QLatin1Char(' '));
{ }
iftag("tag") currentDescription.genre.append(text);
{ }
children else_skip
{
iftag("name")
{
if (!currentDescription.genre.isEmpty()) {
currentDescription.genre.append(QLatin1Char(' '));
} }
currentDescription.genre.append(text);
} }
else_skip else_skip
} }
} }
else_skip else_skip
} }
m_results << currentDescription;
} }
else_skip else_skip
} }
m_results << currentDescription;
} }
else_skip else_skip
} }
} }
else_skip else_skip
} }
}
else_skip
} // namespace QtGui
#include <qtutilities/misc/undefxmlparsermacros.h> #include <qtutilities/misc/undefxmlparsermacros.h>
// clang-format on
// check for parsing errors // check for parsing errors
switch (xmlReader.error()) { switch (xmlReader.error()) {
@ -217,7 +184,7 @@ endResetModel();
QueryResultsModel *queryMusicBrainz(SongDescription &&songDescription) QueryResultsModel *queryMusicBrainz(SongDescription &&songDescription)
{ {
static const QString defaultMusicBrainzUrl(QStringLiteral("https://musicbrainz.org/ws/2/recording/")); static const auto defaultMusicBrainzUrl(QStringLiteral("https://musicbrainz.org/ws/2/recording/"));
// compose parts // compose parts
QStringList parts; QStringList parts;
@ -236,7 +203,7 @@ QueryResultsModel *queryMusicBrainz(SongDescription &&songDescription)
} }
// compose URL // compose URL
const QString &musicBrainzUrl = Settings::values().dbQuery.musicBrainzUrl; const auto &musicBrainzUrl = Settings::values().dbQuery.musicBrainzUrl;
QUrl url(musicBrainzUrl.isEmpty() ? defaultMusicBrainzUrl : (musicBrainzUrl + QStringLiteral("/recording/"))); QUrl url(musicBrainzUrl.isEmpty() ? defaultMusicBrainzUrl : (musicBrainzUrl + QStringLiteral("/recording/")));
QUrlQuery query; QUrlQuery query;
query.addQueryItem(QStringLiteral("query"), parts.join(QStringLiteral(" AND "))); query.addQueryItem(QStringLiteral("query"), parts.join(QStringLiteral(" AND ")));
@ -250,8 +217,8 @@ QueryResultsModel *queryMusicBrainz(SongDescription &&songDescription)
QNetworkReply *queryCoverArtArchive(const QString &albumId) QNetworkReply *queryCoverArtArchive(const QString &albumId)
{ {
static const QString defaultArchiveUrl(QStringLiteral("https://coverartarchive.org")); static const auto defaultArchiveUrl(QStringLiteral("https://coverartarchive.org"));
const QString &coverArtArchiveUrl = Settings::values().dbQuery.coverArtArchiveUrl; const auto &coverArtArchiveUrl = Settings::values().dbQuery.coverArtArchiveUrl;
return networkAccessManager().get(QNetworkRequest(QUrl( return networkAccessManager().get(QNetworkRequest(QUrl(
(coverArtArchiveUrl.isEmpty() ? defaultArchiveUrl : coverArtArchiveUrl) % QStringLiteral("/release/") % albumId % QStringLiteral("/front")))); (coverArtArchiveUrl.isEmpty() ? defaultArchiveUrl : coverArtArchiveUrl) % QStringLiteral("/release/") % albumId % QStringLiteral("/front"))));
} }

View File

@ -101,33 +101,35 @@ DbQueryWidget::~DbQueryWidget()
void DbQueryWidget::insertSearchTermsFromTagEdit(TagEdit *tagEdit) void DbQueryWidget::insertSearchTermsFromTagEdit(TagEdit *tagEdit)
{ {
if (tagEdit) { if (!tagEdit) {
// set title, album and artist return;
m_ui->titleLineEdit->setText(tagValueToQString(tagEdit->value(KnownField::Title))); }
m_ui->albumLineEdit->setText(tagValueToQString(tagEdit->value(KnownField::Album)));
m_ui->artistLineEdit->setText(tagValueToQString(tagEdit->value(KnownField::Artist)));
// set track number, or if not available part number // set title, album and artist
bool trackValueOk = false; m_ui->titleLineEdit->setText(tagValueToQString(tagEdit->value(KnownField::Title)));
try { m_ui->albumLineEdit->setText(tagValueToQString(tagEdit->value(KnownField::Album)));
TagValue trackValue = tagEdit->value(KnownField::TrackPosition); m_ui->artistLineEdit->setText(tagValueToQString(tagEdit->value(KnownField::Artist)));
if (!trackValue.isEmpty()) {
m_ui->trackSpinBox->setValue(trackValue.toPositionInSet().position()); // set track number, or if not available part number
trackValueOk = true; bool trackValueOk = false;
} try {
} catch (const ConversionException &) { TagValue trackValue = tagEdit->value(KnownField::TrackPosition);
if (!trackValue.isEmpty()) {
m_ui->trackSpinBox->setValue(trackValue.toPositionInSet().position());
trackValueOk = true;
} }
if (!trackValueOk) { } catch (const ConversionException &) {
TagValue trackValue = tagEdit->value(KnownField::PartNumber); }
if (!trackValue.isEmpty()) { if (!trackValueOk) {
m_ui->trackSpinBox->setValue(trackValue.toInteger()); TagValue trackValue = tagEdit->value(KnownField::PartNumber);
trackValueOk = true; if (!trackValue.isEmpty()) {
} m_ui->trackSpinBox->setValue(trackValue.toInteger());
} trackValueOk = true;
if (!trackValueOk) {
m_ui->trackSpinBox->clear();
} }
} }
if (!trackValueOk) {
m_ui->trackSpinBox->clear();
}
} }
SongDescription DbQueryWidget::currentSongDescription() const SongDescription DbQueryWidget::currentSongDescription() const
@ -186,50 +188,52 @@ void DbQueryWidget::searchLyricsWikia()
void DbQueryWidget::abortSearch() void DbQueryWidget::abortSearch()
{ {
if (m_model) { if (!m_model) {
if (m_model->isFetchingCover()) { return;
// call abort to abort fetching cover }
m_model->abort(); if (m_model->isFetchingCover()) {
} else if (!m_model->areResultsAvailable()) { // call abort to abort fetching cover
// delete model to abort search m_model->abort();
m_ui->resultsTreeView->setModel(nullptr); } else if (!m_model->areResultsAvailable()) {
delete m_model; // delete model to abort search
m_model = nullptr; m_ui->resultsTreeView->setModel(nullptr);
delete m_model;
m_model = nullptr;
// update status // update status
m_ui->notificationLabel->setNotificationType(NotificationType::Information); m_ui->notificationLabel->setNotificationType(NotificationType::Information);
m_ui->notificationLabel->setText(tr("Aborted")); m_ui->notificationLabel->setText(tr("Aborted"));
setStatus(true); setStatus(true);
}
} }
} }
void DbQueryWidget::showResults() void DbQueryWidget::showResults()
{ {
if (m_model) { if (!m_model) {
if (m_model->errorList().isEmpty()) { return;
m_ui->notificationLabel->setNotificationType(NotificationType::TaskComplete);
if (m_model->results().isEmpty()) {
m_ui->notificationLabel->setText(tr("No results available"));
} else {
m_ui->notificationLabel->setText(tr("%1 result(s) available", 0, m_model->results().size()).arg(m_model->results().size()));
}
} else {
m_ui->notificationLabel->setNotificationType(NotificationType::Critical);
m_ui->notificationLabel->clearText();
for (const QString &error : m_model->errorList()) {
m_ui->notificationLabel->appendLine(error);
}
}
if (m_model->results().isEmpty()) {
m_ui->applyPushButton->setEnabled(false);
} else {
m_ui->resultsTreeView->selectionModel()->setCurrentIndex(
m_model->index(0, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
m_ui->applyPushButton->setEnabled(m_tagEditorWidget->activeTagEdit());
}
setStatus(true);
} }
if (m_model->errorList().isEmpty()) {
m_ui->notificationLabel->setNotificationType(NotificationType::TaskComplete);
if (m_model->results().isEmpty()) {
m_ui->notificationLabel->setText(tr("No results available"));
} else {
m_ui->notificationLabel->setText(tr("%1 result(s) available", nullptr, m_model->results().size()).arg(m_model->results().size()));
}
} else {
m_ui->notificationLabel->setNotificationType(NotificationType::Critical);
m_ui->notificationLabel->clearText();
for (const QString &error : m_model->errorList()) {
m_ui->notificationLabel->appendLine(error);
}
}
if (m_model->results().isEmpty()) {
m_ui->applyPushButton->setEnabled(false);
} else {
m_ui->resultsTreeView->selectionModel()->setCurrentIndex(
m_model->index(0, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
m_ui->applyPushButton->setEnabled(m_tagEditorWidget->activeTagEdit());
}
setStatus(true);
} }
void DbQueryWidget::setStatus(bool aborted) void DbQueryWidget::setStatus(bool aborted)
@ -254,9 +258,9 @@ void DbQueryWidget::fileStatusChanged(bool, bool hasTags)
void DbQueryWidget::applySelectedResults() void DbQueryWidget::applySelectedResults()
{ {
// check whether model, tag edit and current selection exist // check whether model, tag edit and current selection exist
if (TagEdit *tagEdit = m_tagEditorWidget->activeTagEdit()) { if (auto *const tagEdit = m_tagEditorWidget->activeTagEdit()) {
if (const QItemSelectionModel *selectionModel = m_ui->resultsTreeView->selectionModel()) { if (const auto *const selectionModel = m_ui->resultsTreeView->selectionModel()) {
const QModelIndexList selection = selectionModel->selection().indexes(); const auto selection = selectionModel->selection().indexes();
if (!selection.isEmpty()) { if (!selection.isEmpty()) {
applyResults(tagEdit, selection.front()); applyResults(tagEdit, selection.front());
} }
@ -290,9 +294,9 @@ void DbQueryWidget::applyMatchingResults(TagEdit *tagEdit)
} }
// determine already present title, album and artist // determine already present title, album and artist
const TagValue givenTitle = tagEdit->value(KnownField::Title); const auto givenTitle = tagEdit->value(KnownField::Title);
const TagValue givenAlbum = tagEdit->value(KnownField::Album); const auto givenAlbum = tagEdit->value(KnownField::Album);
const TagValue givenArtist = tagEdit->value(KnownField::Artist); const auto givenArtist = tagEdit->value(KnownField::Artist);
// also determine already present track number (which is a little bit more complex -> TODO: improve backend API) // also determine already present track number (which is a little bit more complex -> TODO: improve backend API)
int givenTrack; int givenTrack;
@ -352,71 +356,79 @@ void DbQueryWidget::autoInsertMatchingResults()
*/ */
void DbQueryWidget::applyResults(TagEdit *tagEdit, const QModelIndex &resultIndex) void DbQueryWidget::applyResults(TagEdit *tagEdit, const QModelIndex &resultIndex)
{ {
if (m_model) { if (!m_model) {
// determine previous value handling return;
PreviousValueHandling previousValueHandling }
= m_ui->overrideCheckBox->isChecked() ? PreviousValueHandling::Update : PreviousValueHandling::Keep;
// loop through all fields // determine previous value handling
for (const ChecklistItem &item : values().dbQuery.fields.items()) { const auto previousValueHandling = m_ui->overrideCheckBox->isChecked() ? PreviousValueHandling::Update : PreviousValueHandling::Keep;
if (item.isChecked()) {
// field should be used
const auto field = static_cast<KnownField>(item.id().toInt());
int row = resultIndex.row();
TagValue value = m_model->fieldValue(row, field);
if (value.isEmpty()) { // loop through all fields
// cover and lyrics might be fetched belated for (const ChecklistItem &item : values().dbQuery.fields.items()) {
switch (field) { if (!item.isChecked()) {
case KnownField::Cover: continue;
if (m_model->fetchCover(resultIndex)) { }
// cover is available now
tagEdit->setValue(KnownField::Cover, m_model->fieldValue(row, KnownField::Cover), previousValueHandling);
} else {
// cover is fetched asynchronously
// -> show status
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
m_ui->notificationLabel->appendLine(tr("Retrieving cover art to be applied ..."));
setStatus(false);
// -> apply cover when available
connect(m_model, &QueryResultsModel::coverAvailable, [this, row, previousValueHandling](const QModelIndex &index) {
if (row == index.row()) {
if (TagEdit *tagEdit = m_tagEditorWidget->activeTagEdit()) {
tagEdit->setValue(KnownField::Cover, m_model->fieldValue(row, KnownField::Cover), previousValueHandling);
}
}
});
}
break;
case KnownField::Lyrics: // determine the field to be used and its value
if (m_model->fetchLyrics(resultIndex)) { const auto field = static_cast<KnownField>(item.id().toInt());
// lyrics are available now const auto row = resultIndex.row();
tagEdit->setValue(KnownField::Lyrics, m_model->fieldValue(row, KnownField::Lyrics), previousValueHandling); const auto value = m_model->fieldValue(row, field);
} else {
// lyrics are fetched asynchronously
// -> show status
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
m_ui->notificationLabel->appendLine(tr("Retrieving lyrics to be applied ..."));
setStatus(false);
// -> apply cover when available
connect(m_model, &QueryResultsModel::lyricsAvailable, [this, row, previousValueHandling](const QModelIndex &index) {
if (row == index.row()) {
if (TagEdit *tagEdit = m_tagEditorWidget->activeTagEdit()) {
tagEdit->setValue(KnownField::Lyrics, m_model->fieldValue(row, KnownField::Lyrics), previousValueHandling);
}
}
});
}
break;
default:; // set the value if available
} if (!value.isEmpty()) {
} else { tagEdit->setValue(field, value, previousValueHandling);
// any other fields are just set continue;
tagEdit->setValue(field, value, previousValueHandling); }
}
// cover and lyrics might be fetched asynchronously
switch (field) {
case KnownField::Cover:
if (m_model->fetchCover(resultIndex)) {
// cover is available now
tagEdit->setValue(KnownField::Cover, m_model->fieldValue(row, KnownField::Cover), previousValueHandling);
continue;
} }
// cover is fetched asynchronously
// -> show status
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
m_ui->notificationLabel->appendLine(tr("Retrieving cover art to be applied ..."));
setStatus(false);
// -> apply cover when available
connect(m_model, &QueryResultsModel::coverAvailable, [this, row, previousValueHandling](const QModelIndex &index) {
if (row != index.row()) {
return;
}
if (auto *const tagEdit = m_tagEditorWidget->activeTagEdit()) {
tagEdit->setValue(KnownField::Cover, m_model->fieldValue(row, KnownField::Cover), previousValueHandling);
}
});
break;
case KnownField::Lyrics:
if (m_model->fetchLyrics(resultIndex)) {
// lyrics are available now
tagEdit->setValue(KnownField::Lyrics, m_model->fieldValue(row, KnownField::Lyrics), previousValueHandling);
continue;
}
// lyrics are fetched asynchronously
// -> show status
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
m_ui->notificationLabel->appendLine(tr("Retrieving lyrics to be applied ..."));
setStatus(false);
// -> apply cover when available
connect(m_model, &QueryResultsModel::lyricsAvailable, [this, row, previousValueHandling](const QModelIndex &index) {
if (row != index.row()) {
return;
}
if (auto *const tagEdit = m_tagEditorWidget->activeTagEdit()) {
tagEdit->setValue(KnownField::Lyrics, m_model->fieldValue(row, KnownField::Lyrics), previousValueHandling);
}
});
break;
default:;
} }
} }
} }
@ -428,93 +440,98 @@ void DbQueryWidget::insertSearchTermsFromActiveTagEdit()
void DbQueryWidget::showResultsContextMenu() void DbQueryWidget::showResultsContextMenu()
{ {
if (const QItemSelectionModel *selectionModel = m_ui->resultsTreeView->selectionModel()) { const auto *const selectionModel = m_ui->resultsTreeView->selectionModel();
const QModelIndexList selection = selectionModel->selection().indexes(); if (!selectionModel) {
if (!selection.isEmpty()) { return;
QMenu contextMenu;
if (m_ui->applyPushButton->isEnabled()) {
contextMenu.addAction(m_ui->applyPushButton->icon(), tr("Use selected row"), this,
static_cast<void (DbQueryWidget::*)(void)>(&DbQueryWidget::applySelectedResults));
}
if (m_model && m_model->areResultsAvailable()) {
if (!contextMenu.isEmpty()) {
contextMenu.addSeparator();
}
contextMenu.addAction(
QIcon::fromTheme(QStringLiteral("view-preview")), tr("Show cover"), this, &DbQueryWidget::fetchAndShowCoverForSelection);
contextMenu.addAction(
QIcon::fromTheme(QStringLiteral("view-media-lyrics")), tr("Show lyrics"), this, &DbQueryWidget::fetchAndShowLyricsForSelection);
contextMenu.addAction(
QIcon::fromTheme(QStringLiteral("internet-web-browser")), tr("Show in browser"), this, &DbQueryWidget::openSelectionInBrowser);
}
contextMenu.exec(QCursor::pos());
}
} }
const auto selection = selectionModel->selection().indexes();
if (selection.isEmpty()) {
return;
}
QMenu contextMenu;
if (m_ui->applyPushButton->isEnabled()) {
contextMenu.addAction(m_ui->applyPushButton->icon(), tr("Use selected row"), this,
static_cast<void (DbQueryWidget::*)(void)>(&DbQueryWidget::applySelectedResults));
}
if (m_model && m_model->areResultsAvailable()) {
if (!contextMenu.isEmpty()) {
contextMenu.addSeparator();
}
contextMenu.addAction(
QIcon::fromTheme(QStringLiteral("view-preview")), tr("Show cover"), this, &DbQueryWidget::fetchAndShowCoverForSelection);
contextMenu.addAction(
QIcon::fromTheme(QStringLiteral("view-media-lyrics")), tr("Show lyrics"), this, &DbQueryWidget::fetchAndShowLyricsForSelection);
contextMenu.addAction(
QIcon::fromTheme(QStringLiteral("internet-web-browser")), tr("Show in browser"), this, &DbQueryWidget::openSelectionInBrowser);
}
contextMenu.exec(QCursor::pos());
} }
void DbQueryWidget::fetchAndShowCoverForSelection() void DbQueryWidget::fetchAndShowCoverForSelection()
{ {
const QModelIndex selectedIndex = this->selectedIndex(); const auto selectedIndex = this->selectedIndex();
if (!selectedIndex.isValid()) { if (!selectedIndex.isValid()) {
return; return;
} }
if (const QByteArray *cover = m_model->cover(selectedIndex)) { if (const QByteArray *const cover = m_model->cover(selectedIndex)) {
showCover(*cover); showCover(*cover);
} else { return;
if (m_model->fetchCover(selectedIndex)) { }
if (const QByteArray *cover = m_model->cover(selectedIndex)) {
showCover(*cover); if (m_model->fetchCover(selectedIndex)) {
} else { if (const QByteArray *const cover = m_model->cover(selectedIndex)) {
// cover couldn't be fetched, error tracked via resultsAvailable() signal so nothing to do showCover(*cover);
}
} else { } else {
// cover is fetched asynchronously // cover couldn't be fetched, error tracked via resultsAvailable() signal so nothing to do
// -> memorize index to be shown
m_coverIndex = selectedIndex.row();
// -> show status
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
m_ui->notificationLabel->setText(tr("Retrieving cover art ..."));
setStatus(false);
} }
} else {
// cover is fetched asynchronously
// -> memorize index to be shown
m_coverIndex = selectedIndex.row();
// -> show status
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
m_ui->notificationLabel->setText(tr("Retrieving cover art ..."));
setStatus(false);
} }
} }
void DbQueryWidget::fetchAndShowLyricsForSelection() void DbQueryWidget::fetchAndShowLyricsForSelection()
{ {
const QModelIndex selectedIndex = this->selectedIndex(); const auto selectedIndex = this->selectedIndex();
if (!selectedIndex.isValid()) { if (!selectedIndex.isValid()) {
return; return;
} }
if (const QString *lyrics = m_model->lyrics(selectedIndex)) { if (const QString *const lyrics = m_model->lyrics(selectedIndex)) {
showLyrics(*lyrics); showLyrics(*lyrics);
} else { return;
if (m_model->fetchLyrics(selectedIndex)) { }
if (const QByteArray *cover = m_model->cover(selectedIndex)) {
showLyrics(*cover); if (m_model->fetchLyrics(selectedIndex)) {
} else { if (const QByteArray *cover = m_model->cover(selectedIndex)) {
// lyrics couldn't be fetched, error tracked via resultsAvailable() signal so nothing to do showLyrics(*cover);
}
} else { } else {
// lyrics are fetched asynchronously // lyrics couldn't be fetched, error tracked via resultsAvailable() signal so nothing to do
// -> memorize index to be shown
m_lyricsIndex = selectedIndex.row();
// -> show status
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
m_ui->notificationLabel->setText(tr("Retrieving lyrics ..."));
setStatus(false);
} }
} else {
// lyrics are fetched asynchronously
// -> memorize index to be shown
m_lyricsIndex = selectedIndex.row();
// -> show status
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
m_ui->notificationLabel->setText(tr("Retrieving lyrics ..."));
setStatus(false);
} }
} }
void DbQueryWidget::openSelectionInBrowser() void DbQueryWidget::openSelectionInBrowser()
{ {
const QModelIndex selectedIndex = this->selectedIndex(); const auto selectedIndex = this->selectedIndex();
if (!selectedIndex.isValid()) { if (!selectedIndex.isValid()) {
return; return;
} }
const QUrl url(m_model->webUrl(selectedIndex)); const auto url = m_model->webUrl(selectedIndex);
if (url.isEmpty()) { if (url.isEmpty()) {
m_ui->notificationLabel->appendLine(tr("No web URL available.")); m_ui->notificationLabel->appendLine(tr("No web URL available."));
return; return;
@ -611,11 +628,11 @@ QModelIndex DbQueryWidget::selectedIndex() const
if (!m_model) { if (!m_model) {
return QModelIndex(); return QModelIndex();
} }
const QItemSelectionModel *selectionModel = m_ui->resultsTreeView->selectionModel(); const auto *const selectionModel = m_ui->resultsTreeView->selectionModel();
if (!selectionModel) { if (!selectionModel) {
return QModelIndex(); return QModelIndex();
} }
const QModelIndexList selection = selectionModel->selectedRows(); const auto selection = selectionModel->selectedRows();
if (selection.size() != 1) { if (selection.size() != 1) {
return QModelIndex(); return QModelIndex();
} }