File indexing completed on 2024-05-12 05:09:38
0001 /*************************************************************************** 0002 Copyright (C) 2019-2020 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 "mobygamesfetcher.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/mapvalue.h" 0032 #include "../utils/tellico_utils.h" 0033 #include "../core/tellico_strings.h" 0034 #include "../tellico_debug.h" 0035 0036 #include <KLocalizedString> 0037 #include <KConfigGroup> 0038 #include <KJob> 0039 #include <KJobUiDelegate> 0040 #include <KJobWidgets/KJobWidgets> 0041 #include <KIO/StoredTransferJob> 0042 0043 #include <QUrl> 0044 #include <QLabel> 0045 #include <QFile> 0046 #include <QTextStream> 0047 #include <QGridLayout> 0048 #include <QTextCodec> 0049 #include <QJsonDocument> 0050 #include <QJsonObject> 0051 #include <QUrlQuery> 0052 #include <QThread> 0053 #include <QTimer> 0054 0055 namespace { 0056 static const int MOBYGAMES_MAX_RETURNS_TOTAL = 10; 0057 static const char* MOBYGAMES_API_URL = "https://api.mobygames.com/v1"; 0058 } 0059 0060 using namespace Tellico; 0061 using Tellico::Fetch::MobyGamesFetcher; 0062 0063 MobyGamesFetcher::MobyGamesFetcher(QObject* parent_) 0064 : Fetcher(parent_) 0065 , m_started(false) 0066 , m_imageSize(SmallImage) 0067 , m_requestPlatformId(0) { 0068 // setLimit(MOBYGAMES_MAX_RETURNS_TOTAL); 0069 m_idleTime.start(); 0070 // delay reading the platform names from the cache file 0071 QTimer::singleShot(0, this, &MobyGamesFetcher::populateHashes); 0072 } 0073 0074 MobyGamesFetcher::~MobyGamesFetcher() { 0075 } 0076 0077 QString MobyGamesFetcher::source() const { 0078 return m_name.isEmpty() ? defaultName() : m_name; 0079 } 0080 0081 QString MobyGamesFetcher::attribution() const { 0082 return i18n(providedBy, QLatin1String("https://mobygames.com"), QLatin1String("MobyGames")); 0083 } 0084 0085 bool MobyGamesFetcher::canSearch(Fetch::FetchKey k) const { 0086 return k == Title || k == Keyword; 0087 } 0088 0089 bool MobyGamesFetcher::canFetch(int type) const { 0090 return type == Data::Collection::Game; 0091 } 0092 0093 void MobyGamesFetcher::readConfigHook(const KConfigGroup& config_) { 0094 QString k = config_.readEntry("API Key"); 0095 if(!k.isEmpty()) { 0096 m_apiKey = k; 0097 } 0098 const int imageSize = config_.readEntry("Image Size", -1); 0099 if(imageSize > -1) { 0100 m_imageSize = static_cast<ImageSize>(imageSize); 0101 } 0102 } 0103 0104 void MobyGamesFetcher::search() { 0105 continueSearch(); 0106 } 0107 0108 void MobyGamesFetcher::continueSearch() { 0109 m_started = true; 0110 m_requestPlatformId = 0; 0111 0112 QUrl u(QString::fromLatin1(MOBYGAMES_API_URL)); 0113 u.setPath(u.path() + QStringLiteral("/games")); 0114 0115 QUrlQuery q; 0116 switch(request().key()) { 0117 case Title: 0118 q.addQueryItem(QStringLiteral("title"), request().value()); 0119 break; 0120 0121 case Keyword: 0122 { 0123 // figure out if the platform is part of the search string 0124 int pId = 0; 0125 QString value = request().value(); // resulting value 0126 QString matchedPlatform; 0127 // iterate over all known platforms; this doesn't seem to be too much of a performance hit 0128 QHash<int, QString>::const_iterator i = m_platforms.constBegin(); 0129 while(i != m_platforms.constEnd()) { 0130 // don't forget that some platform names are substrings of others, like Wii and WiiU 0131 if(i.value().length() > matchedPlatform.length() && request().value().contains(i.value())) { 0132 pId = i.key(); 0133 matchedPlatform = i.value(); 0134 QString v = request().value(); // reset search value 0135 v.remove(matchedPlatform); // remove platform from search value 0136 value = v.simplified(); 0137 // can't break, because of potential substring platform name 0138 } 0139 ++i; 0140 } 0141 q.addQueryItem(QStringLiteral("title"), value); 0142 if(pId > 0) { 0143 m_requestPlatformId = pId; 0144 q.addQueryItem(QStringLiteral("platform"), QString::number(pId)); 0145 } 0146 } 0147 break; 0148 0149 case Raw: 0150 q.setQuery(request().value()); 0151 break; 0152 0153 default: 0154 myWarning() << source() << "- key not recognized:" << request().key(); 0155 stop(); 0156 return; 0157 } 0158 0159 if(m_apiKey.isEmpty()) { 0160 myDebug() << source() << "- empty API key"; 0161 message(i18n("An access key is required to use this data source.") 0162 + QLatin1Char(' ') + 0163 i18n("Those values must be entered in the data source settings."), MessageHandler::Error); 0164 stop(); 0165 return; 0166 } 0167 0168 q.addQueryItem(QStringLiteral("api_key"), m_apiKey); 0169 q.addQueryItem(QStringLiteral("limit"), QString::number(MOBYGAMES_MAX_RETURNS_TOTAL)); 0170 q.addQueryItem(QStringLiteral("format"), QStringLiteral("normal")); 0171 u.setQuery(q); 0172 // u = QUrl::fromLocalFile(QStringLiteral("/home/robby/games.json")); 0173 // myDebug() << u; 0174 0175 markTime(); 0176 m_job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); 0177 KJobWidgets::setWindow(m_job, GUI::Proxy::widget()); 0178 connect(m_job.data(), &KJob::result, this, &MobyGamesFetcher::slotComplete); 0179 } 0180 0181 void MobyGamesFetcher::stop() { 0182 if(!m_started) { 0183 return; 0184 } 0185 if(m_job) { 0186 m_job->kill(); 0187 m_job = nullptr; 0188 } 0189 m_started = false; 0190 emit signalDone(this); 0191 } 0192 0193 Tellico::Data::EntryPtr MobyGamesFetcher::fetchEntryHook(uint uid_) { 0194 if(!m_entries.contains(uid_)) { 0195 myDebug() << "no entry ptr"; 0196 return Data::EntryPtr(); 0197 } 0198 0199 Data::EntryPtr entry = m_entries.value(uid_); 0200 0201 QUrl u(QString::fromLatin1(MOBYGAMES_API_URL)); 0202 u.setPath(u.path() + QStringLiteral("/games/%1/platforms/%2") 0203 .arg(entry->field(QStringLiteral("moby-id")), 0204 entry->field(QStringLiteral("platform-id")))); 0205 QUrlQuery q; 0206 q.addQueryItem(QStringLiteral("api_key"), m_apiKey); 0207 u.setQuery(q); 0208 // myDebug() << u; 0209 0210 markTime(); 0211 QPointer<KIO::StoredTransferJob> job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); 0212 KJobWidgets::setWindow(job, GUI::Proxy::widget()); 0213 if(!job->exec()) { 0214 myDebug() << job->errorString() << u; 0215 return entry; 0216 } 0217 QByteArray data = job->data(); 0218 if(data.isEmpty()) { 0219 myDebug() << "no data for" << u; 0220 return entry; 0221 } 0222 #if 0 0223 myWarning() << "Remove platforms debug from mobygamesfetcher.cpp"; 0224 QFile file(QStringLiteral("/tmp/moby-game-info.json")); 0225 if(file.open(QIODevice::WriteOnly)) { 0226 QTextStream t(&file); 0227 t.setCodec("UTF-8"); 0228 t << data; 0229 } 0230 file.close(); 0231 #endif 0232 0233 QJsonDocument doc = QJsonDocument::fromJson(data); 0234 QVariantMap map = doc.object().toVariantMap(); 0235 foreach(const QVariant& rating, map.value(QStringLiteral("ratings")).toList()) { 0236 const QVariantMap ratingMap = rating.toMap(); 0237 const QString ratingSystem = ratingMap.value(QStringLiteral("rating_system_name")).toString(); 0238 if(ratingSystem == QStringLiteral("PEGI Rating")) { 0239 QString rating = ratingMap.value(QStringLiteral("rating_name")).toString(); 0240 if(!rating.startsWith(QStringLiteral("PEGI"))) { 0241 rating.prepend(QStringLiteral("PEGI ")); 0242 } 0243 entry->setField(QStringLiteral("pegi"), rating); 0244 } else if(ratingSystem == QStringLiteral("ESRB Rating")) { 0245 const int esrb = ratingMap.value(QStringLiteral("rating_id")).toInt(); 0246 if(m_esrbHash.contains(esrb)) { 0247 entry->setField(QStringLiteral("certification"), m_esrbHash.value(esrb)); 0248 } 0249 } 0250 } 0251 // just use the first release 0252 const QVariantList releaseList = map.value(QStringLiteral("releases")).toList(); 0253 if(!releaseList.isEmpty()) { 0254 const QVariantMap releaseMap = releaseList.at(0).toMap(); 0255 QStringList pubs, devs; 0256 foreach(const QVariant& company, releaseMap.value(QStringLiteral("companies")).toList()) { 0257 const QVariantMap companyMap = company.toMap(); 0258 if(companyMap.value(QStringLiteral("role")) == QStringLiteral("Developed by")) { 0259 devs += companyMap.value(QStringLiteral("company_name")).toString(); 0260 } else if(companyMap.value(QStringLiteral("role")) == QStringLiteral("Published by")) { 0261 pubs += companyMap.value(QStringLiteral("company_name")).toString(); 0262 } 0263 } 0264 // myDebug() << pubs << devs; 0265 entry->setField(QStringLiteral("publisher"), pubs.join(FieldFormat::delimiterString())); 0266 entry->setField(QStringLiteral("developer"), devs.join(FieldFormat::delimiterString())); 0267 } 0268 0269 if(m_imageSize == NoImage) { 0270 entry->setField(QStringLiteral("moby-id"), QString()); 0271 entry->setField(QStringLiteral("platform-id"), QString()); 0272 return entry; 0273 } 0274 0275 // check for empty cover 0276 const QString image_id = entry->field(QStringLiteral("cover")); 0277 if(!image_id.isEmpty()) { 0278 return entry; 0279 } 0280 0281 u = QUrl(QString::fromLatin1(MOBYGAMES_API_URL)); 0282 u.setPath(u.path() + QStringLiteral("/games/%1/platforms/%2/covers") 0283 .arg(entry->field(QStringLiteral("moby-id")), 0284 entry->field(QStringLiteral("platform-id")))); 0285 u.setQuery(q); 0286 // myDebug() << u; 0287 0288 markTime(); 0289 job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); 0290 KJobWidgets::setWindow(job, GUI::Proxy::widget()); 0291 if(!job->exec()) { 0292 myDebug() << job->errorString() << u; 0293 return entry; 0294 } 0295 data = job->data(); 0296 if(data.isEmpty()) { 0297 myDebug() << "no data for" << u; 0298 return entry; 0299 } 0300 #if 0 0301 myWarning() << "Remove covers debug from mobygamesfetcher.cpp"; 0302 QFile file2(QStringLiteral("/tmp/moby-covers.json")); 0303 if(file2.open(QIODevice::WriteOnly)) { 0304 QTextStream t(&file2); 0305 t.setCodec("UTF-8"); 0306 t << data; 0307 } 0308 file2.close(); 0309 #endif 0310 0311 QString coverUrl; 0312 doc = QJsonDocument::fromJson(data); 0313 map = doc.object().toVariantMap(); 0314 // prefer "Front Cover" but fall back to "Media" 0315 QString front, media; 0316 QVariantList coverGroupList = map.value(QStringLiteral("cover_groups")).toList(); 0317 foreach(const QVariant& coverGroup, coverGroupList) { 0318 // just take the cover from the first group with front cover, appear to be grouped by country 0319 QVariantList coverList = coverGroup.toMap().value(QStringLiteral("covers")).toList(); 0320 foreach(const QVariant& coverVariant, coverList) { 0321 const QVariantMap coverMap = coverVariant.toMap(); 0322 if(media.isEmpty() && 0323 coverMap.value(QStringLiteral("scan_of")) == QStringLiteral("Media")) { 0324 media = m_imageSize == SmallImage ? 0325 coverMap.value(QStringLiteral("thumbnail_image")).toString() : 0326 coverMap.value(QStringLiteral("image")).toString(); 0327 } else if(coverMap.value(QStringLiteral("scan_of")) == QStringLiteral("Front Cover")) { 0328 front = m_imageSize == SmallImage ? 0329 coverMap.value(QStringLiteral("thumbnail_image")).toString() : 0330 coverMap.value(QStringLiteral("image")).toString(); 0331 break; 0332 } 0333 } 0334 if(!front.isEmpty()) { 0335 // no need to continue iteration through cover groups 0336 break; 0337 } 0338 } 0339 0340 coverUrl = front.isEmpty() ? media : front; // fall back to media image 0341 0342 if(!coverUrl.isEmpty()) { 0343 // myDebug() << coverUrl; 0344 const QString id = ImageFactory::addImage(QUrl::fromUserInput(coverUrl), true /* quiet */); 0345 if(id.isEmpty()) { 0346 message(i18n("The cover image could not be loaded."), MessageHandler::Warning); 0347 } 0348 // empty image ID is ok 0349 entry->setField(QStringLiteral("cover"), id); 0350 } 0351 0352 const QString screenshot = QStringLiteral("screenshot"); 0353 if(optionalFields().contains(screenshot)) { 0354 if(!entry->collection()->hasField(screenshot)) { 0355 entry->collection()->addField(Data::Field::createDefaultField(Data::Field::ScreenshotField)); 0356 } 0357 u = QUrl(QString::fromLatin1(MOBYGAMES_API_URL)); 0358 u.setPath(u.path() + QStringLiteral("/games/%1/platforms/%2/screenshots") 0359 .arg(entry->field(QStringLiteral("moby-id")), 0360 entry->field(QStringLiteral("platform-id")))); 0361 u.setQuery(q); 0362 markTime(); 0363 job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); 0364 KJobWidgets::setWindow(job, GUI::Proxy::widget()); 0365 if(!job->exec()) { 0366 myDebug() << job->errorString() << u; 0367 return entry; 0368 } 0369 #if 0 0370 myWarning() << "Remove screenshots debug from mobygamesfetcher.cpp"; 0371 QFile file3(QStringLiteral("/tmp/moby-screenshots.json")); 0372 if(file3.open(QIODevice::WriteOnly)) { 0373 QTextStream t(&file3); 0374 t.setCodec("UTF-8"); 0375 t << job->data(); 0376 } 0377 file3.close(); 0378 #endif 0379 QString screenshotUrl; 0380 doc = QJsonDocument::fromJson(job->data()); 0381 map = doc.object().toVariantMap(); 0382 auto list = map.value(QStringLiteral("screenshots")).toList(); 0383 if(!list.isEmpty()) { 0384 screenshotUrl = mapValue(list.at(0).toMap(), "image"); 0385 } 0386 if(!screenshotUrl.isEmpty()) { 0387 // myDebug() << screenshotUrl; 0388 const QString id = ImageFactory::addImage(QUrl::fromUserInput(screenshotUrl), true /* quiet */); 0389 entry->setField(screenshot, id); 0390 } 0391 } 0392 0393 // clear the placeholder fields 0394 entry->setField(QStringLiteral("moby-id"), QString()); 0395 entry->setField(QStringLiteral("platform-id"), QString()); 0396 return entry; 0397 } 0398 0399 Tellico::Fetch::FetchRequest MobyGamesFetcher::updateRequest(Data::EntryPtr entry_) { 0400 const QString title = entry_->field(QStringLiteral("title")); 0401 const QString platform = entry_->field(QStringLiteral("platform")); 0402 // if the platform name is not empty, we can use that to limit the title search 0403 if(!platform.isEmpty()) { 0404 // iterate through platform map to potentially match name 0405 // would ultimately be faster to have a second hash to map name to id or a bidirectional 0406 // could assume the platform name is already normalized, but allow user to have entered something 0407 // not quite the same 0408 if(m_platforms.isEmpty()) { 0409 updatePlatforms(); 0410 } 0411 const int pId = m_platforms.key(Data::GameCollection::normalizePlatform(platform)); 0412 if(pId > 0) { 0413 return FetchRequest(Raw, QString::fromLatin1("title=%1&platform=%2").arg(title, QString::number(pId))); 0414 } 0415 } 0416 0417 // fallback to pure title search 0418 if(!title.isEmpty()) { 0419 return FetchRequest(Title, title); 0420 } 0421 return FetchRequest(); 0422 } 0423 0424 void MobyGamesFetcher::slotComplete(KJob* job_) { 0425 KIO::StoredTransferJob* job = static_cast<KIO::StoredTransferJob*>(job_); 0426 0427 if(job->error()) { 0428 job->uiDelegate()->showErrorMessage(); 0429 stop(); 0430 return; 0431 } 0432 0433 const QByteArray data = job->data(); 0434 if(data.isEmpty()) { 0435 myDebug() << "no data"; 0436 stop(); 0437 return; 0438 } 0439 // see bug 319662. If fetcher is cancelled, job is killed 0440 // if the pointer is retained, it gets double-deleted 0441 m_job = nullptr; 0442 0443 #if 0 0444 myWarning() << "Remove debug from mobygamesfetcher.cpp"; 0445 QFile file(QStringLiteral("/tmp/moby-results.json")); 0446 if(file.open(QIODevice::WriteOnly)) { 0447 QTextStream t(&file); 0448 t.setCodec("UTF-8"); 0449 t << data; 0450 } 0451 file.close(); 0452 #endif 0453 0454 Data::CollPtr coll(new Data::GameCollection(true)); 0455 if(optionalFields().contains(QStringLiteral("pegi"))) { 0456 coll->addField(Data::Field::createDefaultField(Data::Field::PegiField)); 0457 } 0458 if(optionalFields().contains(QStringLiteral("mobygames"))) { 0459 Data::FieldPtr field(new Data::Field(QStringLiteral("mobygames"), i18n("MobyGames Link"), Data::Field::URL)); 0460 field->setCategory(i18n("General")); 0461 coll->addField(field); 0462 } 0463 // placeholder for mobygames id, to be removed later 0464 Data::FieldPtr f1(new Data::Field(QStringLiteral("moby-id"), QString(), Data::Field::Number)); 0465 coll->addField(f1); 0466 Data::FieldPtr f2(new Data::Field(QStringLiteral("platform-id"), QString(), Data::Field::Number)); 0467 coll->addField(f2); 0468 0469 QJsonDocument doc = QJsonDocument::fromJson(data); 0470 QVariantMap map = doc.object().toVariantMap(); 0471 0472 // check for error 0473 if(map.contains(QStringLiteral("error"))) { 0474 const QString msg = map.value(QStringLiteral("message")).toString(); 0475 message(msg, MessageHandler::Error); 0476 myDebug() << "MobyGamesFetcher -" << msg; 0477 stop(); 0478 return; 0479 } 0480 0481 if(m_platforms.isEmpty()) { 0482 updatePlatforms(); 0483 } 0484 0485 foreach(const QVariant& result, map.value(QStringLiteral("games")).toList()) { 0486 QVariantMap resultMap = result.toMap(); 0487 Data::EntryList entries = createEntries(coll, resultMap); 0488 foreach(const Data::EntryPtr& entry, entries) { 0489 FetchResult* r = new FetchResult(this, entry); 0490 m_entries.insert(r->uid, entry); 0491 emit signalResultFound(r); 0492 } 0493 } 0494 0495 stop(); 0496 } 0497 0498 Tellico::Data::EntryList MobyGamesFetcher::createEntries(Data::CollPtr coll_, const QVariantMap& resultMap_) { 0499 Data::EntryPtr entry(new Data::Entry(coll_)); 0500 entry->setField(QStringLiteral("title"), mapValue(resultMap_, "title")); 0501 entry->setField(QStringLiteral("description"), mapValue(resultMap_, "description")); 0502 entry->setField(QStringLiteral("moby-id"), mapValue(resultMap_, "game_id")); 0503 0504 QStringList genres; 0505 foreach(const QVariant& genreMap, resultMap_.value(QStringLiteral("genres")).toList()) { 0506 const QString g = genreMap.toMap().value(QStringLiteral("genre_name")).toString(); 0507 if(!g.isEmpty()) { 0508 genres << g; 0509 } 0510 } 0511 entry->setField(QStringLiteral("genre"), genres.join(FieldFormat::delimiterString())); 0512 0513 if(optionalFields().contains(QStringLiteral("mobygames"))) { 0514 entry->setField(QStringLiteral("mobygames"), mapValue(resultMap_, "moby_url")); 0515 } 0516 0517 const QString platformS(QStringLiteral("platform")); 0518 0519 // for efficiency, check if the search includes a platform 0520 // since the results will include all the platforms, not just the searched one 0521 if(request().key() == Raw && 0522 request().value().contains(platformS)) { 0523 QUrlQuery q(request().value()); 0524 m_requestPlatformId = q.queryItemValue(platformS).toInt(); 0525 } 0526 0527 Data::EntryList entries; 0528 // return a new entry for every platform 0529 foreach(const QVariant& platformMapV, resultMap_.value(QStringLiteral("platforms")).toList()) { 0530 Data::EntryPtr newEntry(new Data::Entry(*entry)); 0531 0532 const QVariantMap platformMap = platformMapV.toMap(); 0533 const int platformId = platformMap.value(QStringLiteral("platform_id")).toInt(); 0534 if(m_platforms.contains(platformId)) { 0535 const QString platform = m_platforms[platformId]; 0536 // make the assumption that if the platform name isn't already in the allowed list, it should be added 0537 Data::FieldPtr f = newEntry->collection()->fieldByName(platformS); 0538 if(f && !f->allowed().contains(platform)) { 0539 f->setAllowed(QStringList(f->allowed()) << platform); 0540 } 0541 newEntry->setField(platformS, platform); 0542 } else { 0543 myDebug() << "platform list does not contain" << platformId << mapValue(platformMap, "platform_name"); 0544 } 0545 0546 newEntry->setField(QStringLiteral("platform-id"), 0547 platformMap.value(QStringLiteral("platform_id")).toString()); 0548 newEntry->setField(QStringLiteral("year"), 0549 platformMap.value(QStringLiteral("first_release_date")).toString().left(4)); 0550 if(m_requestPlatformId == 0 || m_requestPlatformId == platformId) entries << newEntry; 0551 } 0552 return entries; 0553 } 0554 0555 void MobyGamesFetcher::markTime() { 0556 // need to wait a bit after previous query, Moby error message say 1 sec 0557 if(m_idleTime.elapsed() < 1000) QThread::msleep(1000); 0558 m_idleTime.restart(); 0559 } 0560 0561 void MobyGamesFetcher::populateHashes() { 0562 // cheat by grabbing i18n values from default collection 0563 Data::CollPtr c(new Data::GameCollection(true)); 0564 QStringList esrb = c->fieldByName(QStringLiteral("certification"))->allowed(); 0565 Q_ASSERT(esrb.size() == 8); 0566 while(esrb.size() < 8) { 0567 esrb << QString(); 0568 } 0569 // 89 == EC 0570 // 90 == Everyone 0571 // etc. 0572 // 95 == Pending 0573 // https://www.mobygames.com/attribute/sheet/attributeId,89 0574 m_esrbHash.insert(89, esrb.at(6)); 0575 m_esrbHash.insert(90, esrb.at(5)); 0576 m_esrbHash.insert(91, esrb.at(4)); 0577 m_esrbHash.insert(92, esrb.at(3)); 0578 m_esrbHash.insert(93, esrb.at(2)); 0579 m_esrbHash.insert(94, esrb.at(1)); 0580 m_esrbHash.insert(95, esrb.at(7)); 0581 0582 // Read the cached data for the platform list 0583 QFile file(Tellico::saveLocation(QStringLiteral("mobygames-data/")) + QLatin1String("platforms.json")); 0584 if(file.open(QIODevice::ReadOnly)) { 0585 m_platforms.clear(); 0586 const QVariantMap topMap = QJsonDocument::fromJson(file.readAll()).object().toVariantMap(); 0587 foreach(const QVariant& platform, topMap.value(QStringLiteral("platforms")).toList()) { 0588 const QVariantMap m = platform.toMap(); 0589 Data::GameCollection::GamePlatform pId = Data::GameCollection::guessPlatform(mapValue(m, "platform_name")); 0590 if(pId == Data::GameCollection::UnknownPlatform) { 0591 // platform is not in the default list, just keep it as is 0592 m_platforms.insert(m.value(QStringLiteral("platform_id")).toInt(), 0593 mapValue(m, "platform_name")); 0594 } else { 0595 m_platforms.insert(m.value(QStringLiteral("platform_id")).toInt(), 0596 Data::GameCollection::platformName(pId)); 0597 } 0598 } 0599 } else if(file.exists()) { // don't want errors for non-existing file 0600 myDebug() << "Failed to read from" << file.fileName() << file.errorString(); 0601 } 0602 } 0603 0604 void MobyGamesFetcher::updatePlatforms() { 0605 QUrl u(QString::fromLatin1(MOBYGAMES_API_URL)); 0606 u.setPath(u.path() + QStringLiteral("/platforms")); 0607 QUrlQuery q; 0608 q.addQueryItem(QStringLiteral("api_key"), m_apiKey); 0609 u.setQuery(q); 0610 0611 // u = QUrl::fromLocalFile(QStringLiteral("/home/robby/platforms.json")); // for testing 0612 // myDebug() << "Reading platforms from" << u; 0613 markTime(); 0614 const QByteArray data = FileHandler::readDataFile(u, true); 0615 QFile file(Tellico::saveLocation(QStringLiteral("mobygames-data/")) + QLatin1String("platforms.json")); 0616 if(!file.open(QIODevice::WriteOnly) || file.write(data) == -1) { 0617 myDebug() << "unable to write to" << file.fileName() << file.errorString(); 0618 return; 0619 } 0620 file.close(); 0621 populateHashes(); 0622 } 0623 0624 Tellico::Fetch::ConfigWidget* MobyGamesFetcher::configWidget(QWidget* parent_) const { 0625 return new MobyGamesFetcher::ConfigWidget(parent_, this); 0626 } 0627 0628 QString MobyGamesFetcher::defaultName() { 0629 return QStringLiteral("MobyGames"); 0630 } 0631 0632 QString MobyGamesFetcher::defaultIcon() { 0633 return favIcon("https://www.mobygames.com/static/img/favicon.ico"); 0634 } 0635 0636 Tellico::StringHash MobyGamesFetcher::allOptionalFields() { 0637 StringHash hash; 0638 hash[QStringLiteral("pegi")] = i18n("PEGI Rating"); 0639 hash[QStringLiteral("mobygames")] = i18n("MobyGames Link"); 0640 hash[QStringLiteral("screenshot")] = i18n("Screenshot"); 0641 return hash; 0642 } 0643 0644 MobyGamesFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const MobyGamesFetcher* fetcher_) 0645 : Fetch::ConfigWidget(parent_) { 0646 QGridLayout* l = new QGridLayout(optionsWidget()); 0647 l->setSpacing(4); 0648 l->setColumnStretch(1, 10); 0649 0650 int row = -1; 0651 0652 QLabel* al = new QLabel(i18n("Registration is required for accessing this data source. " 0653 "If you agree to the terms and conditions, <a href='%1'>sign " 0654 "up for an account</a>, and enter your information below.", 0655 QStringLiteral("https://www.mobygames.com/info/api")), 0656 optionsWidget()); 0657 al->setOpenExternalLinks(true); 0658 al->setWordWrap(true); 0659 ++row; 0660 l->addWidget(al, row, 0, 1, 2); 0661 // richtext gets weird with size 0662 al->setMinimumWidth(al->sizeHint().width()); 0663 0664 QLabel* label = new QLabel(i18n("Access key: "), optionsWidget()); 0665 l->addWidget(label, ++row, 0); 0666 0667 m_apiKeyEdit = new QLineEdit(optionsWidget()); 0668 connect(m_apiKeyEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified); 0669 l->addWidget(m_apiKeyEdit, row, 1); 0670 label->setBuddy(m_apiKeyEdit); 0671 0672 label = new QLabel(i18n("&Image size: "), optionsWidget()); 0673 l->addWidget(label, ++row, 0); 0674 m_imageCombo = new GUI::ComboBox(optionsWidget()); 0675 m_imageCombo->addItem(i18n("Small Image"), SmallImage); 0676 // m_imageCombo->addItem(i18n("Medium Image"), MediumImage); // no medium right now, either thumbnail (small) or large 0677 m_imageCombo->addItem(i18n("Large Image"), LargeImage); 0678 m_imageCombo->addItem(i18n("No Image"), NoImage); 0679 void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated; 0680 connect(m_imageCombo, activatedInt, this, &ConfigWidget::slotSetModified); 0681 l->addWidget(m_imageCombo, row, 1); 0682 QString w = i18n("The cover image may be downloaded as well. However, too many large images in the " 0683 "collection may degrade performance."); 0684 label->setWhatsThis(w); 0685 m_imageCombo->setWhatsThis(w); 0686 label->setBuddy(m_imageCombo); 0687 0688 l->setRowStretch(++row, 10); 0689 0690 // now add additional fields widget 0691 addFieldsWidget(MobyGamesFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList()); 0692 0693 if(fetcher_) { 0694 m_apiKeyEdit->setText(fetcher_->m_apiKey); 0695 m_imageCombo->setCurrentData(fetcher_->m_imageSize); 0696 } else { // defaults 0697 m_imageCombo->setCurrentData(SmallImage); 0698 } 0699 } 0700 0701 void MobyGamesFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) { 0702 const QString apiKey = m_apiKeyEdit->text().trimmed(); 0703 if(!apiKey.isEmpty()) { 0704 config_.writeEntry("API Key", apiKey); 0705 } 0706 const int n = m_imageCombo->currentData().toInt(); 0707 config_.writeEntry("Image Size", n); 0708 } 0709 0710 QString MobyGamesFetcher::ConfigWidget::preferredName() const { 0711 return MobyGamesFetcher::defaultName(); 0712 }