File indexing completed on 2023-05-30 11:30:53

0001 /**
0002  * Copyright (C) 2004 Nathan Toone <nathan@toonetown.com>
0003  * Copyright (C) 2007, 2017 Michael Pyne <mpyne@kde.org>
0004  * Copyright (C) 2012 Martin Sandsmark <martin.sandsmark@kde.org>
0005  *
0006  * This program is free software; you can redistribute it and/or modify it under
0007  * the terms of the GNU General Public License as published by the Free Software
0008  * Foundation; either version 2 of the License, or (at your option) any later
0009  * version.
0010  *
0011  * This program is distributed in the hope that it will be useful, but WITHOUT ANY
0012  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0013  * PARTICULAR PURPOSE. See the GNU General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU General Public License along with
0016  * this program.  If not, see <http://www.gnu.org/licenses/>.
0017  */
0018 
0019 #include "webimagefetcher.h"
0020 
0021 #include <KLocalizedString>
0022 #include <KIO/Job>
0023 #include <KMessageBox>
0024 
0025 #include "covermanager.h"
0026 #include "filehandle.h"
0027 #include "juktag.h"
0028 #include "juk.h"
0029 #include "juk_debug.h"
0030 
0031 #include <QDialog>
0032 #include <QDialogButtonBox>
0033 #include <QDomDocument>
0034 #include <QDomElement>
0035 #include <QLabel>
0036 #include <QLayout>
0037 #include <QPixmap>
0038 #include <QPointer>
0039 #include <QPushButton>
0040 #include <QStatusBar>
0041 #include <QUrl>
0042 #include <QUrlQuery>
0043 
0044 class WebImageFetcher::Private
0045 {
0046     friend class WebImageFetcher;
0047 
0048     FileHandle file;
0049     QString artist;
0050     QString albumName;
0051     QPointer<KIO::StoredTransferJob> connection;
0052     QDialog *dialog = nullptr;
0053     QUrl url;
0054 };
0055 
0056 WebImageFetcher::WebImageFetcher(QObject *parent)
0057   : QObject(parent)
0058   , d(new Private)
0059 {
0060 }
0061 
0062 WebImageFetcher::~WebImageFetcher()
0063 {
0064     delete d;
0065 }
0066 
0067 void WebImageFetcher::setFile(const FileHandle &file)
0068 {
0069     d->file = file;
0070     d->artist = file.tag()->artist();
0071     d->albumName = file.tag()->album();
0072 }
0073 
0074 void WebImageFetcher::abortSearch()
0075 {
0076     if (d->connection)
0077         d->connection->kill();
0078 }
0079 void WebImageFetcher::searchCover()
0080 {
0081     QStatusBar *statusBar = JuK::JuKInstance()->statusBar();
0082     statusBar->showMessage(i18n("Searching for cover. Please Wait..."));
0083 
0084     QUrlQuery urlQuery;
0085     urlQuery.addQueryItem("method", "album.getInfo");
0086     urlQuery.addQueryItem("api_key", "3e6ecbd7284883089e8f2b5b53b0aecd");
0087     urlQuery.addQueryItem("artist", d->artist);
0088     urlQuery.addQueryItem("album", d->albumName);
0089 
0090     QUrl url("http://ws.audioscrobbler.com/2.0/");
0091     url.setQuery(urlQuery);
0092 
0093     qCDebug(JUK_LOG) << "Using request " << url.toDisplayString();
0094 
0095     d->connection = KIO::storedGet(url, KIO::Reload /* reload always */, KIO::HideProgressInfo);
0096     connect(d->connection, SIGNAL(result(KJob*)), SLOT(slotWebRequestFinished(KJob*)));
0097 
0098     // Wait for the results...
0099 }
0100 
0101 void WebImageFetcher::slotWebRequestFinished(KJob *job)
0102 {
0103     if (job != d->connection)
0104         return;
0105 
0106     QStatusBar *statusBar = JuK::JuKInstance()->statusBar();
0107 
0108     if (!job || job->error()) {
0109         qCCritical(JUK_LOG) << "Error reading image results from last.fm!\n";
0110         qCCritical(JUK_LOG) << d->connection->errorString();
0111         return;
0112     }
0113 
0114     if (d->connection->data().isEmpty()) {
0115         qCCritical(JUK_LOG) << "last.fm returned an empty result!\n";
0116         return;
0117     }
0118 
0119     QDomDocument results("ResultSet");
0120 
0121     QString errorStr;
0122     int errorCol, errorLine;
0123     if (!results.setContent(d->connection->data(), &errorStr, &errorLine, &errorCol)) {
0124         qCCritical(JUK_LOG) << "Unable to create XML document from results.\n";
0125         qCCritical(JUK_LOG) << "Line " << errorLine << ", " << errorStr;
0126 
0127         return;
0128     }
0129 
0130     QDomElement n = results.documentElement();
0131 
0132     if (n.isNull()) {
0133         qCDebug(JUK_LOG) << "No document root in XML results??\n";
0134         return;
0135     }
0136     if (n.nodeName() != QLatin1String("lfm")) {
0137         qCDebug(JUK_LOG) << "Invalid resulting XML document, not <lfm>";
0138         return;
0139     }
0140     if (n.attribute(QStringLiteral("status")) != QLatin1String("ok")) {
0141         const QDomElement err = n.firstChildElement(QStringLiteral("error"));
0142         const int errCode = err.attribute(QStringLiteral("code")).toInt();
0143         if (errCode == 6) {
0144             KMessageBox::information(nullptr, i18n("Album '%1' not found.", d->albumName), i18nc("@title:window", "Album not Found"));
0145         } else {
0146             KMessageBox::error(nullptr, i18n("Error %1 when searching for cover:\n%2", errCode, err.text()));
0147         }
0148         statusBar->clearMessage();
0149         return;
0150     }
0151     n = n.firstChildElement("album");
0152 
0153     //FIXME: We assume they have a sane sorting (smallest -> largest)
0154     const QString imageUrl = n.lastChildElement("image").text();
0155     if (imageUrl.isEmpty()) {
0156         KMessageBox::information(nullptr, i18n("No available cover for the album '%1'.", d->albumName),
0157                 i18nc("@title:window", "Cover not Available"));
0158         statusBar->clearMessage();
0159         return;
0160     }
0161     d->url = QUrl::fromEncoded(imageUrl.toLatin1());
0162     //TODO: size attribute can have the values mega, extralarge, large, medium and small
0163 
0164     qCDebug(JUK_LOG) << "Got cover:" << d->url;
0165 
0166     statusBar->showMessage(i18n("Downloading cover. Please Wait..."));
0167 
0168     KIO::StoredTransferJob *newJob = KIO::storedGet(d->url, KIO::Reload /* reload always */, KIO::HideProgressInfo);
0169     connect(newJob, SIGNAL(result(KJob*)), SLOT(slotImageFetched(KJob*)));
0170 }
0171 
0172 void WebImageFetcher::slotImageFetched(KJob* j)
0173 {
0174     QStatusBar *statusBar = JuK::JuKInstance()->statusBar();
0175     statusBar->clearMessage();
0176 
0177     KIO::StoredTransferJob *job = qobject_cast<KIO::StoredTransferJob*>(j);
0178 
0179     if (d->dialog)
0180         return;
0181 
0182     d->dialog = new QDialog;
0183     d->dialog->setWindowTitle(i18n("Cover found"));
0184 
0185     auto dlgVLayout = new QVBoxLayout(d->dialog);
0186 
0187     if(job->error()) {
0188         qCCritical(JUK_LOG) << "Unable to grab image" << job->errorText();
0189 
0190         KMessageBox::error(nullptr, i18n("Failed to download requested cover art: %1", job->errorString()),
0191                 i18nc("@title:window", "Could not download cover art"));
0192         return;
0193     }
0194 
0195     // TODO: 150x150 seems inconsistent with HiDPI, figure out something better
0196     QPixmap iconImage, realImage(150, 150);
0197     iconImage.loadFromData(job->data());
0198     realImage.fill(Qt::transparent);
0199 
0200     if(iconImage.isNull()) {
0201         qCCritical(JUK_LOG) << "Thumbnail image is not of a supported format\n";
0202         return;
0203     }
0204 
0205     // Scale down if necesssary
0206     if(iconImage.width() > 150 || iconImage.height() > 150)
0207         iconImage = iconImage.scaled(150, 150, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0208 
0209     QLabel *cover = new QLabel(d->dialog);
0210     cover->setPixmap(iconImage);
0211     dlgVLayout->addWidget(cover);
0212 
0213     QLabel *infoLabel = new QLabel(i18n("Cover fetched from <a href='https://last.fm/'>last.fm</a>."), d->dialog);
0214     infoLabel->setOpenExternalLinks(true);
0215     infoLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
0216     dlgVLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding));
0217     dlgVLayout->addWidget(infoLabel);
0218 
0219     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel, d->dialog);
0220     dlgVLayout->addWidget(buttonBox);
0221     connect(buttonBox, &QDialogButtonBox::accepted, d->dialog, &QDialog::accept);
0222     connect(buttonBox, &QDialogButtonBox::rejected, d->dialog, &QDialog::reject);
0223 
0224     connect(d->dialog, &QDialog::accepted, this, &WebImageFetcher::slotCoverChosen);
0225     connect(d->dialog, &QDialog::rejected, this, &WebImageFetcher::destroyDialog);
0226 
0227     d->dialog->setWindowIcon(realImage);
0228     d->dialog->show();
0229 }
0230 
0231 
0232 void WebImageFetcher::slotCoverChosen()
0233 {
0234     qCDebug(JUK_LOG) << "Adding new cover for " << d->file.tag()->fileName()
0235     << "from URL" << d->url;
0236 
0237     coverKey newId = CoverManager::addCover(d->url, d->file.tag()->artist(), d->file.tag()->album());
0238 
0239     if (newId != CoverManager::NoMatch) {
0240         emit signalCoverChanged(newId);
0241         destroyDialog();
0242     }
0243 }
0244 
0245 void WebImageFetcher::destroyDialog()
0246 {
0247     if (!d->dialog)
0248         return;
0249 
0250     d->dialog->close();
0251     d->dialog->deleteLater();
0252     d->dialog = 0;
0253 }
0254 
0255 // vim: set et sw=4 tw=0 sta: