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
{
if (row < m_results.size()) {
const SongDescription &res = m_results.at(row);
switch (knownField) {
case KnownField::Title:
returnValue(title);
case KnownField::Album:
returnValue(album);
case KnownField::Artist:
returnValue(artist);
case KnownField::Genre:
returnValue(genre);
case KnownField::Year:
returnValue(year);
case KnownField::TrackPosition:
return TagValue(PositionInSet(res.track, res.totalTracks));
case KnownField::PartNumber:
return TagValue(res.track);
case KnownField::TotalParts:
return TagValue(res.totalTracks);
case KnownField::Cover:
if (!res.cover.isEmpty()) {
TagValue tagValue(res.cover.data(), static_cast<size_t>(res.cover.size()), TagDataType::Picture);
tagValue.setMimeType(containerMimeType(parseSignature(res.cover.data(), res.cover.size())));
return tagValue;
}
break;
case KnownField::Lyrics:
returnValue(lyrics);
default:;
if (row >= m_results.size()) {
return TagValue();
}
const SongDescription &res = m_results.at(row);
switch (knownField) {
case KnownField::Title:
returnValue(title);
case KnownField::Album:
returnValue(album);
case KnownField::Artist:
returnValue(artist);
case KnownField::Genre:
returnValue(genre);
case KnownField::Year:
returnValue(year);
case KnownField::TrackPosition:
return TagValue(PositionInSet(res.track, res.totalTracks));
case KnownField::PartNumber:
return TagValue(res.track);
case KnownField::TotalParts:
return TagValue(res.totalTracks);
case KnownField::Cover:
if (!res.cover.isEmpty()) {
TagValue tagValue(res.cover.data(), static_cast<size_t>(res.cover.size()), TagDataType::Picture);
tagValue.setMimeType(containerMimeType(parseSignature(res.cover.data(), res.cover.size())));
return tagValue;
}
break;
case KnownField::Lyrics:
returnValue(lyrics);
default:;
}
return TagValue();
}
@ -97,38 +98,39 @@ TagValue QueryResultsModel::fieldValue(int row, KnownField knownField) const
QVariant QueryResultsModel::data(const QModelIndex &index, int role) const
{
if (index.isValid() && index.row() < m_results.size()) {
const SongDescription &res = m_results.at(index.row());
switch (role) {
case Qt::DisplayRole:
switch (index.column()) {
case TitleCol:
return res.title;
case AlbumCol:
return res.album;
case ArtistCol:
return res.artist;
case GenreCol:
return res.genre;
case YearCol:
return res.year;
case TrackCol:
if (res.track) {
return res.track;
} else {
return QString();
}
case TotalTracksCol:
if (res.totalTracks) {
return res.totalTracks;
} else {
return QString();
}
default:;
if (!index.isValid() || index.row() >= m_results.size()) {
return QVariant();
}
const SongDescription &res = m_results.at(index.row());
switch (role) {
case Qt::DisplayRole:
switch (index.column()) {
case TitleCol:
return res.title;
case AlbumCol:
return res.album;
case ArtistCol:
return res.artist;
case GenreCol:
return res.genre;
case YearCol:
return res.year;
case TrackCol:
if (res.track) {
return res.track;
} else {
return QString();
}
case TotalTracksCol:
if (res.totalTracks) {
return res.totalTracks;
} else {
return QString();
}
break;
default:;
}
break;
default:;
}
return QVariant();
}
@ -186,11 +188,12 @@ int QueryResultsModel::columnCount(const QModelIndex &parent) const
const QByteArray *QueryResultsModel::cover(const QModelIndex &index) const
{
if (!index.parent().isValid() && index.row() < m_results.size()) {
const QByteArray &cover = m_results.at(index.row()).cover;
if (!cover.isEmpty()) {
return &cover;
}
if (!index.isValid() || index.row() >= m_results.size()) {
return nullptr;
}
const auto &cover = m_results.at(index.row()).cover;
if (!cover.isEmpty()) {
return &cover;
}
return nullptr;
}
@ -216,11 +219,12 @@ bool QueryResultsModel::fetchCover(const QModelIndex &index)
const QString *QueryResultsModel::lyrics(const QModelIndex &index) const
{
if (!index.parent().isValid() && index.row() < m_results.size()) {
const QString &lyrics = m_results.at(index.row()).lyrics;
if (!lyrics.isEmpty()) {
return &lyrics;
}
if (!index.isValid() || index.row() >= m_results.size()) {
return nullptr;
}
const auto &lyrics = m_results.at(index.row()).lyrics;
if (!lyrics.isEmpty()) {
return &lyrics;
}
return nullptr;
}
@ -268,16 +272,16 @@ HttpResultsModel::~HttpResultsModel()
*/
void HttpResultsModel::handleInitialReplyFinished()
{
auto *reply = static_cast<QNetworkReply *>(sender());
auto *const reply = static_cast<QNetworkReply *>(sender());
QByteArray data;
if (auto *newReply = evaluateReplyResults(reply, data, false)) {
if (auto *const newReply = evaluateReplyResults(reply, data, false)) {
addReply(newReply, this, &HttpResultsModel::handleInitialReplyFinished);
} else {
if (!data.isEmpty()) {
parseInitialResults(data);
}
setResultsAvailable(true); // update status, emit resultsAvailable()
return;
}
if (!data.isEmpty()) {
parseInitialResults(data);
}
setResultsAvailable(true); // update status, emit resultsAvailable()
}
QNetworkReply *HttpResultsModel::evaluateReplyResults(QNetworkReply *reply, QByteArray &data, bool alwaysFollowRedirection)
@ -286,37 +290,35 @@ QNetworkReply *HttpResultsModel::evaluateReplyResults(QNetworkReply *reply, QByt
reply->deleteLater();
m_replies.removeAll(reply);
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 {
if (reply->error() != QNetworkReply::NoError) {
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;
}
@ -325,40 +327,41 @@ QNetworkReply *HttpResultsModel::evaluateReplyResults(QNetworkReply *reply, QByt
*/
void HttpResultsModel::abort()
{
if (!m_replies.isEmpty()) {
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);
if (m_replies.isEmpty()) {
return;
}
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)
{
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));
} else {
if (!data.isEmpty()) {
parseCoverResults(albumId, row, data);
}
setResultsAvailable(true);
return;
}
if (!data.isEmpty()) {
parseCoverResults(albumId, row, data);
}
setResultsAvailable(true);
}
void HttpResultsModel::parseCoverResults(const QString &albumId, int row, const QByteArray &data)
{
// add cover -> determine album ID and row
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 {
if (albumId.isEmpty() || row >= m_results.size()) {
m_errorList << tr("Internal error: context for cover reply invalid");
setResultsAvailable(true);
return;
}
if (!data.isEmpty()) {
m_coverData[albumId] = data;
m_results[row].cover = data;
emit coverAvailable(index(row, 0));
}
setFetchingCover(false);
}

View File

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

View File

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

View File

@ -101,33 +101,35 @@ DbQueryWidget::~DbQueryWidget()
void DbQueryWidget::insertSearchTermsFromTagEdit(TagEdit *tagEdit)
{
if (tagEdit) {
// set title, album and artist
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)));
if (!tagEdit) {
return;
}
// set track number, or if not available part number
bool trackValueOk = false;
try {
TagValue trackValue = tagEdit->value(KnownField::TrackPosition);
if (!trackValue.isEmpty()) {
m_ui->trackSpinBox->setValue(trackValue.toPositionInSet().position());
trackValueOk = true;
}
} catch (const ConversionException &) {
// set title, album and artist
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
bool trackValueOk = false;
try {
TagValue trackValue = tagEdit->value(KnownField::TrackPosition);
if (!trackValue.isEmpty()) {
m_ui->trackSpinBox->setValue(trackValue.toPositionInSet().position());
trackValueOk = true;
}
if (!trackValueOk) {
TagValue trackValue = tagEdit->value(KnownField::PartNumber);
if (!trackValue.isEmpty()) {
m_ui->trackSpinBox->setValue(trackValue.toInteger());
trackValueOk = true;
}
}
if (!trackValueOk) {
m_ui->trackSpinBox->clear();
} catch (const ConversionException &) {
}
if (!trackValueOk) {
TagValue trackValue = tagEdit->value(KnownField::PartNumber);
if (!trackValue.isEmpty()) {
m_ui->trackSpinBox->setValue(trackValue.toInteger());
trackValueOk = true;
}
}
if (!trackValueOk) {
m_ui->trackSpinBox->clear();
}
}
SongDescription DbQueryWidget::currentSongDescription() const
@ -186,50 +188,52 @@ void DbQueryWidget::searchLyricsWikia()
void DbQueryWidget::abortSearch()
{
if (m_model) {
if (m_model->isFetchingCover()) {
// call abort to abort fetching cover
m_model->abort();
} else if (!m_model->areResultsAvailable()) {
// delete model to abort search
m_ui->resultsTreeView->setModel(nullptr);
delete m_model;
m_model = nullptr;
if (!m_model) {
return;
}
if (m_model->isFetchingCover()) {
// call abort to abort fetching cover
m_model->abort();
} else if (!m_model->areResultsAvailable()) {
// delete model to abort search
m_ui->resultsTreeView->setModel(nullptr);
delete m_model;
m_model = nullptr;
// update status
m_ui->notificationLabel->setNotificationType(NotificationType::Information);
m_ui->notificationLabel->setText(tr("Aborted"));
setStatus(true);
}
// update status
m_ui->notificationLabel->setNotificationType(NotificationType::Information);
m_ui->notificationLabel->setText(tr("Aborted"));
setStatus(true);
}
}
void DbQueryWidget::showResults()
{
if (m_model) {
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", 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) {
return;
}
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)
@ -254,9 +258,9 @@ void DbQueryWidget::fileStatusChanged(bool, bool hasTags)
void DbQueryWidget::applySelectedResults()
{
// check whether model, tag edit and current selection exist
if (TagEdit *tagEdit = m_tagEditorWidget->activeTagEdit()) {
if (const QItemSelectionModel *selectionModel = m_ui->resultsTreeView->selectionModel()) {
const QModelIndexList selection = selectionModel->selection().indexes();
if (auto *const tagEdit = m_tagEditorWidget->activeTagEdit()) {
if (const auto *const selectionModel = m_ui->resultsTreeView->selectionModel()) {
const auto selection = selectionModel->selection().indexes();
if (!selection.isEmpty()) {
applyResults(tagEdit, selection.front());
}
@ -290,9 +294,9 @@ void DbQueryWidget::applyMatchingResults(TagEdit *tagEdit)
}
// determine already present title, album and artist
const TagValue givenTitle = tagEdit->value(KnownField::Title);
const TagValue givenAlbum = tagEdit->value(KnownField::Album);
const TagValue givenArtist = tagEdit->value(KnownField::Artist);
const auto givenTitle = tagEdit->value(KnownField::Title);
const auto givenAlbum = tagEdit->value(KnownField::Album);
const auto givenArtist = tagEdit->value(KnownField::Artist);
// also determine already present track number (which is a little bit more complex -> TODO: improve backend API)
int givenTrack;
@ -352,71 +356,79 @@ void DbQueryWidget::autoInsertMatchingResults()
*/
void DbQueryWidget::applyResults(TagEdit *tagEdit, const QModelIndex &resultIndex)
{
if (m_model) {
// determine previous value handling
PreviousValueHandling previousValueHandling
= m_ui->overrideCheckBox->isChecked() ? PreviousValueHandling::Update : PreviousValueHandling::Keep;
if (!m_model) {
return;
}
// loop through all fields
for (const ChecklistItem &item : values().dbQuery.fields.items()) {
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);
// determine previous value handling
const auto previousValueHandling = m_ui->overrideCheckBox->isChecked() ? PreviousValueHandling::Update : PreviousValueHandling::Keep;
if (value.isEmpty()) {
// cover and lyrics might be fetched belated
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);
} 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;
// loop through all fields
for (const ChecklistItem &item : values().dbQuery.fields.items()) {
if (!item.isChecked()) {
continue;
}
case KnownField::Lyrics:
if (m_model->fetchLyrics(resultIndex)) {
// lyrics are available now
tagEdit->setValue(KnownField::Lyrics, m_model->fieldValue(row, KnownField::Lyrics), previousValueHandling);
} 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;
// determine the field to be used and its value
const auto field = static_cast<KnownField>(item.id().toInt());
const auto row = resultIndex.row();
const auto value = m_model->fieldValue(row, field);
default:;
}
} else {
// any other fields are just set
tagEdit->setValue(field, value, previousValueHandling);
}
// set the value if available
if (!value.isEmpty()) {
tagEdit->setValue(field, value, previousValueHandling);
continue;
}
// 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()
{
if (const QItemSelectionModel *selectionModel = m_ui->resultsTreeView->selectionModel()) {
const QModelIndexList selection = selectionModel->selection().indexes();
if (!selection.isEmpty()) {
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 *const selectionModel = m_ui->resultsTreeView->selectionModel();
if (!selectionModel) {
return;
}
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()
{
const QModelIndex selectedIndex = this->selectedIndex();
const auto selectedIndex = this->selectedIndex();
if (!selectedIndex.isValid()) {
return;
}
if (const QByteArray *cover = m_model->cover(selectedIndex)) {
if (const QByteArray *const cover = m_model->cover(selectedIndex)) {
showCover(*cover);
} else {
if (m_model->fetchCover(selectedIndex)) {
if (const QByteArray *cover = m_model->cover(selectedIndex)) {
showCover(*cover);
} else {
// cover couldn't be fetched, error tracked via resultsAvailable() signal so nothing to do
}
return;
}
if (m_model->fetchCover(selectedIndex)) {
if (const QByteArray *const cover = m_model->cover(selectedIndex)) {
showCover(*cover);
} 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);
// cover couldn't be fetched, error tracked via resultsAvailable() signal so nothing to do
}
} 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()
{
const QModelIndex selectedIndex = this->selectedIndex();
const auto selectedIndex = this->selectedIndex();
if (!selectedIndex.isValid()) {
return;
}
if (const QString *lyrics = m_model->lyrics(selectedIndex)) {
if (const QString *const lyrics = m_model->lyrics(selectedIndex)) {
showLyrics(*lyrics);
} else {
if (m_model->fetchLyrics(selectedIndex)) {
if (const QByteArray *cover = m_model->cover(selectedIndex)) {
showLyrics(*cover);
} else {
// lyrics couldn't be fetched, error tracked via resultsAvailable() signal so nothing to do
}
return;
}
if (m_model->fetchLyrics(selectedIndex)) {
if (const QByteArray *cover = m_model->cover(selectedIndex)) {
showLyrics(*cover);
} 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);
// lyrics couldn't be fetched, error tracked via resultsAvailable() signal so nothing to do
}
} 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()
{
const QModelIndex selectedIndex = this->selectedIndex();
const auto selectedIndex = this->selectedIndex();
if (!selectedIndex.isValid()) {
return;
}
const QUrl url(m_model->webUrl(selectedIndex));
const auto url = m_model->webUrl(selectedIndex);
if (url.isEmpty()) {
m_ui->notificationLabel->appendLine(tr("No web URL available."));
return;
@ -611,11 +628,11 @@ QModelIndex DbQueryWidget::selectedIndex() const
if (!m_model) {
return QModelIndex();
}
const QItemSelectionModel *selectionModel = m_ui->resultsTreeView->selectionModel();
const auto *const selectionModel = m_ui->resultsTreeView->selectionModel();
if (!selectionModel) {
return QModelIndex();
}
const QModelIndexList selection = selectionModel->selectedRows();
const auto selection = selectionModel->selectedRows();
if (selection.size() != 1) {
return QModelIndex();
}