diff --git a/ext/libclementine-common/CMakeLists.txt b/ext/libclementine-common/CMakeLists.txt index 60cb945b4..a1b5c3d32 100644 --- a/ext/libclementine-common/CMakeLists.txt +++ b/ext/libclementine-common/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --std=c++0x") set(SOURCES core/closure.cpp + core/latch.cpp core/logging.cpp core/messagehandler.cpp core/messagereply.cpp @@ -16,6 +17,7 @@ set(SOURCES set(HEADERS core/closure.h + core/latch.h core/messagehandler.h core/messagereply.h core/workerpool.h diff --git a/ext/libclementine-common/core/latch.cpp b/ext/libclementine-common/core/latch.cpp new file mode 100644 index 000000000..1198d91d4 --- /dev/null +++ b/ext/libclementine-common/core/latch.cpp @@ -0,0 +1,36 @@ +/* This file is part of Clementine. + Copyright 2016, John Maguire + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include "latch.h" + +#include "core/logging.h" + +CountdownLatch::CountdownLatch() : count_(0) {} + +void CountdownLatch::Wait() { + QMutexLocker l(&mutex_); + ++count_; +} + +void CountdownLatch::CountDown() { + QMutexLocker l(&mutex_); + Q_ASSERT(count_ > 0); + --count_; + qLog(Debug) << "Decrement:" << count_; + if (count_ == 0) { + emit Done(); + } +} diff --git a/ext/libclementine-common/core/latch.h b/ext/libclementine-common/core/latch.h new file mode 100644 index 000000000..bec6f6455 --- /dev/null +++ b/ext/libclementine-common/core/latch.h @@ -0,0 +1,38 @@ +/* This file is part of Clementine. + Copyright 2016, John Maguire + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef CORE_LATCH_H +#define CORE_LATCH_H + +#include +#include + +class CountdownLatch : public QObject { + Q_OBJECT + public: + CountdownLatch(); + void Wait(); + void CountDown(); + +signals: + void Done(); + + private: + QMutex mutex_; + int count_; +}; + +#endif // CORE_LATCH_H diff --git a/src/songinfo/artistbiography.cpp b/src/songinfo/artistbiography.cpp index 28304d2fd..0d8c15696 100644 --- a/src/songinfo/artistbiography.cpp +++ b/src/songinfo/artistbiography.cpp @@ -22,11 +22,18 @@ #include #include "core/closure.h" +#include "core/latch.h" +#include "core/logging.h" #include "core/network.h" #include "songinfo/songinfotextview.h" namespace { const char* kArtistBioUrl = "https://data.clementine-player.org/fetchbio"; +const char* kWikipediaImageListUrl = + "https://en.wikipedia.org/w/api.php?action=query&prop=images&format=json"; +const char* kWikipediaImageInfoUrl = + "https://en.wikipedia.org/w/" + "api.php?action=query&prop=imageinfo&iiprop=url&format=json"; QString GetLocale() { QLocale locale; @@ -75,6 +82,105 @@ void ArtistBiography::FetchInfo(int id, const Song& metadata) { editor->SetHtml(text); data.contents_ = editor; emit InfoReady(id, data); - emit Finished(id); + + if (url.contains("wikipedia.org")) { + FetchWikipediaImages(id, url); + } else { + emit Finished(id); + } + }); +} + +namespace { + +QStringList ExtractImageTitles(const QVariantMap& json) { + QStringList ret; + for (auto it = json.constBegin(); it != json.constEnd(); ++it) { + if (it.value().type() == QVariant::Map) { + ret.append(ExtractImageTitles(it.value().toMap())); + } else if (it.key() == "images" && it.value().type() == QVariant::List) { + QVariantList images = it.value().toList(); + for (QVariant i : images) { + QVariantMap image = i.toMap(); + QString image_title = image["title"].toString(); + if (!image_title.isEmpty() && + ( + // SVGs tend to be irrelevant icons. + image_title.endsWith(".jpg", Qt::CaseInsensitive) || + image_title.endsWith(".png", Qt::CaseInsensitive))) { + ret.append(image_title); + } + } + } + } + return ret; +} + +QString ExtractImageUrl(const QVariantMap& json) { + for (auto it = json.constBegin(); it != json.constEnd(); ++it) { + if (it.value().type() == QVariant::Map) { + QString r = ExtractImageUrl(it.value().toMap()); + if (!r.isEmpty()) { + return r; + } + } else if (it.key() == "imageinfo") { + QVariantList imageinfos = it.value().toList(); + QVariantMap imageinfo = imageinfos.first().toMap(); + return imageinfo["url"].toString(); + } + } + return QString::null; +} + +} // namespace + +void ArtistBiography::FetchWikipediaImages(int id, + const QString& wikipedia_url) { + QRegExp regex("/wiki/(.*)"); + if (regex.indexIn(wikipedia_url) == -1) { + emit Finished(id); + return; + } + QString wiki_title = regex.cap(1); + QUrl url(kWikipediaImageListUrl); + url.addQueryItem("titles", wiki_title); + + qLog(Debug) << "Wikipedia images:" << url; + + QNetworkRequest request(url); + QNetworkReply* reply = network_->get(request); + NewClosure(reply, SIGNAL(finished()), [this, id, reply]() { + reply->deleteLater(); + + QJson::Parser parser; + QVariantMap response = parser.parse(reply).toMap(); + + QStringList image_titles = ExtractImageTitles(response); + + CountdownLatch* latch = new CountdownLatch; + NewClosure(latch, SIGNAL(Done()), [this, latch, id]() { + latch->deleteLater(); + emit Finished(id); + }); + + for (const QString& image_title : image_titles) { + latch->Wait(); + QUrl url(kWikipediaImageInfoUrl); + url.addQueryItem("titles", image_title); + + QNetworkRequest request(url); + QNetworkReply* reply = network_->get(request); + NewClosure(reply, SIGNAL(finished()), [this, id, reply, latch]() { + reply->deleteLater(); + QJson::Parser parser; + QVariantMap json = parser.parse(reply).toMap(); + QString url = ExtractImageUrl(json); + qLog(Debug) << "Found wikipedia image url:" << url; + if (!url.isEmpty()) { + emit ImageReady(id, QUrl(url)); + } + latch->CountDown(); + }); + } }); } diff --git a/src/songinfo/artistbiography.h b/src/songinfo/artistbiography.h index eb6da873b..a15eaa3e8 100644 --- a/src/songinfo/artistbiography.h +++ b/src/songinfo/artistbiography.h @@ -34,6 +34,8 @@ class ArtistBiography : public SongInfoProvider { void FetchInfo(int id, const Song& metadata) override; private: + void FetchWikipediaImages(int id, const QString& title); + std::unique_ptr network_; }; diff --git a/src/widgets/prettyimage.cpp b/src/widgets/prettyimage.cpp index 3b8536d83..bfb3959f6 100644 --- a/src/widgets/prettyimage.cpp +++ b/src/widgets/prettyimage.cpp @@ -88,7 +88,8 @@ void PrettyImage::ImageFetched(RedirectFollower* follower) { QImage image = QImage::fromData(reply->readAll()); if (image.isNull()) { - qLog(Debug) << "Image failed to load" << reply->request().url(); + qLog(Debug) << "Image failed to load" << reply->request().url() + << reply->error(); deleteLater(); } else { state_ = State_CreatingThumbnail;