File indexing completed on 2024-05-12 05:09:37

0001 /***************************************************************************
0002     Copyright (C) 2023 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "itunesfetcher.h"
0026 #include "../collections/bookcollection.h"
0027 #include "../collections/musiccollection.h"
0028 #include "../collections/videocollection.h"
0029 #include "../images/imagefactory.h"
0030 #include "../gui/combobox.h"
0031 #include "../utils/guiproxy.h"
0032 #include "../utils/isbnvalidator.h"
0033 #include "../utils/string_utils.h"
0034 #include "../utils/mapvalue.h"
0035 #include "../tellico_debug.h"
0036 
0037 #include <KLocalizedString>
0038 #include <KConfigGroup>
0039 #include <KJob>
0040 #include <KJobUiDelegate>
0041 #include <KJobWidgets/KJobWidgets>
0042 #include <KIO/StoredTransferJob>
0043 
0044 #include <QLabel>
0045 #include <QFile>
0046 #include <QTextStream>
0047 #include <QGridLayout>
0048 #include <QTextCodec>
0049 #include <QJsonDocument>
0050 #include <QJsonObject>
0051 #include <QJsonArray>
0052 #include <QUrlQuery>
0053 
0054 namespace {
0055   static const int ITUNES_MAX_RETURNS_TOTAL = 20;
0056   static const char* ITUNES_API_URL = "https://itunes.apple.com";
0057 }
0058 
0059 using namespace Tellico;
0060 using Tellico::Fetch::ItunesFetcher;
0061 
0062 ItunesFetcher::ItunesFetcher(QObject* parent_)
0063     : Fetcher(parent_)
0064     , m_started(false)
0065     , m_isTV(false)
0066     , m_imageSize(LargeImage) {
0067 }
0068 
0069 ItunesFetcher::~ItunesFetcher() {
0070 }
0071 
0072 QString ItunesFetcher::source() const {
0073   return m_name.isEmpty() ? defaultName() : m_name;
0074 }
0075 
0076 bool ItunesFetcher::canSearch(Fetch::FetchKey k) const {
0077   return k == Keyword || k == ISBN || k == UPC;
0078 }
0079 
0080 bool ItunesFetcher::canFetch(int type) const {
0081   return type == Data::Collection::Book
0082       || type == Data::Collection::Album
0083       || type == Data::Collection::Video;
0084 }
0085 
0086 void ItunesFetcher::readConfigHook(const KConfigGroup& config_) {
0087   const int imageSize = config_.readEntry("Image Size", -1);
0088   if(imageSize > -1) {
0089     m_imageSize = static_cast<ImageSize>(imageSize);
0090   }
0091 }
0092 
0093 void ItunesFetcher::saveConfigHook(KConfigGroup& config_) {
0094   Q_UNUSED(config_)
0095 }
0096 
0097 void ItunesFetcher::search() {
0098   m_started = true;
0099   m_isTV = false;
0100 
0101   QUrl u(QString::fromLatin1(ITUNES_API_URL));
0102   u = u.adjusted(QUrl::StripTrailingSlash);
0103   QUrlQuery q;
0104   switch(request().key()) {
0105     case Keyword:
0106       u.setPath(u.path() + QLatin1String("/search"));
0107       if(collectionType() == Data::Collection::Book) {
0108         q.addQueryItem(QStringLiteral("media"), QLatin1String("audiobook"));
0109         q.addQueryItem(QStringLiteral("entity"), QLatin1String("audiobook"));
0110       } else if(collectionType() == Data::Collection::Album) {
0111         q.addQueryItem(QStringLiteral("media"), QLatin1String("music"));
0112         q.addQueryItem(QStringLiteral("entity"), QLatin1String("album"));
0113       } else if(collectionType() == Data::Collection::Video) {
0114         q.addQueryItem(QStringLiteral("media"), QLatin1String("movie,tvShow"));
0115         q.addQueryItem(QStringLiteral("entity"), QLatin1String("movie,tvSeason"));
0116       }
0117       q.addQueryItem(QStringLiteral("limit"), QString::number(ITUNES_MAX_RETURNS_TOTAL));
0118       q.addQueryItem(QStringLiteral("term"),  QString::fromLatin1(QUrl::toPercentEncoding(request().value())));
0119       break;
0120 
0121     case ISBN:
0122       u.setPath(u.path() + QLatin1String("/lookup"));
0123       {
0124         QString isbn = ISBNValidator::isbn13(request().value());
0125         isbn.remove(QLatin1Char('-'));
0126         q.addQueryItem(QStringLiteral("isbn"), isbn);
0127       }
0128       break;
0129 
0130     case UPC:
0131       u.setPath(u.path() + QLatin1String("/lookup"));
0132       if(collectionType() == Data::Collection::Album) {
0133         // include songs
0134         q.addQueryItem(QStringLiteral("entity"), QLatin1String("song"));
0135       }
0136       q.addQueryItem(QStringLiteral("upc"), request().value());
0137       break;
0138 
0139     default:
0140       myWarning() << source() << "- key not recognized:" << request().key();
0141       stop();
0142       return;
0143   }
0144   u.setQuery(q);
0145 
0146 //  myDebug() << u;
0147   m_job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
0148   KJobWidgets::setWindow(m_job, GUI::Proxy::widget());
0149   connect(m_job.data(), &KJob::result, this, &ItunesFetcher::slotComplete);
0150 }
0151 
0152 void ItunesFetcher::stop() {
0153   if(!m_started) {
0154     return;
0155   }
0156   if(m_job) {
0157     m_job->kill();
0158     m_job = nullptr;
0159   }
0160   m_started = false;
0161   emit signalDone(this);
0162 }
0163 
0164 Tellico::Fetch::FetchRequest ItunesFetcher::updateRequest(Data::EntryPtr entry_) {
0165   const QString upc = entry_->field(QStringLiteral("upc"));
0166   if(!upc.isEmpty()) {
0167     return FetchRequest(UPC, upc);
0168   }
0169 
0170   const QString title = entry_->field(QStringLiteral("title"));
0171   if(!title.isEmpty()) {
0172     return FetchRequest(Keyword, title);
0173   }
0174 
0175   return FetchRequest();
0176 }
0177 
0178 void ItunesFetcher::slotComplete(KJob* job_) {
0179   KIO::StoredTransferJob* job = static_cast<KIO::StoredTransferJob*>(job_);
0180 
0181   if(job->error()) {
0182     job->uiDelegate()->showErrorMessage();
0183     stop();
0184     return;
0185   }
0186 
0187   const QByteArray data = job->data();
0188   if(data.isEmpty()) {
0189     myDebug() << "iTunesFetcher: no data";
0190     stop();
0191     return;
0192   }
0193   // see bug 319662. If fetcher is cancelled, job is killed
0194   // if the pointer is retained, it gets double-deleted
0195   m_job = nullptr;
0196 
0197 #if 0
0198   myWarning() << "Remove debug from itunesfetcher.cpp";
0199   QFile f(QStringLiteral("/tmp/test-itunes.json"));
0200   if(f.open(QIODevice::WriteOnly)) {
0201     QTextStream t(&f);
0202     t.setCodec("UTF-8");
0203     t << data;
0204   }
0205   f.close();
0206 #endif
0207 
0208   QJsonDocument doc = QJsonDocument::fromJson(data);
0209   if(doc.isNull()) {
0210     myDebug() << "null JSON document";
0211     stop();
0212     return;
0213   }
0214 
0215   QJsonArray results = doc.object().value(QLatin1String("results")).toArray();
0216   if(results.isEmpty()) {
0217     myDebug() << "iTunesFetcher: no results";
0218     stop();
0219     return;
0220   }
0221 
0222   Data::CollPtr coll;
0223   if(collectionType() == Data::Collection::Book) {
0224     coll = new Data::BookCollection(true);
0225   } else if(collectionType() == Data::Collection::Album) {
0226     coll = new Data::MusicCollection(true);
0227   } else if(collectionType() == Data::Collection::Video) {
0228     coll = new Data::VideoCollection(true);
0229   }
0230   Q_ASSERT(coll);
0231   if(!coll) {
0232     stop();
0233     return;
0234   }
0235 
0236   // placeholder for collection id, to be removed later
0237   Data::FieldPtr f1(new Data::Field(QStringLiteral("collectionId"), QString(), Data::Field::Number));
0238   coll->addField(f1);
0239   if(optionalFields().contains(QStringLiteral("itunes"))) {
0240     Data::FieldPtr field(new Data::Field(QStringLiteral("itunes"), i18n("iTunes Link"), Data::Field::URL));
0241     field->setCategory(i18n("General"));
0242     coll->addField(field);
0243   }
0244   if(collectionType() == Data::Collection::Video &&
0245      optionalFields().contains(QStringLiteral("episode"))) {
0246     coll->addField(Data::Field::createDefaultField(Data::Field::EpisodeField));
0247   }
0248 
0249   QList<FetchResult*> fetchResults;
0250   foreach(const QJsonValue& result, results) {
0251     auto obj = result.toObject();
0252     if(obj.value(QLatin1String("kind")) == QLatin1String("song")) {
0253       readTrackInfo(obj.toVariantMap());
0254     } else {
0255       Data::EntryPtr entry(new Data::Entry(coll));
0256       populateEntry(entry, obj.toVariantMap());
0257 
0258       FetchResult* r = new FetchResult(this, entry);
0259       m_entries.insert(r->uid, entry);
0260       fetchResults.append(r);
0261     }
0262   }
0263 
0264   // don't emit result until after adding tracks
0265   for(auto fetchResult : fetchResults) {
0266     emit signalResultFound(fetchResult);
0267   }
0268   stop();
0269 }
0270 
0271 Tellico::Data::EntryPtr ItunesFetcher::fetchEntryHook(uint uid_) {
0272   Data::EntryPtr entry = m_entries.value(uid_);
0273   if(!entry) {
0274     myWarning() << "no entry in dict";
0275     return Data::EntryPtr();
0276   }
0277 
0278   // check if tracks need to be downloaded
0279   const QString collectionId = entry->field(QLatin1String("collectionId"));
0280   if(!collectionId.isEmpty() && collectionType() == Data::Collection::Album &&
0281      entry->field(QLatin1String("track")).isEmpty()) {
0282     QUrl u(QString::fromLatin1(ITUNES_API_URL));
0283     u = u.adjusted(QUrl::StripTrailingSlash);
0284     u.setPath(u.path() + QLatin1String("/lookup"));
0285     QUrlQuery q;
0286     q.addQueryItem(QStringLiteral("entity"), QLatin1String("song"));
0287     q.addQueryItem(QStringLiteral("id"), collectionId);
0288     u.setQuery(q);
0289     auto job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
0290     if(job->exec()) {
0291 #if 0
0292       myWarning() << "Remove debug from itunesfetcher.cpp";
0293       QFile f(QStringLiteral("/tmp/test-itunes-tracks.json"));
0294       if(f.open(QIODevice::WriteOnly)) {
0295         QTextStream t(&f);
0296         t.setCodec("UTF-8");
0297         t << job->data();
0298       }
0299       f.close();
0300 #endif
0301       QJsonDocument doc = QJsonDocument::fromJson(job->data());
0302       QJsonArray results = doc.object().value(QLatin1String("results")).toArray();
0303       foreach(const QJsonValue& result, results) {
0304         auto obj = result.toObject();
0305         if(obj.value(QLatin1String("wrapperType")) == QLatin1String("track")) {
0306           readTrackInfo(obj.toVariantMap());
0307         }
0308       }
0309       auto discsInColl = m_trackList.value(collectionId.toInt());
0310       bool changeTrackTitle = true;
0311       for(int disc = 0; disc < discsInColl.count(); ++disc) {
0312         QString trackField = QStringLiteral("track");
0313         if(disc > 0) {
0314           trackField.append(QString::number(disc+1));
0315           Data::FieldPtr f2(new Data::Field(trackField,
0316                                             i18n("Tracks (Disc %1)", disc+1),
0317                                             Data::Field::Table));
0318           f2->setFormatType(FieldFormat::FormatTitle);
0319           f2->setProperty(QStringLiteral("columns"), QStringLiteral("3"));
0320           f2->setProperty(QStringLiteral("column1"), i18n("Title"));
0321           f2->setProperty(QStringLiteral("column2"), i18n("Artist"));
0322           f2->setProperty(QStringLiteral("column3"), i18n("Length"));
0323           entry->collection()->addField(f2);
0324           // also change the title of the first track field
0325           if(changeTrackTitle) {
0326             Data::FieldPtr f1 = entry->collection()->fieldByName(QStringLiteral("track"));
0327             f1->setTitle(i18n("Tracks (Disc %1)", 1));
0328             entry->collection()->modifyField(f1);
0329             changeTrackTitle = false;
0330           }
0331         }
0332         entry->setField(trackField, discsInColl.at(disc).join(FieldFormat::rowDelimiterString()));
0333       }
0334     }
0335   }
0336 
0337   if(m_isTV && optionalFields().contains(QStringLiteral("episode"))) {
0338     populateEpisodes(entry);
0339   }
0340 
0341   // image might still be a URL
0342   const QString image_id = entry->field(QStringLiteral("cover"));
0343   if(image_id.contains(QLatin1Char('/'))) {
0344     // default image is the 100x100 size and considered 'Small'
0345     QString newImage = image_id;
0346     if(m_imageSize == LargeImage) {
0347       newImage.replace(QLatin1String("100x100"), QLatin1String("600x600"));
0348     }
0349     QString id = ImageFactory::addImage(QUrl::fromUserInput(newImage), true /* quiet */);
0350     if(id.isEmpty() && newImage != image_id) {
0351       // fallback to original
0352       id = ImageFactory::addImage(QUrl::fromUserInput(image_id), true /* quiet */);
0353     }
0354     if(id.isEmpty()) {
0355       message(i18n("The cover image could not be loaded."), MessageHandler::Warning);
0356     }
0357     // empty image ID is ok
0358     entry->setField(QStringLiteral("cover"), id);
0359   }
0360 
0361   // clear the placeholder field
0362   entry->setField(QStringLiteral("collectionId"), QString());
0363 
0364   return entry;
0365 }
0366 
0367 void ItunesFetcher::populateEntry(Data::EntryPtr entry_, const QVariantMap& resultMap_) {
0368   entry_->setField(QStringLiteral("collectionId"), mapValue(resultMap_, "collectionId"));
0369   if(collectionType() == Data::Collection::Book) {
0370     QString title = mapValue(resultMap_, "collectionName");
0371     if(title.isEmpty()) title = mapValue(resultMap_, "trackName");
0372     entry_->setField(QStringLiteral("title"), title);
0373     entry_->setField(QStringLiteral("author"), mapValue(resultMap_, "artistName"));
0374     entry_->setField(QStringLiteral("plot"), mapValue(resultMap_, "description"));
0375     static const QRegularExpression publisherRx(QStringLiteral("^© \\d{4} (.+)$"));
0376     auto publisherMatch = publisherRx.match(mapValue(resultMap_, "copyright"));
0377     if(publisherMatch.hasMatch()) {
0378       entry_->setField(QStringLiteral("publisher"), publisherMatch.captured(1));
0379     }
0380   } else if(collectionType() == Data::Collection::Album) {
0381     entry_->setField(QStringLiteral("title"), mapValue(resultMap_, "collectionName"));
0382     entry_->setField(QStringLiteral("artist"), mapValue(resultMap_, "artistName"));
0383     static const QRegularExpression labelRx(QStringLiteral("^℗ \\d{4} ([^,]+)"));
0384     auto labelMatch = labelRx.match(mapValue(resultMap_, "copyright"));
0385     if(labelMatch.hasMatch()) {
0386       entry_->setField(QStringLiteral("label"), labelMatch.captured(1));
0387     }
0388   } else if(collectionType() == Data::Collection::Video) {
0389     if(mapValue(resultMap_, "collectionType") == QLatin1String("TV Season")) {
0390       m_isTV = true;
0391       // collection Name includes season
0392       entry_->setField(QStringLiteral("title"), mapValue(resultMap_, "collectionName"));
0393       // artistName is TV Show title
0394       entry_->setField(QStringLiteral("keyword"), mapValue(resultMap_, "artistName"));
0395     } else {
0396       entry_->setField(QStringLiteral("title"), mapValue(resultMap_, "trackName"));
0397       entry_->setField(QStringLiteral("director"), mapValue(resultMap_, "artistName"));
0398     }
0399     entry_->setField(QStringLiteral("nationality"), mapValue(resultMap_, "country"));
0400     QString cert = mapValue(resultMap_, "contentAdvisoryRating");
0401     if(cert == QStringLiteral("NR")) {
0402       cert = QLatin1Char('U');
0403     }
0404     if(!cert.isEmpty()) {
0405       if(mapValue(resultMap_, "country") == QLatin1String("US")) {
0406         cert += QStringLiteral(" (USA)");
0407       } else {
0408         cert += QLatin1String(" (") + mapValue(resultMap_, "country") + QLatin1Char(')');
0409       }
0410       QStringList certsAllowed = entry_->collection()->fieldByName(QStringLiteral("certification"))->allowed();
0411       if(!certsAllowed.contains(cert)) {
0412         certsAllowed << cert;
0413         Data::FieldPtr f = entry_->collection()->fieldByName(QStringLiteral("certification"));
0414         f->setAllowed(certsAllowed);
0415       }
0416       entry_->setField(QStringLiteral("certification"), cert);
0417     }
0418     entry_->setField(QStringLiteral("plot"), mapValue(resultMap_, "longDescription"));
0419   }
0420   if(collectionType() == Data::Collection::Book) {
0421     entry_->setField(QStringLiteral("binding"), i18n("E-Book"));
0422     entry_->setField(QStringLiteral("pub_year"), mapValue(resultMap_, "releaseDate").left(4));
0423   } else {
0424     entry_->setField(QStringLiteral("year"), mapValue(resultMap_, "releaseDate").left(4));
0425   }
0426 
0427   QStringList genres;
0428   genres += mapValue(resultMap_, "primaryGenreName");
0429   const auto genreList = resultMap_.value(QLatin1String("genres")).toList();
0430   for(const auto& genre : genreList) {
0431     genres += genre.toString();
0432   }
0433   genres.removeDuplicates();
0434   genres.removeOne(QString()); // no empty genres
0435   genres.removeOne(QLatin1String("Books")); // too generic
0436   entry_->setField(QStringLiteral("genre"), genres.join(FieldFormat::delimiterString()));
0437 
0438   if(m_imageSize != NoImage) {
0439     entry_->setField(QStringLiteral("cover"), mapValue(resultMap_, "artworkUrl100"));
0440   }
0441 
0442   if(optionalFields().contains(QStringLiteral("itunes"))) {
0443     entry_->setField(QStringLiteral("itunes"), mapValue(resultMap_, "collectionViewUrl"));
0444   }
0445 
0446   m_collectionHash.insert(resultMap_.value(QLatin1String("collectionId")).toInt(), entry_);
0447 }
0448 
0449 void ItunesFetcher::populateEpisodes(Data::EntryPtr entry_) {
0450   const QString collectionId = entry_->field(QLatin1String("collectionId"));
0451   QUrl u(QString::fromLatin1(ITUNES_API_URL));
0452   u = u.adjusted(QUrl::StripTrailingSlash);
0453   u.setPath(u.path() + QLatin1String("/lookup"));
0454   QUrlQuery q;
0455   q.addQueryItem(QStringLiteral("entity"), QLatin1String("tvEpisode"));
0456   q.addQueryItem(QStringLiteral("id"), collectionId);
0457   u.setQuery(q);
0458 
0459   auto job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
0460   if(!job->exec()) {
0461     myDebug() << "Failed downloa ditunes episodes";
0462     return;
0463    }
0464 
0465 #if 0
0466   myWarning() << "Remove debug2 from ItunesFetcher.cpp";
0467   QFile f(QStringLiteral("/tmp/test-itunes-episodes.json"));
0468   if(f.open(QIODevice::WriteOnly)) {
0469     QTextStream t(&f);
0470     t.setCodec("UTF-8");
0471     t << job->data();
0472   }
0473   f.close();
0474 #endif
0475 
0476   static const QRegularExpression seasonRx(QStringLiteral("Season (\\d+)"));
0477   QMap<int, QString> episodeMap; // mapping episode number to episode string
0478   QJsonDocument doc = QJsonDocument::fromJson(job->data());
0479   QJsonArray results = doc.object().value(QLatin1String("results")).toArray();
0480   foreach(const QJsonValue& result, results) {
0481     auto map = result.toObject().toVariantMap();
0482     if(mapValue(map, "kind") != QStringLiteral("tv-episode")) continue;
0483     int seasonNumber = 1;
0484     // the season number is in the collection title
0485     auto match = seasonRx.match(mapValue(map, "collectionName"));
0486     if(match.hasMatch()) {
0487       seasonNumber = match.captured(1).toInt();
0488     }
0489     QString ep = mapValue(map, "trackName") + FieldFormat::columnDelimiterString() +
0490                  QString::number(seasonNumber) + FieldFormat::columnDelimiterString() +
0491                  mapValue(map, "trackNumber");
0492     episodeMap.insert(seasonNumber*1000 + mapValue(map, "trackNumber").toInt(), ep);
0493   }
0494   // the QMap sorts the values in ascending order by key
0495   const auto episodes = episodeMap.values();
0496   entry_->setField(QStringLiteral("episode"), episodes.join(FieldFormat::rowDelimiterString()));
0497 }
0498 
0499 void ItunesFetcher::readTrackInfo(const QVariantMap& resultMap_) {
0500   QStringList trackInfo;
0501   trackInfo << mapValue(resultMap_, "trackName")
0502             << mapValue(resultMap_, "artistName")
0503             << Tellico::minutes(mapValue(resultMap_, "trackTimeMillis").toInt() / 1000);
0504 
0505   const int collectionId = mapValue(resultMap_, "collectionId").toInt();
0506   const int discNum = mapValue(resultMap_, "discNumber").toInt();
0507   const int trackNum = mapValue(resultMap_, "trackNumber").toInt();
0508   if(trackNum < 1) return;
0509 
0510   auto discsInColl = m_trackList.value(collectionId);
0511   while(discsInColl.size() < discNum) discsInColl << QStringList();
0512 
0513   auto tracks = discsInColl.at(discNum-1);
0514   while(tracks.size() < trackNum) tracks << QString();
0515 
0516   tracks[trackNum-1] = trackInfo.join(FieldFormat::columnDelimiterString());
0517   discsInColl[discNum-1] = tracks;
0518   m_trackList.insert(collectionId, discsInColl);
0519 }
0520 
0521 Tellico::Fetch::ConfigWidget* ItunesFetcher::configWidget(QWidget* parent_) const {
0522   return new ItunesFetcher::ConfigWidget(parent_, this);
0523 }
0524 
0525 QString ItunesFetcher::defaultName() {
0526   return QStringLiteral("iTunes"); // this is the capitalization they use on their site
0527 }
0528 
0529 QString ItunesFetcher::defaultIcon() {
0530   return favIcon(ITUNES_API_URL);
0531 }
0532 
0533 Tellico::StringHash ItunesFetcher::allOptionalFields() {
0534   StringHash hash;
0535   hash[QStringLiteral("itunes")] = i18n("iTunes Link");
0536   hash[QStringLiteral("episode")] = i18n("Episodes");
0537   return hash;
0538 }
0539 
0540 ItunesFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const ItunesFetcher* fetcher_)
0541     : Fetch::ConfigWidget(parent_) {
0542   QGridLayout* l = new QGridLayout(optionsWidget());
0543   l->setColumnStretch(1, 10);
0544 
0545   int row = -1;
0546 
0547   QLabel* label = new QLabel(i18n("&Image size: "), optionsWidget());
0548   l->addWidget(label, ++row, 0);
0549   m_imageCombo = new GUI::ComboBox(optionsWidget());
0550   m_imageCombo->addItem(i18n("No Image"), NoImage);
0551   m_imageCombo->addItem(i18n("Small Image"), SmallImage);
0552   m_imageCombo->addItem(i18n("Large Image"), LargeImage);
0553   void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated;
0554   connect(m_imageCombo, activatedInt, this, &ConfigWidget::slotSetModified);
0555   l->addWidget(m_imageCombo, row, 1);
0556   label->setBuddy(m_imageCombo);
0557 
0558   l->setRowStretch(++row, 10);
0559 
0560   // now add additional fields widget
0561   addFieldsWidget(ItunesFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList());
0562 
0563   if(fetcher_) {
0564     m_imageCombo->setCurrentData(fetcher_->m_imageSize);
0565   } else { // defaults
0566     m_imageCombo->setCurrentData(SmallImage);
0567   }
0568 }
0569 
0570 void ItunesFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) {
0571   const int n = m_imageCombo->currentData().toInt();
0572   config_.writeEntry("Image Size", n);
0573 }
0574 
0575 QString ItunesFetcher::ConfigWidget::preferredName() const {
0576   return ItunesFetcher::defaultName();
0577 }