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: