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: