[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [tellico] src: Add data source for IGDB.com
From:       Robby Stephenson <null () kde ! org>
Date:       2017-04-30 20:40:14
Message-ID: E1d4vde-0001S4-ID () code ! kde ! org
[Download RAW message or body]

Git commit 9b44d9e11142a82ea09a75628e11b56fcf0c2f27 by Robby Stephenson.
Committed on 30/04/2017 at 20:40.
Pushed by rstephenson into branch 'master'.

Add data source for IGDB.com

A  +650  -0    src/fetch/igdbfetcher.cpp     [License: GPL (v2/3)]
A  +126  -0    src/fetch/igdbfetcher.h     [License: GPL (v2/3)]
A  +79   -0    src/tests/igdbfetchertest.cpp     [License: GPL (v2/3)]
A  +40   -0    src/tests/igdbfetchertest.h     [License: GPL (v2/3)]

https://commits.kde.org/tellico/9b44d9e11142a82ea09a75628e11b56fcf0c2f27

diff --git a/src/fetch/igdbfetcher.cpp b/src/fetch/igdbfetcher.cpp
new file mode 100644
index 00000000..a9d15316
--- /dev/null
+++ b/src/fetch/igdbfetcher.cpp
@@ -0,0 +1,650 @@
+/***************************************************************************
+    Copyright (C) 2017 Robby Stephenson <robby@periapsis.org>
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or         *
+ *   modify it under the terms of the GNU General Public License as        *
+ *   published by the Free Software Foundation; either version 2 of        *
+ *   the License or (at your option) version 3 or any later version        *
+ *   accepted by the membership of KDE e.V. (or its successor approved     *
+ *   by the membership of KDE e.V.), which shall act as a proxy            *
+ *   defined in Section 14 of version 3 of the license.                    *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
+ *                                                                         *
+ ***************************************************************************/
+
+#include "igdbfetcher.h"
+#include "../collections/gamecollection.h"
+#include "../images/imagefactory.h"
+#include "../core/filehandler.h"
+#include "../utils/guiproxy.h"
+#include "../utils/string_utils.h"
+#include "../tellico_debug.h"
+
+#include <KLocalizedString>
+#include <KConfigGroup>
+#include <KJob>
+#include <KJobUiDelegate>
+#include <KJobWidgets/KJobWidgets>
+#include <KIO/StoredTransferJob>
+
+#include <QUrl>
+#include <QLabel>
+#include <QFile>
+#include <QTextStream>
+#include <QGridLayout>
+#include <QTextCodec>
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QUrlQuery>
+
+namespace {
+  static const int IGDB_MAX_RETURNS_TOTAL = 20;
+  static const char* IGDB_API_URL = \
"https://igdbcom-internet-game-database-v1.p.mashape.com/"; +  static const char* \
IGDB_API_KEY = "Ger6nO0EnKmsh7FCyUPa3GMdeYM5p1sfrPjjsnLYoHdDf19CGG"; +}
+
+using namespace Tellico;
+using Tellico::Fetch::IGDBFetcher;
+
+QHash<int, QString> IGDBFetcher::s_genreHash;
+QHash<int, QString> IGDBFetcher::s_platformHash;
+QHash<QString, QString> IGDBFetcher::s_companyHash;
+QHash<QString, QString> IGDBFetcher::s_esrbHash;
+QHash<QString, QString> IGDBFetcher::s_pegiHash;
+
+IGDBFetcher::IGDBFetcher(QObject* parent_)
+    : Fetcher(parent_), m_started(false), m_apiKey(QLatin1String(IGDB_API_KEY)) {
+  //  setLimit(IGDB_MAX_RETURNS_TOTAL);
+  if(s_genreHash.isEmpty()) {
+    populateHashes();
+  }
+}
+
+IGDBFetcher::~IGDBFetcher() {
+  myDebug() << "destroyed IGDBFetcher";
+}
+
+QString IGDBFetcher::source() const {
+  return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+QString IGDBFetcher::attribution() const {
+  return i18n("This information was freely provided by <a \
href=\"http://igdb.com\">IGDB.com</a>."); +}
+
+bool IGDBFetcher::canSearch(FetchKey k) const {
+  return k == Keyword;
+}
+
+bool IGDBFetcher::canFetch(int type) const {
+  return type == Data::Collection::Game;
+}
+
+void IGDBFetcher::readConfigHook(const KConfigGroup& config_) {
+  QString k = config_.readEntry("API Key", IGDB_API_KEY);
+  if(!k.isEmpty()) {
+    m_apiKey = k;
+  }
+}
+
+void IGDBFetcher::search() {
+  continueSearch();
+}
+
+void IGDBFetcher::continueSearch() {
+  m_started = true;
+
+  if(m_apiKey.isEmpty()) {
+    myDebug() << "empty API key";
+    stop();
+    return;
+  }
+
+  QUrl u(QString::fromLatin1(IGDB_API_URL));
+  u.setPath(u.path() + QLatin1String("games/"));
+  QUrlQuery q;
+  switch(request().key) {
+    case Keyword:
+      q.addQueryItem(QLatin1String("search"), request().value);
+      break;
+
+    default:
+      myWarning() << "key not recognized:" << request().key;
+      stop();
+      return;
+  }
+//  q.addQueryItem(QLatin1String("fields"), QLatin1String("id,name"));
+  q.addQueryItem(QLatin1String("fields"), QLatin1String("*"));
+  q.addQueryItem(QLatin1String("limit"), QString::number(IGDB_MAX_RETURNS_TOTAL));
+  u.setQuery(q);
+//  myDebug() << u;
+
+  m_job = igdbJob(u, m_apiKey);
+  connect(m_job, SIGNAL(result(KJob*)), SLOT(slotComplete(KJob*)));
+}
+
+void IGDBFetcher::stop() {
+  if(!m_started) {
+    return;
+  }
+  if(m_job) {
+    m_job->kill();
+    m_job = nullptr;
+  }
+  m_started = false;
+  emit signalDone(this);
+}
+
+Tellico::Data::EntryPtr IGDBFetcher::fetchEntryHook(uint uid_) {
+  if(!m_entries.contains(uid_)) {
+    myDebug() << "no entry ptr";
+    return Data::EntryPtr();
+  }
+
+  Data::EntryPtr entry = m_entries.value(uid_);
+
+  QStringList publishers;
+  // grab the publisher data
+  if(entry->field(QLatin1String("publisher")).isEmpty()) {
+    foreach(const QString& pid, \
FieldFormat::splitValue(entry->field(QLatin1String("pub-id")))) { +      const \
QString publisher = companyName(pid); +      if(!publisher.isEmpty()) {
+        publishers << publisher;
+      }
+    }
+  }
+  entry->setField(QLatin1String("publisher"), \
publishers.join(FieldFormat::delimiterString())); +
+  QStringList developers;
+  // grab the developer data
+  if(entry->field(QLatin1String("developer")).isEmpty()) {
+    foreach(const QString& did, \
FieldFormat::splitValue(entry->field(QLatin1String("dev-id")))) { +      const \
QString developer = companyName(did); +      if(!developer.isEmpty()) {
+        developers << developer;
+      }
+    }
+  }
+  entry->setField(QLatin1String("developer"), \
developers.join(FieldFormat::delimiterString())); +
+  // clear the placeholder fields
+  entry->setField(QLatin1String("pub-id"), QString());
+  entry->setField(QLatin1String("dev-id"), QString());
+  return entry;
+}
+
+Tellico::Fetch::FetchRequest IGDBFetcher::updateRequest(Data::EntryPtr entry_) {
+  QString title = entry_->field(QLatin1String("title"));
+  if(!title.isEmpty()) {
+    return FetchRequest(Keyword, title);
+  }
+  return FetchRequest();
+}
+
+void IGDBFetcher::slotComplete(KJob* job_) {
+  KIO::StoredTransferJob* job = static_cast<KIO::StoredTransferJob*>(job_);
+
+  if(job->error()) {
+    job->ui()->showErrorMessage();
+    stop();
+    return;
+  }
+
+  const QByteArray data = job->data();
+  if(data.isEmpty()) {
+    myDebug() << "no data";
+    stop();
+    return;
+  }
+  // see bug 319662. If fetcher is cancelled, job is killed
+  // if the pointer is retained, it gets double-deleted
+  m_job = nullptr;
+
+#if 0
+  myWarning() << "Remove debug from igdbfetcher.cpp";
+  QFile file(QString::fromLatin1("/tmp/test.json"));
+  if(file.open(QIODevice::WriteOnly)) {
+    QTextStream t(&file);
+    t.setCodec("UTF-8");
+    t << data;
+  }
+  file.close();
+#endif
+
+  Data::CollPtr coll(new Data::GameCollection(true));
+  if(optionalFields().contains(QLatin1String("pegi"))) {
+    QStringList pegi = QString::fromLatin1("PEGI 3, PEGI 7, PEGI 12, PEGI 16, PEGI \
18") +                                    .split(QRegExp(QLatin1String("\\s*,\\s*")), \
QString::SkipEmptyParts); +    Data::FieldPtr field(new \
Data::Field(QLatin1String("pegi"), i18n("PEGI Rating"), pegi)); +    \
field->setFlags(Data::Field::AllowGrouped); +    field->setCategory(i18n("General"));
+    coll->addField(field);
+  }
+  if(optionalFields().contains(QLatin1String("igdb"))) {
+    Data::FieldPtr field(new Data::Field(QLatin1String("igdb"), i18n("IGDB Link"), \
Data::Field::URL)); +    field->setCategory(i18n("General"));
+    coll->addField(field);
+  }
+  // placeholder for publisher id, to be removed later
+  Data::FieldPtr f(new Data::Field(QLatin1String("pub-id"), QString(), \
Data::Field::Number)); +  f->setFlags(Data::Field::AllowMultiple);
+  coll->addField(f);
+  // placeholder for developer id, to be removed later
+  f = new Data::Field(QLatin1String("dev-id"), QString(), Data::Field::Number);
+  f->setFlags(Data::Field::AllowMultiple);
+  coll->addField(f);
+
+  QJsonDocument doc = QJsonDocument::fromJson(data);
+  foreach(const QVariant& result, doc.array().toVariantList()) {
+    QVariantMap resultMap = result.toMap();
+    Data::EntryPtr entry(new Data::Entry(coll));
+    populateEntry(entry, resultMap);
+
+    FetchResult* r = new FetchResult(Fetcher::Ptr(this), entry);
+    m_entries.insert(r->uid, entry);
+    emit signalResultFound(r);
+  }
+
+  stop();
+}
+
+void IGDBFetcher::populateEntry(Data::EntryPtr entry_, const QVariantMap& \
resultMap_) { +  entry_->setField(QLatin1String("title"), value(resultMap_, "name"));
+  entry_->setField(QLatin1String("description"), value(resultMap_, "summary"));
+  entry_->setField(QLatin1String("certification"), \
s_esrbHash.value(value(resultMap_, "esrb", "rating"))); +  \
entry_->setField(QLatin1String("pub-id"), value(resultMap_, "publishers")); +  \
entry_->setField(QLatin1String("dev-id"), value(resultMap_, "developers")); +
+  QString cover = value(resultMap_, "cover", "url");
+  if(cover.startsWith(QLatin1Char('/'))) {
+    cover.prepend(QLatin1String("https:"));
+  }
+  entry_->setField(QLatin1String("cover"), cover);
+
+  QVariantList genreIDs = resultMap_.value(QLatin1String("genres")).toList();
+  QStringList genres;
+  foreach(const QVariant& id, genreIDs) {
+    QString g = s_genreHash.value(id.toInt());
+    if(!g.isEmpty()) {
+      genres << g;
+    }
+  }
+  entry_->setField(QLatin1String("genre"), \
genres.join(FieldFormat::delimiterString())); +
+  QVariantList releases = resultMap_.value(QLatin1String("release_dates")).toList();
+  if(!releases.isEmpty()) {
+    QVariantMap releaseMap = releases.at(0).toMap();
+    // for now just grab the year of the first release
+    entry_->setField(QLatin1String("year"), value(releaseMap, "y"));
+    const QString platform = \
s_platformHash.value(releaseMap.value(QLatin1String("platform")).toInt()); +    \
if(platform == QLatin1String("Nintendo Entertainment System (NES)")) { +      \
entry_->setField(QLatin1String("platform"), i18n("Nintendo")); +    } else \
if(platform == QLatin1String("Nintendo PlayStation")) { +      \
entry_->setField(QLatin1String("platform"), i18n("PlayStation")); +    } else \
if(platform == QLatin1String("PlayStation 2")) { +      \
entry_->setField(QLatin1String("platform"), i18n("PlayStation2")); +    } else \
if(platform == QLatin1String("PlayStation 3")) { +      \
entry_->setField(QLatin1String("platform"), i18n("PlayStation3")); +    } else \
if(platform == QLatin1String("PlayStation 4")) { +      \
entry_->setField(QLatin1String("platform"), i18n("PlayStation4")); +    } else \
if(platform == QLatin1String("PlayStation Portable")) { +      \
entry_->setField(QLatin1String("platform"), i18nc("PlayStation Portable", "PSP")); +  \
} else if(platform == QLatin1String("Wii")) { +      \
entry_->setField(QLatin1String("platform"), i18n("Nintendo Wii")); +    } else \
if(platform == QLatin1String("Nintendo GameCube")) { +      \
entry_->setField(QLatin1String("platform"), i18n("GameCube")); +    } else \
if(platform == QLatin1String("PC (Microsoft Windows)")) { +      \
entry_->setField(QLatin1String("platform"), i18nc("Windows Platform", "Windows")); +  \
} else if(platform == QLatin1String("Mac")) { +      \
entry_->setField(QLatin1String("platform"), i18n("Mac OS")); +    } else {
+      // TODO all the other platform translations
+      // also make the assumption that if the platform name isn't already in the \
allowed list, it should be added +      Data::FieldPtr f = \
entry_->collection()->fieldByName(QLatin1String("platform")); +      if(f && \
!f->allowed().contains(platform)) { +        f->setAllowed(QStringList(f->allowed()) \
<< platform); +      }
+      entry_->setField(QLatin1String("platform"), platform);
+    }
+  }
+
+  if(optionalFields().contains(QLatin1String("pegi"))) {
+    entry_->setField(QLatin1String("pegi"), s_pegiHash.value(value(resultMap_, \
"pegi", "rating"))); +  }
+
+  if(optionalFields().contains(QLatin1String("igdb"))) {
+    entry_->setField(QLatin1String("igdb"), value(resultMap_, "url"));
+  }
+}
+
+QString IGDBFetcher::companyName(const QString& companyId_) const {
+  if(s_companyHash.contains(companyId_)) {
+    return s_companyHash.value(companyId_);
+  }
+  QUrl u(QString::fromLatin1(IGDB_API_URL));
+  u.setPath(u.path() + QLatin1String("companies/") + companyId_);
+
+  QUrlQuery q;
+  q.addQueryItem(QLatin1String("fields"), QLatin1String("*"));
+
+  u.setQuery(q);
+
+  QPointer<KIO::StoredTransferJob> job = igdbJob(u, m_apiKey);
+  if(!job->exec()) {
+    myDebug() << job->errorString() << u;
+    return QString();
+  }
+  const QByteArray data = job->data();
+  if(data.isEmpty()) {
+    myDebug() << "no data for" << u;
+    return QString();
+  }
+#if 0
+  myWarning() << "Remove company debug from igdbfetcher.cpp";
+  QFile file(QString::fromLatin1("/tmp/igdb-company.json"));
+  if(file.open(QIODevice::WriteOnly)) {
+    QTextStream t(&file);
+    t.setCodec("UTF-8");
+    t << data;
+  }
+  file.close();
+#endif
+
+  QJsonDocument doc = QJsonDocument::fromJson(data);
+  const QString company = value(doc.array().toVariantList().at(0).toMap(), "name");
+  s_companyHash.insert(companyId_, company);
+  return company;
+}
+
+Tellico::Fetch::ConfigWidget* IGDBFetcher::configWidget(QWidget* parent_) const {
+  return new IGDBFetcher::ConfigWidget(parent_, this);
+}
+
+QString IGDBFetcher::defaultName() {
+  return i18n("Internet Game Database (IGDB.com)");
+}
+
+QString IGDBFetcher::defaultIcon() {
+  return favIcon("http://www.igdb.com");
+}
+
+Tellico::StringHash IGDBFetcher::allOptionalFields() {
+  StringHash hash;
+  hash[QLatin1String("pegi")] = i18n("PEGI Rating");
+  hash[QLatin1String("igdb")] = i18n("IGDB Link");
+  return hash;
+}
+
+IGDBFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const IGDBFetcher* \
fetcher_) +    : Fetch::ConfigWidget(parent_) {
+  QGridLayout* l = new QGridLayout(optionsWidget());
+  l->setSpacing(4);
+  l->setColumnStretch(1, 10);
+
+  int row = -1;
+
+  QLabel* al = new QLabel(i18n("Registration is required for accessing the %1 data \
source. " +                               "If you agree to the terms and conditions, \
<a href='%2'>sign " +                               "up for an account</a>, and enter \
your information below.", +                                \
IGDBFetcher::defaultName(), +                                \
QLatin1String("http://igdb.github.io/api/about/welcome/")), +                         \
optionsWidget()); +  al->setOpenExternalLinks(true);
+  al->setWordWrap(true);
+  ++row;
+  l->addWidget(al, row, 0, 1, 2);
+  // richtext gets weird with size
+  al->setMinimumWidth(al->sizeHint().width());
+
+  QLabel* label = new QLabel(i18n("Access key: "), optionsWidget());
+  l->addWidget(label, ++row, 0);
+
+  m_apiKeyEdit = new QLineEdit(optionsWidget());
+  connect(m_apiKeyEdit, SIGNAL(textChanged(const QString&)), \
SLOT(slotSetModified())); +  l->addWidget(m_apiKeyEdit, row, 1);
+  QString w = i18n("The default Tellico key may be used, but searching may fail due \
to reaching access limits."); +  label->setWhatsThis(w);
+  m_apiKeyEdit->setWhatsThis(w);
+  label->setBuddy(m_apiKeyEdit);
+
+  l->setRowStretch(++row, 10);
+
+  // now add additional fields widget
+  addFieldsWidget(IGDBFetcher::allOptionalFields(), fetcher_ ? \
fetcher_->optionalFields() : QStringList()); +
+  if(fetcher_) {
+    // only show the key if it is not the default Tellico one...
+    // that way the user is prompted to apply for their own
+    if(fetcher_->m_apiKey != QLatin1String(IGDB_API_KEY)) {
+      m_apiKeyEdit->setText(fetcher_->m_apiKey);
+    }
+  }
+}
+
+void IGDBFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) {
+  const QString apiKey = m_apiKeyEdit->text().trimmed();
+  if(!apiKey.isEmpty()) {
+    config_.writeEntry("API Key", apiKey);
+  }
+}
+
+QString IGDBFetcher::ConfigWidget::preferredName() const {
+  return IGDBFetcher::defaultName();
+}
+
+// static
+QString IGDBFetcher::value(const QVariantMap& map, const char* name) {
+  const QVariant v = map.value(QLatin1String(name));
+  if(v.isNull())  {
+    return QString();
+  } else if(v.canConvert(QVariant::String)) {
+    return v.toString();
+  } else if(v.canConvert(QVariant::StringList)) {
+    return v.toStringList().join(FieldFormat::delimiterString());
+  } else {
+    return QString();
+  }
+}
+
+QString IGDBFetcher::value(const QVariantMap& map, const char* object, const char* \
name) { +  const QVariant v = map.value(QLatin1String(object));
+  if(v.isNull())  {
+    return QString();
+  } else if(v.canConvert(QVariant::Map)) {
+    return value(v.toMap(), name);
+  } else if(v.canConvert(QVariant::List)) {
+    QVariantList list = v.toList();
+    return list.isEmpty() ? QString() : value(list.at(0).toMap(), name);
+  } else {
+    return QString();
+  }
+}
+
+// Be lazy. Use static hash for certain field names for now.
+// Don't expect IGDB values to change. This avoids exponentially multiplying the \
number of API calls +void IGDBFetcher::populateHashes() {
+  s_genreHash.insert(2,  QLatin1String("Point-and-click"));
+  s_genreHash.insert(4,  QLatin1String("Fighting"));
+  s_genreHash.insert(5,  QLatin1String("Shooter"));
+  s_genreHash.insert(7,  QLatin1String("Music"));
+  s_genreHash.insert(8,  QLatin1String("Platform"));
+  s_genreHash.insert(9,  QLatin1String("Puzzle"));
+  s_genreHash.insert(10, QLatin1String("Racing"));
+  s_genreHash.insert(11, QLatin1String("Real Time Strategy (RTS)"));
+  s_genreHash.insert(12, QLatin1String("Role-playing (RPG)"));
+  s_genreHash.insert(13, QLatin1String("Simulator"));
+  s_genreHash.insert(14, QLatin1String("Sport"));
+  s_genreHash.insert(15, QLatin1String("Strategy"));
+  s_genreHash.insert(16, QLatin1String("Turn-based strategy (TBS)"));
+  s_genreHash.insert(24, QLatin1String("Tactical"));
+  s_genreHash.insert(25, QLatin1String("Hack and slash/Beat 'em up"));
+  s_genreHash.insert(26, QLatin1String("Quiz/Trivia"));
+  s_genreHash.insert(30, QLatin1String("Pinball"));
+  s_genreHash.insert(31, QLatin1String("Adventure"));
+  s_genreHash.insert(32, QLatin1String("Indie"));
+  s_genreHash.insert(33, QLatin1String("Arcade"));
+
+  s_platformHash.insert(3, QLatin1String("Linux"));
+  s_platformHash.insert(4, QLatin1String("Nintendo 64"));
+  s_platformHash.insert(5, QLatin1String("Wii"));
+  s_platformHash.insert(6, QLatin1String("PC (Microsoft Windows)"));
+  s_platformHash.insert(7, QLatin1String("PlayStation"));
+  s_platformHash.insert(8, QLatin1String("PlayStation 2"));
+  s_platformHash.insert(9, QLatin1String("PlayStation 3"));
+  s_platformHash.insert(11, QLatin1String("Xbox"));
+  s_platformHash.insert(12, QLatin1String("Xbox 360"));
+  s_platformHash.insert(13, QLatin1String("PC DOS"));
+  s_platformHash.insert(14, QLatin1String("Mac"));
+  s_platformHash.insert(15, QLatin1String("Commodore C64/128"));
+  s_platformHash.insert(16, QLatin1String("Amiga"));
+  s_platformHash.insert(18, QLatin1String("Nintendo Entertainment System (NES)"));
+  s_platformHash.insert(19, QLatin1String("Super Nintendo Entertainment System \
(SNES)")); +  s_platformHash.insert(20, QLatin1String("Nintendo DS"));
+  s_platformHash.insert(21, QLatin1String("Nintendo GameCube"));
+  s_platformHash.insert(22, QLatin1String("Game Boy Color"));
+  s_platformHash.insert(23, QLatin1String("Dreamcast"));
+  s_platformHash.insert(24, QLatin1String("Game Boy Advance"));
+  s_platformHash.insert(25, QLatin1String("Amstrad CPC"));
+  s_platformHash.insert(26, QLatin1String("ZX Spectrum"));
+  s_platformHash.insert(27, QLatin1String("MSX"));
+  s_platformHash.insert(29, QLatin1String("Sega Mega Drive/Genesis"));
+  s_platformHash.insert(30, QLatin1String("Sega 32X"));
+  s_platformHash.insert(32, QLatin1String("Sega Saturn"));
+  s_platformHash.insert(33, QLatin1String("Game Boy"));
+  s_platformHash.insert(34, QLatin1String("Android"));
+  s_platformHash.insert(35, QLatin1String("Sega Game Gear"));
+  s_platformHash.insert(36, QLatin1String("Xbox Live Arcade"));
+  s_platformHash.insert(37, QLatin1String("Nintendo 3DS"));
+  s_platformHash.insert(38, QLatin1String("PlayStation Portable"));
+  s_platformHash.insert(39, QLatin1String("iOS"));
+  s_platformHash.insert(41, QLatin1String("Wii U"));
+  s_platformHash.insert(42, QLatin1String("N-Gage"));
+  s_platformHash.insert(44, QLatin1String("Tapwave Zodiac"));
+  s_platformHash.insert(45, QLatin1String("PlayStation Network"));
+  s_platformHash.insert(46, QLatin1String("PlayStation Vita"));
+  s_platformHash.insert(47, QLatin1String("Virtual Console (Nintendo)"));
+  s_platformHash.insert(48, QLatin1String("PlayStation 4"));
+  s_platformHash.insert(49, QLatin1String("Xbox One"));
+  s_platformHash.insert(50, QLatin1String("3DO Interactive Multiplayer"));
+  s_platformHash.insert(51, QLatin1String("Family Computer Disk System"));
+  s_platformHash.insert(52, QLatin1String("Arcade"));
+  s_platformHash.insert(53, QLatin1String("MSX2"));
+  s_platformHash.insert(55, QLatin1String("Mobile"));
+  s_platformHash.insert(56, QLatin1String("WiiWare"));
+  s_platformHash.insert(57, QLatin1String("WonderSwan"));
+  s_platformHash.insert(58, QLatin1String("Super Famicom"));
+  s_platformHash.insert(59, QLatin1String("Atari 2600"));
+  s_platformHash.insert(60, QLatin1String("Atari 7800"));
+  s_platformHash.insert(61, QLatin1String("Atari Lynx"));
+  s_platformHash.insert(62, QLatin1String("Atari Jaguar"));
+  s_platformHash.insert(63, QLatin1String("Atari ST/STE"));
+  s_platformHash.insert(64, QLatin1String("Sega Master System"));
+  s_platformHash.insert(65, QLatin1String("Atari 8-bit"));
+  s_platformHash.insert(66, QLatin1String("Atari 5200"));
+  s_platformHash.insert(67, QLatin1String("Intellivision"));
+  s_platformHash.insert(68, QLatin1String("ColecoVision"));
+  s_platformHash.insert(69, QLatin1String("BBC Microcomputer System"));
+  s_platformHash.insert(70, QLatin1String("Vectrex"));
+  s_platformHash.insert(71, QLatin1String("Commodore VIC-20"));
+  s_platformHash.insert(72, QLatin1String("Ouya"));
+  s_platformHash.insert(73, QLatin1String("BlackBerry OS"));
+  s_platformHash.insert(74, QLatin1String("Windows Phone"));
+  s_platformHash.insert(75, QLatin1String("Apple II"));
+  s_platformHash.insert(77, QLatin1String("Sharp X1"));
+  s_platformHash.insert(78, QLatin1String("Sega CD"));
+  s_platformHash.insert(79, QLatin1String("Neo Geo MVS"));
+  s_platformHash.insert(80, QLatin1String("Neo Geo AES"));
+  s_platformHash.insert(82, QLatin1String("Web browser"));
+  s_platformHash.insert(84, QLatin1String("SG-1000"));
+  s_platformHash.insert(85, QLatin1String("Donner Model 30"));
+  s_platformHash.insert(86, QLatin1String("TurboGrafx-16/PC Engine"));
+  s_platformHash.insert(87, QLatin1String("Virtual Boy"));
+  s_platformHash.insert(88, QLatin1String("Odyssey"));
+  s_platformHash.insert(89, QLatin1String("Microvision"));
+  s_platformHash.insert(90, QLatin1String("Commodore PET"));
+  s_platformHash.insert(91, QLatin1String("Bally Astrocade"));
+  s_platformHash.insert(92, QLatin1String("SteamOS"));
+  s_platformHash.insert(93, QLatin1String("Commodore 16"));
+  s_platformHash.insert(94, QLatin1String("Commodore Plus/4"));
+  s_platformHash.insert(95, QLatin1String("PDP-1"));
+  s_platformHash.insert(96, QLatin1String("PDP-10"));
+  s_platformHash.insert(97, QLatin1String("PDP-8"));
+  s_platformHash.insert(98, QLatin1String("DEC GT40"));
+  s_platformHash.insert(99, QLatin1String("Family Computer"));
+  s_platformHash.insert(100, QLatin1String("Analogue electronics"));
+  s_platformHash.insert(101, QLatin1String("Ferranti Nimrod Computer"));
+  s_platformHash.insert(102, QLatin1String("EDSAC"));
+  s_platformHash.insert(103, QLatin1String("PDP-7"));
+  s_platformHash.insert(104, QLatin1String("HP 2100"));
+  s_platformHash.insert(105, QLatin1String("HP 3000"));
+  s_platformHash.insert(106, QLatin1String("SDS Sigma 7"));
+  s_platformHash.insert(107, QLatin1String("Call-A-Computer time-shared mainframe \
computer system")); +  s_platformHash.insert(108, QLatin1String("PDP-11"));
+  s_platformHash.insert(109, QLatin1String("CDC Cyber 70"));
+  s_platformHash.insert(110, QLatin1String("PLATO"));
+  s_platformHash.insert(111, QLatin1String("Imlac PDS-1"));
+  s_platformHash.insert(112, QLatin1String("Microcomputer"));
+  s_platformHash.insert(113, QLatin1String("OnLive Game System"));
+  s_platformHash.insert(114, QLatin1String("Amiga CD32"));
+  s_platformHash.insert(115, QLatin1String("Apple IIGS"));
+  s_platformHash.insert(116, QLatin1String("Acorn Archimedes"));
+  s_platformHash.insert(117, QLatin1String("Philips CD-i"));
+  s_platformHash.insert(118, QLatin1String("FM Towns"));
+  s_platformHash.insert(119, QLatin1String("Neo Geo Pocket"));
+  s_platformHash.insert(120, QLatin1String("Neo Geo Pocket Color"));
+  s_platformHash.insert(121, QLatin1String("Sharp X68000"));
+  s_platformHash.insert(122, QLatin1String("Nuon"));
+  s_platformHash.insert(123, QLatin1String("WonderSwan Color"));
+  s_platformHash.insert(124, QLatin1String("SwanCrystal"));
+  s_platformHash.insert(125, QLatin1String("PC-8801"));
+  s_platformHash.insert(126, QLatin1String("TRS-80"));
+  s_platformHash.insert(127, QLatin1String("Fairchild Channel F"));
+  s_platformHash.insert(128, QLatin1String("PC Engine SuperGrafx"));
+  s_platformHash.insert(129, QLatin1String("Texas Instruments TI-99"));
+  s_platformHash.insert(130, QLatin1String("Nintendo Switch"));
+  s_platformHash.insert(131, QLatin1String("Nintendo PlayStation"));
+  s_platformHash.insert(132, QLatin1String("Amazon Fire TV"));
+  s_platformHash.insert(133, QLatin1String("Philips Videopac G7000"));
+  s_platformHash.insert(134, QLatin1String("Acorn Electron"));
+  s_platformHash.insert(135, QLatin1String("Hyper Neo Geo 64"));
+  s_platformHash.insert(136, QLatin1String("Neo Geo CD"));
+
+  // cheat by grabbing i18n values from default collection
+  Data::CollPtr c(new Data::GameCollection(true));
+  QStringList esrb = c->fieldByName(QLatin1String("certification"))->allowed();
+  Q_ASSERT(esrb.size() == 8);
+  while(esrb.size() < 8) {
+    esrb << QString();
+  }
+  s_esrbHash.insert(QLatin1String("1"), esrb.at(7));
+  s_esrbHash.insert(QLatin1String("2"), esrb.at(6));
+  s_esrbHash.insert(QLatin1String("3"), esrb.at(5));
+  s_esrbHash.insert(QLatin1String("4"), esrb.at(4));
+  s_esrbHash.insert(QLatin1String("5"), esrb.at(3));
+  s_esrbHash.insert(QLatin1String("6"), esrb.at(2));
+  s_esrbHash.insert(QLatin1String("7"), esrb.at(1));
+
+  s_pegiHash.insert(QLatin1String("1"), QLatin1String("PEGI 3"));
+  s_pegiHash.insert(QLatin1String("2"), QLatin1String("PEGI 7"));
+  s_pegiHash.insert(QLatin1String("3"), QLatin1String("PEGI 12"));
+  s_pegiHash.insert(QLatin1String("4"), QLatin1String("PEGI 16"));
+  s_pegiHash.insert(QLatin1String("5"), QLatin1String("PEGI 18"));
+}
+
+QPointer<KIO::StoredTransferJob> IGDBFetcher::igdbJob(const QUrl& url_, const \
QString& apiKey_) { +  QPointer<KIO::StoredTransferJob> job = KIO::storedGet(url_, \
KIO::NoReload, KIO::HideProgressInfo); +  \
job->addMetaData(QLatin1String("customHTTPHeader"), QLatin1String("X-Mashape-Key: ") \
+ apiKey_); +  job->addMetaData(QLatin1String("accept"), \
QLatin1String("application/json")); +  KJobWidgets::setWindow(job, \
GUI::Proxy::widget()); +  return job;
+}
diff --git a/src/fetch/igdbfetcher.h b/src/fetch/igdbfetcher.h
new file mode 100644
index 00000000..c68aaade
--- /dev/null
+++ b/src/fetch/igdbfetcher.h
@@ -0,0 +1,126 @@
+/***************************************************************************
+    Copyright (C) 2017 Robby Stephenson <robby@periapsis.org>
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or         *
+ *   modify it under the terms of the GNU General Public License as        *
+ *   published by the Free Software Foundation; either version 2 of        *
+ *   the License or (at your option) version 3 or any later version        *
+ *   accepted by the membership of KDE e.V. (or its successor approved     *
+ *   by the membership of KDE e.V.), which shall act as a proxy            *
+ *   defined in Section 14 of version 3 of the license.                    *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifndef TELLICO_IGDBFETCHER_H
+#define TELLICO_IGDBFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <QLineEdit>
+#include <QPointer>
+#include <QDate>
+
+class KJob;
+namespace KIO {
+  class StoredTransferJob;
+}
+
+namespace Tellico {
+  namespace Fetch {
+
+/**
+ * A fetcher for igdb.com
+ *
+ * @author Robby Stephenson
+ */
+class IGDBFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+  /**
+   */
+  IGDBFetcher(QObject* parent);
+  /**
+   */
+  virtual ~IGDBFetcher();
+
+  /**
+   */
+  virtual QString source() const Q_DECL_OVERRIDE;
+  virtual QString attribution() const Q_DECL_OVERRIDE;
+  virtual bool isSearching() const Q_DECL_OVERRIDE { return m_started; }
+  virtual bool canSearch(FetchKey k) const Q_DECL_OVERRIDE;
+  virtual void stop() Q_DECL_OVERRIDE;
+  virtual Data::EntryPtr fetchEntryHook(uint uid) Q_DECL_OVERRIDE;
+  virtual Type type() const Q_DECL_OVERRIDE { return IGDB; }
+  virtual bool canFetch(int type) const Q_DECL_OVERRIDE;
+  virtual void readConfigHook(const KConfigGroup& config) Q_DECL_OVERRIDE;
+  virtual void continueSearch() Q_DECL_OVERRIDE;
+
+  /**
+   * Returns a widget for modifying the fetcher's config.
+   */
+  virtual Fetch::ConfigWidget* configWidget(QWidget* parent) const Q_DECL_OVERRIDE;
+
+  class ConfigWidget;
+  friend class ConfigWidget;
+
+  static QString defaultName();
+  static QString defaultIcon();
+  static StringHash allOptionalFields();
+
+private Q_SLOTS:
+  void slotComplete(KJob* job);
+
+private:
+  virtual void search() Q_DECL_OVERRIDE;
+  virtual FetchRequest updateRequest(Data::EntryPtr entry) Q_DECL_OVERRIDE;
+  void populateEntry(Data::EntryPtr entry, const QVariantMap& resultMap);
+  QString companyName(const QString& companyId) const;
+
+  static QString value(const QVariantMap& map, const char* name);
+  static QString value(const QVariantMap& map, const char* object, const char* \
name); +  static void populateHashes();
+  static QPointer<KIO::StoredTransferJob> igdbJob(const QUrl& url, const QString& \
apiKey); +
+  bool m_started;
+
+  QString m_apiKey;
+  QHash<int, Data::EntryPtr> m_entries;
+  QPointer<KIO::StoredTransferJob> m_job;
+
+  static QHash<int, QString> s_genreHash;
+  static QHash<int, QString> s_platformHash;
+  static QHash<QString, QString> s_companyHash;
+  static QHash<QString, QString> s_esrbHash;
+  static QHash<QString, QString> s_pegiHash;
+};
+
+class IGDBFetcher::ConfigWidget : public Fetch::ConfigWidget {
+Q_OBJECT
+
+public:
+  explicit ConfigWidget(QWidget* parent_, const IGDBFetcher* fetcher = nullptr);
+  virtual void saveConfigHook(KConfigGroup&) Q_DECL_OVERRIDE;
+  virtual QString preferredName() const Q_DECL_OVERRIDE;
+
+private:
+  QLineEdit* m_apiKeyEdit;
+};
+
+  } // end namespace
+} // end namespace
+#endif
diff --git a/src/tests/igdbfetchertest.cpp b/src/tests/igdbfetchertest.cpp
new file mode 100644
index 00000000..5290dbab
--- /dev/null
+++ b/src/tests/igdbfetchertest.cpp
@@ -0,0 +1,79 @@
+/***************************************************************************
+    Copyright (C) 2017 Robby Stephenson <robby@periapsis.org>
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or         *
+ *   modify it under the terms of the GNU General Public License as        *
+ *   published by the Free Software Foundation; either version 2 of        *
+ *   the License or (at your option) version 3 or any later version        *
+ *   accepted by the membership of KDE e.V. (or its successor approved     *
+ *   by the membership of KDE e.V.), which shall act as a proxy            *
+ *   defined in Section 14 of version 3 of the license.                    *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
+ *                                                                         *
+ ***************************************************************************/
+
+#undef QT_NO_CAST_FROM_ASCII
+
+#include "igdbfetchertest.h"
+
+#include "../fetch/igdbfetcher.h"
+#include "../collections/gamecollection.h"
+#include "../entry.h"
+#include "../images/imagefactory.h"
+
+#include <KConfig>
+#include <KConfigGroup>
+
+#include <QTest>
+
+QTEST_GUILESS_MAIN( IGDBFetcherTest )
+
+IGDBFetcherTest::IGDBFetcherTest() : AbstractFetcherTest() {
+}
+
+void IGDBFetcherTest::initTestCase() {
+  Tellico::ImageFactory::init();
+}
+
+void IGDBFetcherTest::testKeyword() {
+  KConfig config(QFINDTESTDATA("tellicotest.config"), KConfig::SimpleConfig);
+  QString groupName = QLatin1String("igdb");
+  if(!config.hasGroup(groupName)) {
+    QSKIP("This test requires a config file.", SkipAll);
+  }
+  KConfigGroup cg(&config, groupName);
+
+  Tellico::Fetch::FetchRequest request(Tellico::Data::Collection::Game, \
Tellico::Fetch::Keyword, +                                       QLatin1String("Zelda \
Twilight Princess Wii")); +  Tellico::Fetch::Fetcher::Ptr fetcher(new \
Tellico::Fetch::IGDBFetcher(this)); +  fetcher->readConfig(cg, cg.name());
+
+  Tellico::Data::EntryList results = DO_FETCH1(fetcher, request, 1);
+
+  QCOMPARE(results.size(), 1);
+
+  Tellico::Data::EntryPtr entry = results.at(0);
+  QVERIFY(entry);
+  QCOMPARE(entry->field("title"), QLatin1String("The Legend of Zelda: Twilight \
Princess")); +  QCOMPARE(entry->field("year"), QLatin1String("2006"));
+  QCOMPARE(entry->field("platform"), QLatin1String("Nintendo Wii"));
+  QCOMPARE(entry->field("certification"), QLatin1String("Teen"));
+  QCOMPARE(entry->field("pegi"), QLatin1String("PEGI 12"));
+  QCOMPARE(entry->field("genre"), QLatin1String("Adventure"));
+  QCOMPARE(entry->field("publisher"), QLatin1String("Nintendo"));
+  QCOMPARE(entry->field("developer"), QLatin1String("Nintendo EAD Group No. 3"));
+  QCOMPARE(entry->field("igdb"), \
QLatin1String("https://www.igdb.com/games/the-legend-of-zelda-twilight-princess")); + \
QVERIFY(!entry->field(QLatin1String("description")).isEmpty()); +  \
QVERIFY(!entry->field(QLatin1String("cover")).isEmpty()); +  \
QVERIFY(!entry->field(QLatin1String("cover")).startsWith('/')); +}
diff --git a/src/tests/igdbfetchertest.h b/src/tests/igdbfetchertest.h
new file mode 100644
index 00000000..1417c38c
--- /dev/null
+++ b/src/tests/igdbfetchertest.h
@@ -0,0 +1,40 @@
+/***************************************************************************
+    Copyright (C) 2017 Robby Stephenson <robby@periapsis.org>
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or         *
+ *   modify it under the terms of the GNU General Public License as        *
+ *   published by the Free Software Foundation; either version 2 of        *
+ *   the License or (at your option) version 3 or any later version        *
+ *   accepted by the membership of KDE e.V. (or its successor approved     *
+ *   by the membership of KDE e.V.), which shall act as a proxy            *
+ *   defined in Section 14 of version 3 of the license.                    *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifndef IGDBFETCHERTEST_H
+#define IGDBFETCHERTEST_H
+
+#include "abstractfetchertest.h"
+
+class IGDBFetcherTest : public AbstractFetcherTest {
+Q_OBJECT
+public:
+  IGDBFetcherTest();
+
+private Q_SLOTS:
+  void initTestCase();
+  void testKeyword();
+};
+
+#endif


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic