File indexing completed on 2022-08-04 15:34:54

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