@@ -0,0 +1,231 @@
2+ * Copyright 2013 Canonical Ltd.
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ * Authors: Jussi Pakkanen <jussi.pakkanen@canonical.com>
17+ * Pawel Stolowski <pawel.stolowski@canonical.com>
20+#include "albumartprovider.h"
22+#include <QNetworkAccessManager>
24+#include <QNetworkRequest>
25+#include <QNetworkReply>
27+#include <QTemporaryFile>
31+#include <QStringList>
38+const std::string AlbumArtProvider::DEFAULT_ALBUM_ART = "/usr/share/unity/icons/album_missing.png";
39+const std::string AlbumArtProvider::UNITY_LENS_SCHEMA = "com.canonical.Unity.Lenses";
41+AlbumArtProvider::AlbumArtProvider()
42+ : QQuickImageProvider(QQmlImageProviderBase::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading),
45+ auto schemas = g_settings_list_schemas();
47+ for (int i = 0; schemas[i]; i++) {
48+ if (g_strcmp0(schemas[i], UNITY_LENS_SCHEMA.c_str()) == 0) {
49+ m_settings = g_settings_new(UNITY_LENS_SCHEMA.c_str());
54+ if (m_settings == nullptr) {
55+ qWarning() << "Missing" << QString::fromStdString(UNITY_LENS_SCHEMA) << "schema";
59+AlbumArtProvider::~AlbumArtProvider()
62+ g_object_unref(m_settings);
65+std::string AlbumArtProvider::get_lastfm_url(const albuminfo &ai) {
66+ QString artist = QString::fromStdString(ai.artist);
67+ QString album = QString::fromStdString(ai.album);
69+ /// @todo: this is the old API which will probably get axed at some point in the future
70+ /// The new 2.0 API requires an API key, but supports JSON output, etc, so switching
71+ /// to it should be done ASAP.
72+ QString request = QString("http://ws.audioscrobbler.com/1.0/album/%1/%2/info.xml").arg(artist)
75+ QScopedPointer<QNetworkAccessManager> am(new QNetworkAccessManager);
76+ QNetworkReply *reply = am->get(QNetworkRequest(QUrl(request)));
78+ QObject::connect(am.data(), &QNetworkAccessManager::finished, &loop, &QEventLoop::quit);
81+ if (reply->error() != QNetworkReply::NoError) {
82+ qWarning() << "Error getting the XML:" << reply->errorString();
88+ tmp.setData(reply->readAll());
89+ tmp.open(QIODevice::ReadOnly);
90+ query.bindVariable("reply", &tmp);
91+ query.setQuery("doc($reply)/album/coverart/large[1]/text()");
93+ query.evaluateTo(&image);
94+ image = image.trimmed();
96+ // return empty if last.fm tries to offer their default
98+ if (image.contains("catalogue/noimage")) {
102+ return image.toStdString();
105+bool AlbumArtProvider::download_and_store(const std::string &image_url, const std::string &output_file) {
106+ QString url = QString::fromStdString(image_url);
107+ QString fileName = QString::fromStdString(output_file);
109+ QScopedPointer<QNetworkAccessManager> am(new QNetworkAccessManager);
110+ QNetworkReply *reply = am->get(QNetworkRequest(QUrl(url)));
112+ QObject::connect(am.data(), &QNetworkAccessManager::finished, &loop, &QEventLoop::quit);
115+ if (reply->error() != QNetworkReply::NoError) {
116+ qWarning() << "Error getting the image:" << reply->errorString();
120+ QFile file(fileName);
121+ if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
122+ qWarning() << "Could not open file for writing:" << file.errorString();
125+ if (file.write(reply->readAll()) == -1){
126+ qWarning() << "Could not write the image:" << file.error();
133+void AlbumArtProvider::fix_format(const std::string &fname) {
134+ // MediaArtSpec requires jpg. Convert to it if necessary.
135+ FILE *f = fopen(fname.c_str(), "r");
138+ unsigned char buf[2];
139+ fread(buf, 1, 2, f);
141+ if(buf[0] == 0xff && buf[1] == 0xd8) {
144+ QImage im(fname.c_str());
145+ im.save(fname.c_str(), "JPEG");
148+std::string AlbumArtProvider::get_image(const std::string &artist, const std::string &album) {
151+ info.artist = artist;
154+ if(info.album.empty() || info.artist.empty()) {
155+ return DEFAULT_ALBUM_ART;
157+ if(cache.has_art(info.artist, info.album)) {
158+ // Image may have expired from cache between these two lines.
159+ // It might expire before we return. C'est la vie.
160+ return cache.get_art_file(info.artist, info.album);
163+ if (m_settings != nullptr && g_strcmp0(g_settings_get_string(m_settings, "remote-content-search"), "all") !=0) {
164+ qDebug() << "Remote content disabled";
165+ return DEFAULT_ALBUM_ART;
168+ std::string image_url = get_lastfm_url(info);
169+ if(image_url.empty()) {
170+ return DEFAULT_ALBUM_ART;
172+ QTemporaryFile tempFile;
174+ tempFile.setAutoRemove(true);
175+ QString fname = tempFile.fileName();
176+ std::string tmpname = fname.toUtf8().data();
178+ if(!download_and_store(image_url, tmpname)) {
179+ return DEFAULT_ALBUM_ART;
181+ fix_format(tmpname.c_str());
183+ if(!f.open(QIODevice::ReadWrite)) {
184+ return DEFAULT_ALBUM_ART;
186+ QByteArray arr = f.readAll();
189+ cache.add_art(info.artist, info.album, arr.data(), arr.size());
191+ std::string res = cache.get_art_file(info.artist, info.album);
193+ return DEFAULT_ALBUM_ART;
197+ return DEFAULT_ALBUM_ART;
200+QImage AlbumArtProvider::requestImage(const QString &id, QSize *realSize, const QSize &requestedSize) {
201+ Q_UNUSED(requestedSize)
203+ const QStringList parts = id.split("/");
204+ if (parts.size() != 2) {
205+ qWarning() << "Invalid albumart uri:" << id;
206+ return QImage(QString::fromStdString(DEFAULT_ALBUM_ART));
208+ const std::string artist = QUrl::fromPercentEncoding(parts[0].toUtf8()).toStdString();
209+ const std::string album = QUrl::fromPercentEncoding(parts[1].toUtf8()).toStdString();
211+ std::string tgt_path;
213+ tgt_path = get_image(artist, album);
214+ if(!tgt_path.empty()) {
215+ QString tgt(tgt_path.c_str());
218+ // FIXME: Rescale to requested size preserving aspect.
219+ *realSize = image.size();
222+ } catch(std::exception &e) {
223+ qDebug() << "Album art loader failed: " << e.what();
225+ qDebug() << "Unknown error when generating image.";
228+ QImage fallback(QString::fromStdString(DEFAULT_ALBUM_ART));
229+ *realSize = fallback.size();