File indexing completed on 2024-05-12 16:46:00
0001 /*************************************************************************** 0002 Copyright (C) 2012-2019 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 "thegamesdbfetcher.h" 0026 #include "../collections/gamecollection.h" 0027 #include "../images/imagefactory.h" 0028 #include "../gui/combobox.h" 0029 #include "../core/filehandler.h" 0030 #include "../utils/guiproxy.h" 0031 #include "../utils/string_utils.h" 0032 #include "../utils/tellico_utils.h" 0033 #include "../tellico_debug.h" 0034 0035 #include <KLocalizedString> 0036 #include <KConfigGroup> 0037 #include <KJob> 0038 #include <KJobUiDelegate> 0039 #include <KJobWidgets/KJobWidgets> 0040 #include <KIO/StoredTransferJob> 0041 0042 #include <QLabel> 0043 #include <QLineEdit> 0044 #include <QFile> 0045 #include <QTextStream> 0046 #include <QGridLayout> 0047 #include <QTextCodec> 0048 #include <QJsonDocument> 0049 #include <QJsonArray> 0050 #include <QJsonObject> 0051 #include <QUrlQuery> 0052 #include <QTimer> 0053 0054 namespace { 0055 static const int THEGAMESDB_MAX_RETURNS_TOTAL = 20; 0056 static const char* THEGAMESDB_API_URL = "https://api.thegamesdb.net"; 0057 static const char* THEGAMESDB_API_VERSION = "1"; // krazy:exclude=doublequote_chars 0058 static const char* THEGAMESDB_MAGIC_TOKEN = "f7c4fd9c5d6d4a2fcefe3157192f87e260038abe86b0f3977716596edaebdbb82315586e98fc88b0fb9ff4c01576e4d47b4e556d487a4325221abbddfac36f59d7e114753b5fa6c77a1e73423d5f72460f3b526bcbae4f2be0d86a5854600436784e3a5c5d6bc1a3e2d395f798fb35073051f2c232014023e9dda99edfea5767"; 0059 } 0060 0061 using namespace Tellico; 0062 using Tellico::Fetch::TheGamesDBFetcher; 0063 0064 TheGamesDBFetcher::TheGamesDBFetcher(QObject* parent_) 0065 : Fetcher(parent_) 0066 , m_started(false) 0067 , m_imageSize(SmallImage) { 0068 m_apiKey = Tellico::reverseObfuscate(THEGAMESDB_MAGIC_TOKEN); 0069 // delay reading the platform names from the cache file 0070 QTimer::singleShot(0, this, &TheGamesDBFetcher::loadCachedData); 0071 } 0072 0073 TheGamesDBFetcher::~TheGamesDBFetcher() { 0074 } 0075 0076 QString TheGamesDBFetcher::source() const { 0077 return m_name.isEmpty() ? defaultName() : m_name; 0078 } 0079 0080 bool TheGamesDBFetcher::canSearch(Fetch::FetchKey k) const { 0081 return k == Title; 0082 } 0083 0084 bool TheGamesDBFetcher::canFetch(int type) const { 0085 return type == Data::Collection::Game; 0086 } 0087 0088 void TheGamesDBFetcher::readConfigHook(const KConfigGroup& config_) { 0089 const QString k = config_.readEntry("API Key"); 0090 if(!k.isEmpty()) { 0091 m_apiKey = k; 0092 } 0093 const int imageSize = config_.readEntry("Image Size", -1); 0094 if(imageSize > -1) { 0095 m_imageSize = static_cast<ImageSize>(imageSize); 0096 } 0097 } 0098 0099 void TheGamesDBFetcher::search() { 0100 m_started = true; 0101 0102 if(m_apiKey.isEmpty()) { 0103 myDebug() << "empty API key"; 0104 message(i18n("An access key is required to use this data source.") 0105 + QLatin1Char(' ') + 0106 i18n("Those values must be entered in the data source settings."), MessageHandler::Error); 0107 stop(); 0108 return; 0109 } 0110 0111 QUrl u(QString::fromLatin1(THEGAMESDB_API_URL)); 0112 u.setPath(QLatin1String("/v") + QLatin1String(THEGAMESDB_API_VERSION)); 0113 0114 switch(request().key()) { 0115 case Title: 0116 u = u.adjusted(QUrl::StripTrailingSlash); 0117 u.setPath(u.path() + QLatin1String("/Games/ByGameName")); 0118 { 0119 QUrlQuery q; 0120 q.addQueryItem(QStringLiteral("apikey"), m_apiKey); 0121 if(optionalFields().contains(QStringLiteral("num-player"))) { 0122 q.addQueryItem(QStringLiteral("fields"), QStringLiteral("players,rating,publishers,genres,overview,platform")); 0123 } else { 0124 q.addQueryItem(QStringLiteral("fields"), QStringLiteral("rating,publishers,genres,overview,platform")); 0125 } 0126 q.addQueryItem(QStringLiteral("include"), QStringLiteral("platform,boxart")); 0127 q.addQueryItem(QStringLiteral("name"), request().value()); 0128 if(!request().data().isEmpty()) { 0129 q.addQueryItem(QStringLiteral("filter[platform]"), request().data()); 0130 } 0131 u.setQuery(q); 0132 } 0133 break; 0134 0135 default: 0136 myWarning() << "key not recognized:" << request().key(); 0137 stop(); 0138 return; 0139 } 0140 // u = QUrl::fromLocalFile(QStringLiteral("/tmp/test-tgdb.json")); 0141 // myDebug() << u; 0142 0143 m_job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); 0144 KJobWidgets::setWindow(m_job, GUI::Proxy::widget()); 0145 connect(m_job.data(), &KJob::result, this, &TheGamesDBFetcher::slotComplete); 0146 } 0147 0148 void TheGamesDBFetcher::stop() { 0149 if(!m_started) { 0150 return; 0151 } 0152 if(m_job) { 0153 m_job->kill(); 0154 m_job = nullptr; 0155 } 0156 m_started = false; 0157 emit signalDone(this); 0158 } 0159 0160 Tellico::Data::EntryPtr TheGamesDBFetcher::fetchEntryHook(uint uid_) { 0161 Data::EntryPtr entry = m_entries.value(uid_); 0162 if(!entry) { 0163 myWarning() << "no entry in dict"; 0164 return Data::EntryPtr(); 0165 } 0166 0167 // image might still be a URL 0168 const QString image_id = entry->field(QStringLiteral("cover")); 0169 if(image_id.contains(QLatin1Char('/'))) { 0170 const QString id = ImageFactory::addImage(QUrl::fromUserInput(image_id), true /* quiet */); 0171 if(id.isEmpty()) { 0172 message(i18n("The cover image could not be loaded."), MessageHandler::Warning); 0173 } 0174 // empty image ID is ok 0175 entry->setField(QStringLiteral("cover"), id); 0176 } 0177 0178 // don't want to include TGDb ID field 0179 entry->setField(QStringLiteral("tgdb-id"), QString()); 0180 0181 return entry; 0182 } 0183 0184 Tellico::Fetch::FetchRequest TheGamesDBFetcher::updateRequest(Data::EntryPtr entry_) { 0185 const QString platform = entry_->field(QStringLiteral("platform")); 0186 int platformId = -1; 0187 // if the platform id is available, it can be used to filter the update search 0188 if(!platform.isEmpty()) { 0189 for(auto i = m_platforms.constBegin(); i != m_platforms.constEnd(); ++i) { 0190 if(i.value() == platform) { 0191 platformId = i.key(); 0192 break; 0193 } 0194 } 0195 } 0196 0197 const QString title = entry_->field(QStringLiteral("title")); 0198 if(!title.isEmpty()) { 0199 FetchRequest req(Title, title); 0200 if(platformId > -1) { 0201 req.setData(QString::number(platformId)); 0202 } 0203 return req; 0204 } 0205 return FetchRequest(); 0206 } 0207 0208 void TheGamesDBFetcher::slotComplete(KJob* job_) { 0209 KIO::StoredTransferJob* job = static_cast<KIO::StoredTransferJob*>(job_); 0210 0211 if(job->error()) { 0212 job->uiDelegate()->showErrorMessage(); 0213 stop(); 0214 return; 0215 } 0216 0217 const QByteArray data = job->data(); 0218 if(data.isEmpty()) { 0219 myDebug() << "no data"; 0220 stop(); 0221 return; 0222 } 0223 // see bug 319662. If fetcher is cancelled, job is killed 0224 // if the pointer is retained, it gets double-deleted 0225 m_job = nullptr; 0226 0227 #if 0 0228 myWarning() << "Remove debug from thegamesdbfetcher.cpp"; 0229 QFile f(QStringLiteral("/tmp/test-tgdb.json")); 0230 if(f.open(QIODevice::WriteOnly)) { 0231 QTextStream t(&f); 0232 t.setCodec("UTF-8"); 0233 t << data; 0234 } 0235 f.close(); 0236 #endif 0237 0238 Data::CollPtr coll(new Data::GameCollection(true)); 0239 // always add the tgdb-id for fetchEntryHook 0240 Data::FieldPtr field(new Data::Field(QStringLiteral("tgdb-id"), QStringLiteral("TGDb ID"), Data::Field::Line)); 0241 field->setCategory(i18n("General")); 0242 coll->addField(field); 0243 0244 if(optionalFields().contains(QStringLiteral("num-player"))) { 0245 Data::FieldPtr field(new Data::Field(QStringLiteral("num-player"), i18n("Number of Players"), Data::Field::Number)); 0246 field->setCategory(i18n("General")); 0247 field->setFlags(Data::Field::AllowMultiple | Data::Field::AllowGrouped); 0248 coll->addField(field); 0249 } 0250 0251 QVariantMap topLevelMap = QJsonDocument::fromJson(data).object().toVariantMap(); 0252 if(!topLevelMap.contains(QStringLiteral("data"))) { 0253 myDebug() << "No data in result!"; 0254 } 0255 readPlatformList(topLevelMap.value(QStringLiteral("include")).toMap() 0256 .value(QStringLiteral("platform")).toMap()); 0257 readCoverList(topLevelMap.value(QStringLiteral("include")).toMap() 0258 .value(QStringLiteral("boxart")).toMap()); 0259 0260 QVariantList resultList = topLevelMap.value(QStringLiteral("data")).toMap() 0261 .value(QStringLiteral("games")).toList(); 0262 if(resultList.isEmpty()) { 0263 myDebug() << "no results"; 0264 stop(); 0265 return; 0266 } 0267 0268 int count = 0; 0269 foreach(const QVariant& result, resultList) { 0270 Data::EntryPtr entry(new Data::Entry(coll)); 0271 populateEntry(entry, result.toMap()); 0272 0273 FetchResult* r = new FetchResult(this, entry); 0274 m_entries.insert(r->uid, entry); 0275 emit signalResultFound(r); 0276 ++count; 0277 if(count >= THEGAMESDB_MAX_RETURNS_TOTAL) { 0278 break; 0279 } 0280 } 0281 0282 stop(); 0283 } 0284 0285 void TheGamesDBFetcher::populateEntry(Data::EntryPtr entry_, const QVariantMap& resultMap_) { 0286 entry_->setField(QStringLiteral("tgdb-id"), mapValue(resultMap_, "id")); 0287 entry_->setField(QStringLiteral("title"), mapValue(resultMap_, "game_title")); 0288 entry_->setField(QStringLiteral("year"), mapValue(resultMap_, "release_date").left(4)); 0289 entry_->setField(QStringLiteral("description"), mapValue(resultMap_, "overview")); 0290 0291 const int platformId = mapValue(resultMap_, "platform").toInt(); 0292 if(m_platforms.contains(platformId)) { 0293 const QString platform = m_platforms[platformId]; 0294 // make the assumption that if the platform name isn't already in the allowed list, it should be added 0295 Data::FieldPtr f = entry_->collection()->fieldByName(QStringLiteral("platform")); 0296 if(f && !f->allowed().contains(platform)) { 0297 f->setAllowed(QStringList(f->allowed()) << platform); 0298 } 0299 entry_->setField(QStringLiteral("platform"), platform); 0300 } 0301 0302 const QString esrb = mapValue(resultMap_, "rating") 0303 .section(QLatin1Char('-'), 0, 0) 0304 .trimmed(); // value is like "T - Teen" 0305 Data::GameCollection::EsrbRating rating = Data::GameCollection::UnknownEsrb; 0306 if(esrb == QLatin1String("U")) rating = Data::GameCollection::Unrated; 0307 else if(esrb == QLatin1String("T")) rating = Data::GameCollection::Teen; 0308 else if(esrb == QLatin1String("E")) rating = Data::GameCollection::Everyone; 0309 else if(esrb == QLatin1String("E10+")) rating = Data::GameCollection::Everyone10; 0310 else if(esrb == QLatin1String("EC")) rating = Data::GameCollection::EarlyChildhood; 0311 else if(esrb == QLatin1String("A")) rating = Data::GameCollection::Adults; 0312 else if(esrb == QLatin1String("M")) rating = Data::GameCollection::Mature; 0313 else if(esrb == QLatin1String("RP")) rating = Data::GameCollection::Pending; 0314 if(rating != Data::GameCollection::UnknownEsrb) { 0315 entry_->setField(QStringLiteral("certification"), Data::GameCollection::esrbRating(rating)); 0316 } 0317 0318 const QString coverUrl = m_covers.value(mapValue(resultMap_, "id")); 0319 if(m_imageSize != NoImage) { 0320 entry_->setField(QStringLiteral("cover"), coverUrl); 0321 } 0322 0323 QStringList genres, pubs, devs; 0324 0325 bool alreadyAttemptedLoad = false; 0326 QVariantList genreIdList = resultMap_.value(QStringLiteral("genres")).toList(); 0327 foreach(const QVariant& v, genreIdList) { 0328 const int id = v.toInt(); 0329 if(!m_genres.contains(id) && !alreadyAttemptedLoad) { 0330 readDataList(Genre); 0331 alreadyAttemptedLoad = true; 0332 } 0333 if(m_genres.contains(id)) { 0334 genres << m_genres[id]; 0335 } 0336 } 0337 0338 alreadyAttemptedLoad = false; 0339 QVariantList pubList = resultMap_.value(QStringLiteral("publishers")).toList(); 0340 foreach(const QVariant& v, pubList) { 0341 const int id = v.toInt(); 0342 if(!m_publishers.contains(id) && !alreadyAttemptedLoad) { 0343 readDataList(Publisher); 0344 alreadyAttemptedLoad = true; 0345 } 0346 if(m_publishers.contains(id)) { 0347 pubs << m_publishers[id]; 0348 } 0349 } 0350 0351 alreadyAttemptedLoad = false; 0352 QVariantList devList = resultMap_.value(QStringLiteral("developers")).toList(); 0353 foreach(const QVariant& v, devList) { 0354 const int id = v.toInt(); 0355 if(!m_developers.contains(id) && !alreadyAttemptedLoad) { 0356 readDataList(Developer); 0357 alreadyAttemptedLoad = true; 0358 } 0359 if(m_developers.contains(id)) { 0360 devs << m_developers[id]; 0361 } 0362 } 0363 0364 entry_->setField(QStringLiteral("genre"), genres.join(FieldFormat::delimiterString())); 0365 entry_->setField(QStringLiteral("publisher"), pubs.join(FieldFormat::delimiterString())); 0366 entry_->setField(QStringLiteral("developer"), devs.join(FieldFormat::delimiterString())); 0367 0368 if(entry_->collection()->hasField(QStringLiteral("num-player"))) { 0369 entry_->setField(QStringLiteral("num-player"), mapValue(resultMap_, "players")); 0370 } 0371 } 0372 0373 void TheGamesDBFetcher::readPlatformList(const QVariantMap& platformMap_) { 0374 QMapIterator<QString, QVariant> i(platformMap_); 0375 while(i.hasNext()) { 0376 i.next(); 0377 const QVariantMap map = i.value().toMap(); 0378 const QString name = map.value(QStringLiteral("name")).toString(); 0379 m_platforms.insert(i.key().toInt(), Data::GameCollection::normalizePlatform(name)); 0380 } 0381 0382 // now write it to cache again 0383 const QString id = QStringLiteral("id"); 0384 const QString name = QStringLiteral("name"); 0385 QJsonObject platformObj; 0386 for(auto ii = m_platforms.constBegin(); ii != m_platforms.constEnd(); ++ii) { 0387 QJsonObject iObj; 0388 iObj.insert(id, ii.key()); 0389 iObj.insert(name, ii.value()); 0390 platformObj.insert(QString::number(ii.key()), iObj); 0391 } 0392 QJsonObject dataObj; 0393 dataObj.insert(QStringLiteral("platforms"), platformObj); 0394 QJsonObject docObj; 0395 docObj.insert(QStringLiteral("data"), dataObj); 0396 QJsonDocument doc; 0397 doc.setObject(docObj); 0398 writeDataList(Platform, doc.toJson()); 0399 } 0400 0401 void TheGamesDBFetcher::readCoverList(const QVariantMap& coverDataMap_) { 0402 // first, get the base url 0403 QString imageBase; 0404 switch(m_imageSize) { 0405 case SmallImage: 0406 // this is the default size, using the thumb. Not the small size 0407 imageBase = QStringLiteral("thumb"); 0408 break; 0409 case MediumImage: 0410 imageBase = QStringLiteral("medium"); 0411 break; 0412 case LargeImage: 0413 imageBase = QStringLiteral("large"); 0414 break; 0415 case NoImage: 0416 m_covers.clear(); 0417 return; // no need to read anything 0418 break; 0419 } 0420 0421 QString baseUrl = coverDataMap_.value(QStringLiteral("base_url")).toMap() 0422 .value(imageBase).toString(); 0423 0424 QVariantMap coverMap = coverDataMap_.value(QStringLiteral("data")).toMap(); 0425 QMapIterator<QString, QVariant> i(coverMap); 0426 while(i.hasNext()) { 0427 i.next(); 0428 foreach(QVariant v, i.value().toList()) { 0429 QVariantMap map = v.toMap(); 0430 if(map.value(QStringLiteral("type")) == QLatin1String("boxart") && 0431 map.value(QStringLiteral("side")) == QLatin1String("front")) { 0432 m_covers.insert(i.key(), baseUrl + mapValue(map, "filename")); 0433 break; 0434 } 0435 } 0436 } 0437 } 0438 0439 void TheGamesDBFetcher::loadCachedData() { 0440 // The lists of genres, publishers, and developers are separate, with TGDB requesting that 0441 // the data be cached heavily and only updated when necessary 0442 // read the three cached JSON data file for genres, publishers, and developers 0443 // the platform info is sent with each request response, so it doesn't necessarily need 0444 // to be cache. But if an update request is used, having the cached platform id is helpful 0445 0446 QFile genreFile(dataFileName(Genre)); 0447 if(genreFile.open(QIODevice::ReadOnly)) { 0448 updateData(Genre, genreFile.readAll()); 0449 } 0450 0451 QFile publisherFile(dataFileName(Publisher)); 0452 if(publisherFile.open(QIODevice::ReadOnly)) { 0453 updateData(Publisher, publisherFile.readAll()); 0454 } 0455 0456 QFile developerFile(dataFileName(Developer)); 0457 if(developerFile.open(QIODevice::ReadOnly)) { 0458 updateData(Developer, developerFile.readAll()); 0459 } 0460 0461 QFile platformFile(dataFileName(Platform)); 0462 if(platformFile.open(QIODevice::ReadOnly)) { 0463 updateData(Platform, platformFile.readAll()); 0464 } 0465 } 0466 0467 void TheGamesDBFetcher::updateData(TgdbDataType dataType_, const QByteArray& jsonData_) { 0468 QString dataName; 0469 switch(dataType_) { 0470 case Genre: 0471 dataName = QStringLiteral("genres"); 0472 break; 0473 case Publisher: 0474 dataName = QStringLiteral("publishers"); 0475 break; 0476 case Developer: 0477 dataName = QStringLiteral("developers"); 0478 break; 0479 case Platform: 0480 dataName = QStringLiteral("platforms"); 0481 break; 0482 } 0483 0484 QHash<int, QString> dataHash; 0485 const QVariantMap topMap = QJsonDocument::fromJson(jsonData_).object().toVariantMap(); 0486 const QVariantMap resultMap = topMap.value(QStringLiteral("data")).toMap() 0487 .value(dataName).toMap(); 0488 for(QMapIterator<QString, QVariant> i(resultMap); i.hasNext(); ) { 0489 i.next(); 0490 const QVariantMap m = i.value().toMap(); 0491 dataHash.insert(m.value(QStringLiteral("id")).toInt(), mapValue(m, "name")); 0492 } 0493 0494 // transfer read data into the correct local variable 0495 switch(dataType_) { 0496 case Genre: 0497 m_genres = dataHash; 0498 break; 0499 case Publisher: 0500 m_publishers = dataHash; 0501 break; 0502 case Developer: 0503 m_developers = dataHash; 0504 break; 0505 case Platform: 0506 m_platforms = dataHash; 0507 break; 0508 } 0509 } 0510 0511 void TheGamesDBFetcher::readDataList(TgdbDataType dataType_) { 0512 QUrl u(QString::fromLatin1(THEGAMESDB_API_URL)); 0513 u.setPath(QLatin1String("/v") + QLatin1String(THEGAMESDB_API_VERSION)); 0514 switch(dataType_) { 0515 case Genre: 0516 u.setPath(u.path() + QLatin1String("/Genres")); 0517 break; 0518 case Publisher: 0519 u.setPath(u.path() + QLatin1String("/Publishers")); 0520 break; 0521 case Developer: 0522 u.setPath(u.path() + QLatin1String("/Developers")); 0523 break; 0524 case Platform: 0525 myDebug() << "not trying to read platforms"; 0526 // platforms are not read independently, and are only cached 0527 return; 0528 } 0529 QUrlQuery q; 0530 q.addQueryItem(QStringLiteral("apikey"), m_apiKey); 0531 u.setQuery(q); 0532 0533 // u = QUrl::fromLocalFile(dataFileName(dataType_)); // for testing 0534 // myDebug() << "Reading" << u; 0535 const QByteArray data = FileHandler::readDataFile(u, true); 0536 writeDataList(dataType_, data); 0537 updateData(dataType_, data); 0538 } 0539 0540 void TheGamesDBFetcher::writeDataList(TgdbDataType dataType_, const QByteArray& data_) { 0541 QFile file(dataFileName(dataType_)); 0542 if(!file.open(QIODevice::WriteOnly) || file.write(data_) == -1) { 0543 myDebug() << "unable to write to" << file.fileName() << file.errorString(); 0544 return; 0545 } 0546 file.close(); 0547 } 0548 0549 Tellico::Fetch::ConfigWidget* TheGamesDBFetcher::configWidget(QWidget* parent_) const { 0550 return new TheGamesDBFetcher::ConfigWidget(parent_, this); 0551 } 0552 0553 QString TheGamesDBFetcher::defaultName() { 0554 return QStringLiteral("TheGamesDB"); 0555 } 0556 0557 QString TheGamesDBFetcher::defaultIcon() { 0558 return favIcon("https://thegamesdb.net"); 0559 } 0560 0561 Tellico::StringHash TheGamesDBFetcher::allOptionalFields() { 0562 StringHash hash; 0563 hash[QStringLiteral("num-player")] = i18n("Number of Players"); 0564 return hash; 0565 } 0566 0567 QString TheGamesDBFetcher::dataFileName(TgdbDataType dataType_) { 0568 const QString dataDir = Tellico::saveLocation(QStringLiteral("thegamesdb-data/")); 0569 QString fileName; 0570 switch(dataType_) { 0571 case Genre: 0572 fileName = dataDir + QLatin1String("genres.json"); 0573 break; 0574 case Publisher: 0575 fileName = dataDir + QLatin1String("publishers.json"); 0576 break; 0577 case Developer: 0578 fileName = dataDir + QLatin1String("developers.json"); 0579 break; 0580 case Platform: 0581 fileName = dataDir + QLatin1String("platforms.json"); 0582 break; 0583 } 0584 return fileName; 0585 } 0586 0587 TheGamesDBFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const TheGamesDBFetcher* fetcher_) 0588 : Fetch::ConfigWidget(parent_) { 0589 QGridLayout* l = new QGridLayout(optionsWidget()); 0590 l->setSpacing(4); 0591 l->setColumnStretch(1, 10); 0592 0593 int row = -1; 0594 QLabel* al = new QLabel(i18n("Registration is required for accessing this data source. " 0595 "If you agree to the terms and conditions, <a href='%1'>sign " 0596 "up for an account</a>, and enter your information below.", 0597 QLatin1String("https://forums.thegamesdb.net/viewforum.php?f=10")), 0598 optionsWidget()); 0599 al->setOpenExternalLinks(true); 0600 al->setWordWrap(true); 0601 ++row; 0602 l->addWidget(al, row, 0, 1, 2); 0603 // richtext gets weird with size 0604 al->setMinimumWidth(al->sizeHint().width()); 0605 0606 QLabel* label = new QLabel(i18n("Access key: "), optionsWidget()); 0607 l->addWidget(label, ++row, 0); 0608 0609 m_apiKeyEdit = new QLineEdit(optionsWidget()); 0610 connect(m_apiKeyEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified); 0611 l->addWidget(m_apiKeyEdit, row, 1); 0612 label->setBuddy(m_apiKeyEdit); 0613 0614 label = new QLabel(i18n("&Image size: "), optionsWidget()); 0615 l->addWidget(label, ++row, 0); 0616 m_imageCombo = new GUI::ComboBox(optionsWidget()); 0617 m_imageCombo->addItem(i18n("Small Image"), SmallImage); 0618 m_imageCombo->addItem(i18n("Medium Image"), MediumImage); 0619 m_imageCombo->addItem(i18n("Large Image"), LargeImage); 0620 m_imageCombo->addItem(i18n("No Image"), NoImage); 0621 void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated; 0622 connect(m_imageCombo, activatedInt, this, &ConfigWidget::slotSetModified); 0623 l->addWidget(m_imageCombo, row, 1); 0624 QString w = i18n("The cover image may be downloaded as well. However, too many large images in the " 0625 "collection may degrade performance."); 0626 label->setWhatsThis(w); 0627 m_imageCombo->setWhatsThis(w); 0628 label->setBuddy(m_imageCombo); 0629 0630 l->setRowStretch(++row, 10); 0631 0632 if(fetcher_) { 0633 m_apiKeyEdit->setText(fetcher_->m_apiKey); 0634 m_imageCombo->setCurrentData(fetcher_->m_imageSize); 0635 } else { // defaults 0636 m_apiKeyEdit->setText(Tellico::reverseObfuscate(THEGAMESDB_MAGIC_TOKEN)); 0637 m_imageCombo->setCurrentData(SmallImage); 0638 } 0639 0640 // now add additional fields widget 0641 addFieldsWidget(TheGamesDBFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList()); 0642 } 0643 0644 void TheGamesDBFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) { 0645 const QString apiKey = m_apiKeyEdit->text().trimmed(); 0646 if(!apiKey.isEmpty() && apiKey != Tellico::reverseObfuscate(THEGAMESDB_MAGIC_TOKEN)) { 0647 config_.writeEntry("API Key", apiKey); 0648 } 0649 const int n = m_imageCombo->currentData().toInt(); 0650 config_.writeEntry("Image Size", n); 0651 } 0652 0653 QString TheGamesDBFetcher::ConfigWidget::preferredName() const { 0654 return TheGamesDBFetcher::defaultName(); 0655 }