317 lines
10 KiB
C++
317 lines
10 KiB
C++
/*
|
|
* Strawberry Music Player
|
|
* Copyright 2020, Jonas Kvinge <jonas@jkvinge.net>
|
|
*
|
|
* Strawberry is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Strawberry is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include <QObject>
|
|
#include <QList>
|
|
#include <QString>
|
|
#include <QStringList>
|
|
#include <QUrl>
|
|
#include <QUrlQuery>
|
|
#include <QJsonDocument>
|
|
#include <QJsonArray>
|
|
#include <QJsonObject>
|
|
#include <QJsonValue>
|
|
#include <QNetworkReply>
|
|
|
|
#include "core/logging.h"
|
|
#include "core/networkaccessmanager.h"
|
|
#include "core/iconloader.h"
|
|
#include "core/latch.h"
|
|
|
|
#include "widgets/infotextview.h"
|
|
#include "wikipediaartistbio.h"
|
|
|
|
const char *WikipediaArtistBio::kApiUrl = "https://en.wikipedia.org/w/api.php";
|
|
const int WikipediaArtistBio::kMinimumImageSize = 400;
|
|
|
|
WikipediaArtistBio::WikipediaArtistBio() : ArtistBioProvider(), network_(new NetworkAccessManager(this)) {}
|
|
|
|
WikipediaArtistBio::~WikipediaArtistBio() {
|
|
|
|
while (!replies_.isEmpty()) {
|
|
QNetworkReply *reply = replies_.takeFirst();
|
|
disconnect(reply, nullptr, this, nullptr);
|
|
if (reply->isRunning()) reply->abort();
|
|
reply->deleteLater();
|
|
}
|
|
|
|
}
|
|
|
|
QNetworkReply *WikipediaArtistBio::CreateRequest(QList<Param> ¶ms) {
|
|
|
|
params << Param("format", "json");
|
|
params << Param("action", "query");
|
|
|
|
QUrlQuery url_query;
|
|
for (const Param ¶m : params) {
|
|
url_query.addQueryItem(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second));
|
|
}
|
|
|
|
QUrl url(kApiUrl);
|
|
url.setQuery(url_query);
|
|
QNetworkRequest req(url);
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
|
|
req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
|
|
#else
|
|
req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
|
#endif
|
|
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
|
|
|
QNetworkReply *reply = network_->get(req);
|
|
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(HandleSSLErrors(QList<QSslError>)));
|
|
replies_ << reply;
|
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
QByteArray WikipediaArtistBio::GetReplyData(QNetworkReply *reply) {
|
|
|
|
QByteArray data;
|
|
|
|
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
|
data = reply->readAll();
|
|
}
|
|
else {
|
|
if (reply->error() == QNetworkReply::NoError) {
|
|
qLog(Error) << "Wikipedia artist biography error: Received HTTP code" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
}
|
|
else {
|
|
qLog(Error) << "Wikipedia artist biography error:" << reply->error() << reply->errorString();
|
|
}
|
|
}
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
QJsonObject WikipediaArtistBio::ExtractJsonObj(const QByteArray &data) {
|
|
|
|
if (data.isEmpty()) return QJsonObject();
|
|
|
|
QJsonParseError json_error;
|
|
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
|
|
|
if (json_error.error != QJsonParseError::NoError) {
|
|
qLog(Error) << "Wikipedia artist biography error: Failed to parse json data:" << json_error.errorString();
|
|
return QJsonObject();
|
|
}
|
|
|
|
if (json_doc.isEmpty()) {
|
|
qLog(Error) << "Wikipedia artist biography error: Received empty Json document.";
|
|
return QJsonObject();
|
|
}
|
|
|
|
if (!json_doc.isObject()) {
|
|
qLog(Error) << "Wikipedia artist biography error: Json document is not an object.";
|
|
return QJsonObject();
|
|
}
|
|
|
|
QJsonObject json_obj = json_doc.object();
|
|
if (json_obj.isEmpty()) {
|
|
qLog(Error) << "Wikipedia artist biography error: Received empty Json object.";
|
|
return QJsonObject();
|
|
}
|
|
|
|
return json_obj;
|
|
|
|
}
|
|
|
|
void WikipediaArtistBio::HandleSSLErrors(QList<QSslError>) {}
|
|
|
|
void WikipediaArtistBio::Start(const int id, const Song &metadata) {
|
|
|
|
if (metadata.artist().isEmpty()) {
|
|
emit Finished(id);
|
|
return;
|
|
}
|
|
|
|
CountdownLatch *latch = new CountdownLatch;
|
|
connect(latch, &CountdownLatch::Done, [this, id, latch](){
|
|
latch->deleteLater();
|
|
emit Finished(id);
|
|
});
|
|
|
|
GetImageTitles(id, metadata.artist(), latch);
|
|
//GetArticle(id, metadata.artist(), latch);
|
|
|
|
}
|
|
|
|
void WikipediaArtistBio::GetArticle(const int id, const QString &artist, CountdownLatch *latch) {
|
|
|
|
latch->Wait();
|
|
|
|
ParamList params = ParamList() << Param("titles", artist)
|
|
<< Param("prop", "extracts");
|
|
|
|
QNetworkReply *reply = CreateRequest(params);
|
|
connect(reply, &QNetworkReply::finished, [this, reply, id, latch]() { GetArticleReply(reply, id, latch); });
|
|
|
|
}
|
|
|
|
void WikipediaArtistBio::GetArticleReply(QNetworkReply *reply, const int id, CountdownLatch *latch) {
|
|
|
|
reply->deleteLater();
|
|
replies_.removeAll(reply);
|
|
|
|
QJsonObject json_obj = ExtractJsonObj(GetReplyData(reply));
|
|
|
|
QString title;
|
|
QString text;
|
|
if (!json_obj.isEmpty() && json_obj.contains("query") && json_obj["query"].isObject()) {
|
|
json_obj = json_obj["query"].toObject();
|
|
if (json_obj.contains("pages") && json_obj["pages"].isObject()) {
|
|
QJsonObject value_pages = json_obj["pages"].toObject();
|
|
for (const QJsonValue value_page : value_pages) {
|
|
if (!value_page.isObject()) continue;
|
|
QJsonObject obj_page = value_page.toObject();
|
|
if (!obj_page.contains("title") || !obj_page.contains("extract")) continue;
|
|
title = obj_page["title"].toString();
|
|
text = obj_page["extract"].toString();
|
|
}
|
|
}
|
|
}
|
|
|
|
CollapsibleInfoPane::Data info_data;
|
|
info_data.id_ = title;
|
|
info_data.title_ = tr("Biography");
|
|
info_data.type_ = CollapsibleInfoPane::Data::Type_Biography;
|
|
info_data.icon_ = IconLoader::Load("wikipedia");
|
|
InfoTextView *editor = new InfoTextView;
|
|
editor->SetHtml(text);
|
|
info_data.contents_ = editor;
|
|
emit InfoReady(id, info_data);
|
|
|
|
latch->CountDown();
|
|
|
|
}
|
|
|
|
void WikipediaArtistBio::GetImageTitles(const int id, const QString &artist, CountdownLatch *latch) {
|
|
|
|
latch->Wait();
|
|
|
|
ParamList params = ParamList() << Param("titles", artist)
|
|
<< Param("prop", "images")
|
|
<< Param("imlimit", "25");
|
|
|
|
QNetworkReply *reply = CreateRequest(params);
|
|
connect(reply, &QNetworkReply::finished, [this, reply, id, latch]() { GetImageTitlesFinished(reply, id, latch); });
|
|
|
|
}
|
|
|
|
void WikipediaArtistBio::GetImageTitlesFinished(QNetworkReply *reply, const int id, CountdownLatch *latch) {
|
|
|
|
reply->deleteLater();
|
|
replies_.removeAll(reply);
|
|
|
|
QJsonObject json_obj = ExtractJsonObj(GetReplyData(reply));
|
|
|
|
QString title;
|
|
QStringList titles;
|
|
if (!json_obj.isEmpty() && json_obj.contains("query") && json_obj["query"].isObject()) {
|
|
json_obj = json_obj["query"].toObject();
|
|
if (json_obj.contains("pages") && json_obj["pages"].isObject()) {
|
|
QJsonObject value_pages = json_obj["pages"].toObject();
|
|
for (const QJsonValue value_page : value_pages) {
|
|
if (!value_page.isObject()) continue;
|
|
QJsonObject obj_page = value_page.toObject();
|
|
if (!obj_page.contains("title") || !obj_page.contains("images") || !obj_page["images"].isArray()) continue;
|
|
title = obj_page["title"].toString();
|
|
QJsonArray array_images = obj_page["images"].toArray();
|
|
for (const QJsonValue value_image : array_images) {
|
|
if (!value_image.isObject()) continue;
|
|
QJsonObject obj_image = value_image.toObject();
|
|
if (!obj_image.contains("title")) continue;
|
|
QString filename = obj_image["title"].toString();
|
|
if (filename.endsWith(".jpg", Qt::CaseInsensitive) || filename.endsWith(".png", Qt::CaseInsensitive)) {
|
|
titles << filename;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const QString &image_title : titles) {
|
|
GetImage(id, image_title, latch);
|
|
}
|
|
|
|
latch->CountDown();
|
|
|
|
}
|
|
|
|
void WikipediaArtistBio::GetImage(const int id, const QString &title, CountdownLatch *latch) {
|
|
|
|
latch->Wait();
|
|
|
|
ParamList params2 = ParamList() << Param("titles", title)
|
|
<< Param("prop", "imageinfo")
|
|
<< Param("iiprop", "url|size");
|
|
|
|
QNetworkReply *reply = CreateRequest(params2);
|
|
connect(reply, &QNetworkReply::finished, [this, reply, id, latch]() { GetImageFinished(reply, id, latch); });
|
|
|
|
}
|
|
|
|
void WikipediaArtistBio::GetImageFinished(QNetworkReply *reply, const int id, CountdownLatch *latch) {
|
|
|
|
reply->deleteLater();
|
|
replies_.removeAll(reply);
|
|
|
|
QJsonObject json_obj = ExtractJsonObj(GetReplyData(reply));
|
|
|
|
if (!json_obj.isEmpty()) {
|
|
QList<QUrl> urls = ExtractImageUrls(json_obj);
|
|
for (const QUrl &url : urls) {
|
|
emit ImageReady(id, url);
|
|
}
|
|
}
|
|
|
|
latch->CountDown();
|
|
|
|
}
|
|
|
|
QList<QUrl> WikipediaArtistBio::ExtractImageUrls(QJsonObject json_obj) {
|
|
|
|
QList<QUrl> urls;
|
|
if (json_obj.contains("query") && json_obj["query"].isObject()) {
|
|
json_obj = json_obj["query"].toObject();
|
|
if (json_obj.contains("pages") && json_obj["pages"].isObject()) {
|
|
QJsonObject value_pages = json_obj["pages"].toObject();
|
|
for (const QJsonValue value_page : value_pages) {
|
|
if (!value_page.isObject()) continue;
|
|
QJsonObject obj_page = value_page.toObject();
|
|
if (!obj_page.contains("title") || !obj_page.contains("imageinfo") || !obj_page["imageinfo"].isArray()) continue;
|
|
QJsonArray array_images = obj_page["imageinfo"].toArray();
|
|
for (const QJsonValue value_image : array_images) {
|
|
if (!value_image.isObject()) continue;
|
|
QJsonObject obj_image = value_image.toObject();
|
|
if (!obj_image.contains("url") || !obj_image.contains("width") || !obj_image.contains("height")) continue;
|
|
QUrl url(obj_image["url"].toString());
|
|
const int width = obj_image["width"].toInt();
|
|
const int height = obj_image["height"].toInt();
|
|
if (!url.isValid() || width < kMinimumImageSize || height < kMinimumImageSize) continue;
|
|
urls << url;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return urls;
|
|
|
|
}
|