2016-03-03 22:21:15 +01:00
|
|
|
#include "./dbquery.h"
|
|
|
|
|
|
|
|
#include "../misc/utility.h"
|
|
|
|
#include "../misc/networkaccessmanager.h"
|
2016-03-05 16:50:23 +01:00
|
|
|
#include "../application/settings.h"
|
2016-03-03 22:21:15 +01:00
|
|
|
|
|
|
|
#include <tagparser/tagvalue.h>
|
|
|
|
#include <tagparser/tag.h>
|
2016-05-14 23:23:16 +02:00
|
|
|
#include <tagparser/signature.h>
|
2016-03-03 22:21:15 +01:00
|
|
|
|
|
|
|
#include <QNetworkReply>
|
|
|
|
#include <QNetworkRequest>
|
|
|
|
#include <QUrlQuery>
|
|
|
|
#include <QStringBuilder>
|
2016-03-06 17:52:33 +01:00
|
|
|
#include <QXmlStreamReader>
|
|
|
|
#include <QMessageBox>
|
2016-03-03 22:21:15 +01:00
|
|
|
|
2016-03-06 17:52:33 +01:00
|
|
|
#include <map>
|
|
|
|
#ifdef DEBUG_BUILD
|
|
|
|
# include <iostream>
|
|
|
|
#endif
|
2016-03-03 22:21:15 +01:00
|
|
|
|
2016-03-06 17:52:33 +01:00
|
|
|
using namespace std;
|
2016-03-03 22:21:15 +01:00
|
|
|
using namespace Utility;
|
|
|
|
using namespace Media;
|
|
|
|
|
|
|
|
namespace QtGui {
|
|
|
|
|
|
|
|
SongDescription::SongDescription() :
|
|
|
|
track(0),
|
|
|
|
totalTracks(0),
|
2016-03-06 17:52:33 +01:00
|
|
|
disk(0),
|
|
|
|
cover(nullptr)
|
2016-03-03 22:21:15 +01:00
|
|
|
{}
|
|
|
|
|
|
|
|
QueryResultsModel::QueryResultsModel(QObject *parent) :
|
|
|
|
QAbstractTableModel(parent),
|
2016-03-06 17:52:33 +01:00
|
|
|
m_resultsAvailable(false),
|
|
|
|
m_fetchingCover(false)
|
2016-03-03 22:21:15 +01:00
|
|
|
{}
|
|
|
|
|
|
|
|
void QueryResultsModel::setResultsAvailable(bool resultsAvailable)
|
|
|
|
{
|
2016-03-28 20:28:59 +02:00
|
|
|
if((m_resultsAvailable = resultsAvailable)) {
|
2016-03-06 17:52:33 +01:00
|
|
|
emit this->resultsAvailable();
|
2016-03-03 22:21:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-06 17:52:33 +01:00
|
|
|
void QueryResultsModel::setFetchingCover(bool fetchingCover)
|
|
|
|
{
|
|
|
|
m_fetchingCover = fetchingCover;
|
|
|
|
}
|
|
|
|
|
2016-03-03 22:21:15 +01:00
|
|
|
#define returnValue(field) return qstringToTagValue(res.field, TagTextEncoding::Utf16LittleEndian)
|
|
|
|
|
2016-03-06 17:52:33 +01:00
|
|
|
void QueryResultsModel::abort()
|
|
|
|
{}
|
|
|
|
|
2016-03-03 22:21:15 +01:00
|
|
|
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);
|
2016-03-06 17:52:33 +01:00
|
|
|
case KnownField::Cover:
|
|
|
|
if(!res.cover.isEmpty()) {
|
2016-05-14 23:23:16 +02:00
|
|
|
TagValue tagValue(res.cover.data(), res.cover.size(), TagDataType::Picture);
|
|
|
|
tagValue.setMimeType(containerMimeType(parseSignature(res.cover.data(), res.cover.size())));
|
|
|
|
return tagValue;
|
2016-03-06 17:52:33 +01:00
|
|
|
}
|
|
|
|
break;
|
2016-03-03 22:21:15 +01:00
|
|
|
default:
|
|
|
|
;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return TagValue();
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef returnValue
|
|
|
|
|
|
|
|
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:
|
|
|
|
;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::ItemFlags QueryResultsModel::flags(const QModelIndex &index) const
|
|
|
|
{
|
|
|
|
Qt::ItemFlags flags = Qt::ItemNeverHasChildren | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
|
|
|
|
if(index.isValid()) {
|
|
|
|
flags |= Qt::ItemIsUserCheckable;
|
|
|
|
}
|
|
|
|
return flags;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant QueryResultsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
|
|
|
{
|
|
|
|
switch(orientation) {
|
|
|
|
case Qt::Horizontal:
|
|
|
|
switch(role) {
|
|
|
|
case Qt::DisplayRole:
|
|
|
|
switch(section) {
|
|
|
|
case TitleCol:
|
|
|
|
return tr("Song title");
|
|
|
|
case AlbumCol:
|
|
|
|
return tr("Album");
|
|
|
|
case ArtistCol:
|
|
|
|
return tr("Artist");
|
|
|
|
case YearCol:
|
|
|
|
return tr("Year");
|
|
|
|
case TrackCol:
|
|
|
|
return tr("Track");
|
|
|
|
case TotalTracksCol:
|
|
|
|
return tr("Total tracks");
|
|
|
|
case GenreCol:
|
|
|
|
return tr("Genre");
|
|
|
|
default:
|
|
|
|
;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
;
|
|
|
|
}
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
int QueryResultsModel::rowCount(const QModelIndex &parent) const
|
|
|
|
{
|
|
|
|
return parent.isValid() ? 0 : m_results.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
int QueryResultsModel::columnCount(const QModelIndex &parent) const
|
|
|
|
{
|
|
|
|
return parent.isValid() ? 0 : (TotalTracksCol + 1);
|
|
|
|
}
|
|
|
|
|
2016-03-06 17:52:33 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Fetches the cover the specified \a index.
|
|
|
|
* \returns True if the cover is immidiately available; false is the cover is fetched asynchronously.
|
|
|
|
*
|
|
|
|
* If the cover is fetched asynchronously the coverAvailable() signal is emitted, when the cover
|
|
|
|
* is available.
|
|
|
|
*
|
|
|
|
* The resultsAvailable() signal is emitted if errors have been added to errorList().
|
|
|
|
*/
|
|
|
|
bool QueryResultsModel::fetchCover(const QModelIndex &)
|
|
|
|
{
|
|
|
|
m_errorList << tr("Fetching the cover is not implemented for the selected provider.");
|
|
|
|
emit resultsAvailable();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
class HttpResultsModel : public QueryResultsModel
|
2016-03-03 22:21:15 +01:00
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
public:
|
2016-03-06 17:52:33 +01:00
|
|
|
~HttpResultsModel();
|
|
|
|
void addReply(QNetworkReply *reply);
|
|
|
|
void abort();
|
|
|
|
|
|
|
|
protected:
|
|
|
|
HttpResultsModel(QNetworkReply *reply);
|
|
|
|
virtual void parseResults(QNetworkReply *reply, const QByteArray &data) = 0;
|
2016-03-03 22:21:15 +01:00
|
|
|
|
|
|
|
private slots:
|
2016-03-06 17:52:33 +01:00
|
|
|
void handleReplyFinished();
|
|
|
|
|
|
|
|
protected:
|
|
|
|
QList<QNetworkReply *> m_replies;
|
|
|
|
};
|
|
|
|
|
|
|
|
HttpResultsModel::HttpResultsModel(QNetworkReply *reply)
|
|
|
|
{
|
|
|
|
addReply(reply);
|
|
|
|
}
|
|
|
|
|
|
|
|
HttpResultsModel::~HttpResultsModel()
|
|
|
|
{
|
|
|
|
qDeleteAll(m_replies);
|
|
|
|
}
|
|
|
|
|
|
|
|
void copyProperty(const char *property, const QObject *from, QObject *to)
|
|
|
|
{
|
|
|
|
to->setProperty(property, from->property(property));
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpResultsModel::handleReplyFinished()
|
|
|
|
{
|
|
|
|
auto *reply = static_cast<QNetworkReply *>(sender());
|
|
|
|
if(reply->error() == QNetworkReply::NoError) {
|
|
|
|
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());
|
|
|
|
// -> always follow when retrieving cover art, otherwise ask user
|
|
|
|
bool follow = reply->property("coverArt").toBool();
|
|
|
|
if(!follow) {
|
|
|
|
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());
|
|
|
|
follow = QMessageBox::question(nullptr, tr("Search"), message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes;
|
|
|
|
}
|
|
|
|
if(follow) {
|
|
|
|
QNetworkReply *newReply = networkAccessManager().get(QNetworkRequest(newUrl));
|
|
|
|
// retain possible assigned dynamic properties (TODO: implement it in a better way)
|
|
|
|
copyProperty("coverArt", reply, newReply);
|
|
|
|
copyProperty("albumId", reply, newReply);
|
|
|
|
copyProperty("row", reply, newReply);
|
|
|
|
addReply(newReply);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
m_errorList << tr("Redirection to: ") + newUrl.toString();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
QByteArray data = reply->readAll();
|
|
|
|
#ifdef DEBUG_BUILD
|
|
|
|
std::cerr << "Results from HTTP query:" << std::endl;
|
|
|
|
std::cerr << data.data() << std::endl;
|
|
|
|
#endif
|
|
|
|
parseResults(reply, data);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
m_errorList << reply->errorString();
|
|
|
|
}
|
|
|
|
|
|
|
|
// delete reply
|
|
|
|
reply->deleteLater();
|
|
|
|
m_replies.removeAll(reply);
|
|
|
|
|
|
|
|
// update status, emit resultsAvailable()
|
|
|
|
setResultsAvailable(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpResultsModel::addReply(QNetworkReply *reply)
|
|
|
|
{
|
|
|
|
m_replies << reply;
|
|
|
|
#ifdef DEBUG_BUILD
|
|
|
|
std::cerr << "HTTP query: " << reply->url().toString().toLocal8Bit().data() << std::endl;
|
|
|
|
#endif
|
|
|
|
connect(reply, &QNetworkReply::finished, this, &HttpResultsModel::handleReplyFinished);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class MusicBrainzResultsModel : public HttpResultsModel
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
private:
|
|
|
|
enum What {
|
|
|
|
MusicBrainzMetaData,
|
|
|
|
CoverArt
|
|
|
|
};
|
|
|
|
|
|
|
|
public:
|
|
|
|
MusicBrainzResultsModel(QNetworkReply *reply);
|
|
|
|
bool fetchCover(const QModelIndex &index);
|
|
|
|
static QNetworkRequest coverRequest(const QString &albumId);
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void parseResults(QNetworkReply *reply, const QByteArray &data);
|
2016-03-03 22:21:15 +01:00
|
|
|
|
|
|
|
private:
|
2016-03-06 17:52:33 +01:00
|
|
|
static map<QString, QByteArray> m_coverData;
|
2016-03-03 22:21:15 +01:00
|
|
|
QXmlStreamReader m_reader;
|
2016-03-06 17:52:33 +01:00
|
|
|
What m_what;
|
2016-03-03 22:21:15 +01:00
|
|
|
};
|
|
|
|
|
2016-03-06 17:52:33 +01:00
|
|
|
map<QString, QByteArray> MusicBrainzResultsModel::m_coverData = map<QString, QByteArray>();
|
|
|
|
|
2016-03-03 22:21:15 +01:00
|
|
|
MusicBrainzResultsModel::MusicBrainzResultsModel(QNetworkReply *reply) :
|
2016-03-06 17:52:33 +01:00
|
|
|
HttpResultsModel(reply)
|
|
|
|
{}
|
2016-03-03 22:21:15 +01:00
|
|
|
|
2016-03-06 17:52:33 +01:00
|
|
|
bool MusicBrainzResultsModel::fetchCover(const QModelIndex &index)
|
2016-03-03 22:21:15 +01:00
|
|
|
{
|
2016-03-06 17:52:33 +01:00
|
|
|
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
|
|
|
|
QNetworkReply *reply = queryCoverArtArchive(desc.albumId);
|
|
|
|
addReply(reply);
|
|
|
|
reply->setProperty("coverArt", true);
|
|
|
|
reply->setProperty("albumId", desc.albumId);
|
|
|
|
reply->setProperty("row", index.row());
|
|
|
|
setFetchingCover(true);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
m_errorList << tr("Unable to fetch cover: Album ID is unknown.");
|
|
|
|
emit resultsAvailable();
|
|
|
|
}
|
2016-03-03 22:21:15 +01:00
|
|
|
}
|
2016-03-06 17:52:33 +01:00
|
|
|
return true;
|
2016-03-03 22:21:15 +01:00
|
|
|
}
|
|
|
|
|
2016-03-06 17:52:33 +01:00
|
|
|
void MusicBrainzResultsModel::parseResults(QNetworkReply *reply, const QByteArray &data)
|
2016-03-03 22:21:15 +01:00
|
|
|
{
|
2016-03-06 17:52:33 +01:00
|
|
|
if(reply->property("coverArt").toBool()) {
|
|
|
|
// add cover -> determine album ID and row
|
|
|
|
bool ok;
|
|
|
|
QString albumId = reply->property("albumId").toString();
|
|
|
|
int row = reply->property("row").toInt(&ok);
|
|
|
|
if(!albumId.isEmpty() && ok && row < m_results.size()) {
|
|
|
|
m_coverData[albumId] = data;
|
|
|
|
m_results[row].cover = data;
|
|
|
|
emit coverAvailable(index(row, 0));
|
|
|
|
} else {
|
|
|
|
m_errorList << tr("Cover reply is invalid (internal error).");
|
|
|
|
}
|
|
|
|
setFetchingCover(false);
|
|
|
|
} else {
|
|
|
|
// prepare parsing MusicBrainz meta data
|
|
|
|
beginResetModel();
|
|
|
|
m_results.clear();
|
|
|
|
m_reader.addData(data);
|
|
|
|
|
2016-09-10 17:19:48 +02:00
|
|
|
// parse XML tree
|
2016-03-06 17:52:33 +01:00
|
|
|
#define xmlReader m_reader
|
|
|
|
#include <qtutilities/misc/xmlparsermacros.h>
|
2016-03-03 22:21:15 +01:00
|
|
|
children {
|
|
|
|
iftag("metadata") {
|
|
|
|
children {
|
|
|
|
iftag("recording-list") {
|
|
|
|
children {
|
|
|
|
iftag("recording") {
|
|
|
|
SongDescription currentDescription;
|
|
|
|
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
|
|
|
|
}
|
|
|
|
} eliftag("release-list") {
|
|
|
|
children {
|
|
|
|
iftag("release") {
|
2016-03-06 17:52:33 +01:00
|
|
|
if(currentDescription.albumId.isEmpty()) {
|
|
|
|
currentDescription.albumId = attribute("id").toString();
|
|
|
|
}
|
2016-03-03 22:21:15 +01:00
|
|
|
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();
|
|
|
|
} else_skip
|
|
|
|
}
|
|
|
|
} else_skip
|
|
|
|
}
|
|
|
|
} else_skip
|
|
|
|
}
|
|
|
|
} else_skip
|
|
|
|
}
|
|
|
|
} else_skip
|
|
|
|
}
|
|
|
|
} else_skip
|
|
|
|
}
|
|
|
|
} eliftag("tag-list") {
|
|
|
|
children {
|
|
|
|
iftag("tag") {
|
|
|
|
children {
|
|
|
|
iftag("name") {
|
|
|
|
if(!currentDescription.genre.isEmpty()) {
|
|
|
|
currentDescription.genre.append(QLatin1Char(' '));
|
|
|
|
}
|
|
|
|
currentDescription.genre.append(text);
|
|
|
|
} else_skip
|
|
|
|
}
|
|
|
|
} else_skip
|
|
|
|
}
|
|
|
|
} else_skip
|
|
|
|
}
|
|
|
|
m_results << currentDescription;
|
|
|
|
} else_skip
|
|
|
|
}
|
|
|
|
} else_skip
|
|
|
|
}
|
|
|
|
} else_skip
|
|
|
|
}
|
2016-03-06 17:52:33 +01:00
|
|
|
#include <qtutilities/misc/undefxmlparsermacros.h>
|
|
|
|
|
2016-03-03 22:21:15 +01:00
|
|
|
// check for parsing errors
|
|
|
|
switch(m_reader.error()) {
|
|
|
|
case QXmlStreamReader::NoError:
|
|
|
|
case QXmlStreamReader::PrematureEndOfDocumentError:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
m_errorList << m_reader.errorString();
|
|
|
|
}
|
|
|
|
|
2016-03-06 17:52:33 +01:00
|
|
|
// promote changes
|
|
|
|
endResetModel();
|
2016-03-03 22:21:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QueryResultsModel *queryMusicBrainz(const SongDescription &songDescription)
|
|
|
|
{
|
2016-03-06 17:52:33 +01:00
|
|
|
static QString defaultMusicBrainzUrl(QStringLiteral("https://musicbrainz.org/ws/2/recording/"));
|
2016-03-03 22:21:15 +01:00
|
|
|
|
|
|
|
// compose parts
|
|
|
|
QStringList parts;
|
|
|
|
parts.reserve(4);
|
|
|
|
if(!songDescription.title.isEmpty()) {
|
|
|
|
parts << QChar('\"') % songDescription.title % QChar('\"');
|
|
|
|
}
|
|
|
|
if(!songDescription.artist.isEmpty()) {
|
|
|
|
parts << QStringLiteral("artist:\"") % songDescription.artist % QChar('\"');
|
|
|
|
}
|
|
|
|
if(!songDescription.album.isEmpty()) {
|
|
|
|
parts << QStringLiteral("release:\"") % songDescription.album % QChar('\"');
|
|
|
|
}
|
2016-03-05 16:50:23 +01:00
|
|
|
if(songDescription.track) {
|
|
|
|
parts << QStringLiteral("number:") + QString::number(songDescription.track);
|
|
|
|
}
|
2016-03-03 22:21:15 +01:00
|
|
|
|
|
|
|
// compose URL
|
2016-03-06 17:52:33 +01:00
|
|
|
QUrl url(Settings::musicBrainzUrl().isEmpty() ? defaultMusicBrainzUrl : (Settings::musicBrainzUrl() + QStringLiteral("/recording/")));
|
2016-03-03 22:21:15 +01:00
|
|
|
QUrlQuery query;
|
|
|
|
query.addQueryItem(QStringLiteral("query"), parts.join(QStringLiteral(" AND ")));
|
|
|
|
url.setQuery(query);
|
|
|
|
|
|
|
|
// make request
|
|
|
|
return new MusicBrainzResultsModel(Utility::networkAccessManager().get(QNetworkRequest(url)));
|
|
|
|
}
|
|
|
|
|
2016-03-06 17:52:33 +01:00
|
|
|
QNetworkReply *queryCoverArtArchive(const QString &albumId)
|
|
|
|
{
|
|
|
|
static QString defaultArchiveUrl(QStringLiteral("https://coverartarchive.org"));
|
|
|
|
return networkAccessManager().get(QNetworkRequest(QUrl((Settings::coverArtArchiveUrl().isEmpty() ? defaultArchiveUrl : Settings::coverArtArchiveUrl()) % QStringLiteral("/release/") % albumId % QStringLiteral("/front"))));
|
|
|
|
}
|
|
|
|
|
2016-03-03 22:21:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#include "dbquery.moc"
|