[office/tellico] /: Add Numista data source
Robby Stephenson
null at kde.org
Sun Sep 20 01:53:27 BST 2020
Git commit cd21106a0da2b222656ef2ff95945a3808432c37 by Robby Stephenson.
Committed on 20/09/2020 at 00:52.
Pushed by rstephenson into branch 'master'.
Add Numista data source
M +4 -0 ChangeLog
M +9 -0 doc/configuration.docbook
M +1 -0 src/fetch/CMakeLists.txt
M +2 -1 src/fetch/fetch.h
M +3 -1 src/fetch/fetcherinitializer.cpp
M +1 -0 src/fetch/fetchmanager.cpp
A +466 -0 src/fetch/numistafetcher.cpp [License: GPL (v2/3)]
A +131 -0 src/fetch/numistafetcher.h [License: GPL (v2/3)]
M +8 -0 src/tests/CMakeLists.txt
A +98 -0 src/tests/numistafetchertest.cpp [License: GPL (v2/3)]
C +12 -84 src/tests/numistafetchertest.h [from: src/fetch/fetch.h - 057% similarity]
M +3 -0 src/tests/tellicotest.config
https://invent.kde.org/office/tellico/commit/cd21106a0da2b222656ef2ff95945a3808432c37
diff --git a/ChangeLog b/ChangeLog
index 3c969582..5ef04b38 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2020-09-17 Robby Stephenson <robby at periapsis.org>
+
+ * Added data source for Numista.com.
+
2020-09-16 Robby Stephenson <robby at periapsis.org>
* Enabled general keyword search for MusicBrainz data source (Bug 426560).
diff --git a/doc/configuration.docbook b/doc/configuration.docbook
index 08ad65fe..800d38d2 100644
--- a/doc/configuration.docbook
+++ b/doc/configuration.docbook
@@ -189,6 +189,7 @@ while the full list is <ulink url="https://tellico-project.org/data-sources">ava
<listitem><simpara><link linkend="sru">SRU servers</link>,</simpara></listitem>
<!-- coins -->
<listitem><simpara><link linkend="colnect">Colnect</link>,</simpara></listitem>
+<listitem><simpara><link linkend="numista">Numista</link>,</simpara></listitem>
<!-- others -->
<listitem><simpara><link linkend="externalexec">other external scripts or applications</link>, and</simpara></listitem>
<listitem><simpara><link linkend="multiple-sources">combinations of any of the above sources</link>.</simpara></listitem>
@@ -465,6 +466,14 @@ The <ulink url="http://www.imdb.com">Internet Movie Database</ulink> provides in
</para>
</sect3>
+<sect3 id="numista">
+<title>Numista</title>
+<para>
+<ulink url="https://numista.com">Numista</ulink> is a world coin catalog which grows thanks to member contributions, offering
+online collection management, tools to easily exchange with other collectors, and a forum.
+</para>
+</sect3>
+
</sect2>
<sect2 id="variety-type-sources">
diff --git a/src/fetch/CMakeLists.txt b/src/fetch/CMakeLists.txt
index bf04e0bf..9f34e0ab 100644
--- a/src/fetch/CMakeLists.txt
+++ b/src/fetch/CMakeLists.txt
@@ -50,6 +50,7 @@ SET(fetch_STAT_SRCS
mrlookupfetcher.cpp
multifetcher.cpp
musicbrainzfetcher.cpp
+ numistafetcher.cpp
omdbfetcher.cpp
openlibraryfetcher.cpp
springerfetcher.cpp
diff --git a/src/fetch/fetch.h b/src/fetch/fetch.h
index 22d0878b..9d6abe05 100644
--- a/src/fetch/fetch.h
+++ b/src/fetch/fetch.h
@@ -104,7 +104,8 @@ enum Type {
MobyGames,
ComicVine,
KinoTeatr,
- Colnect
+ Colnect,
+ Numista
};
}
diff --git a/src/fetch/fetcherinitializer.cpp b/src/fetch/fetcherinitializer.cpp
index 71ae2533..e902f862 100644
--- a/src/fetch/fetcherinitializer.cpp
+++ b/src/fetch/fetcherinitializer.cpp
@@ -1,5 +1,5 @@
/***************************************************************************
- Copyright (C) 2009-2011 Robby Stephenson <robby at periapsis.org>
+ Copyright (C) 2009-2020 Robby Stephenson <robby at periapsis.org>
***************************************************************************/
/***************************************************************************
@@ -72,6 +72,7 @@
#include "comicvinefetcher.h"
#include "kinoteatrfetcher.h"
#include "colnectfetcher.h"
+#include "numistafetcher.h"
/**
* Ideally, I'd like these initializations to be in each cpp file for each collection type
@@ -125,6 +126,7 @@ Tellico::Fetch::FetcherInitializer::FetcherInitializer() {
RegisterFetcher<Fetch::ComicVineFetcher> registerComicVine(ComicVine);
RegisterFetcher<Fetch::KinoTeatrFetcher> registerTeatr(KinoTeatr);
RegisterFetcher<Fetch::ColnectFetcher> registerColnect(Colnect);
+ RegisterFetcher<Fetch::NumistaFetcher> registerNumista(Numista);
Fetch::Manager::self()->loadFetchers();
}
diff --git a/src/fetch/fetchmanager.cpp b/src/fetch/fetchmanager.cpp
index a6895c7a..eb61e43d 100644
--- a/src/fetch/fetchmanager.cpp
+++ b/src/fetch/fetchmanager.cpp
@@ -324,6 +324,7 @@ Tellico::Fetch::FetcherVec Manager::defaultFetchers() {
FETCHER_ADD(IMDB);
// coins and stamps
FETCHER_ADD(Colnect);
+ FETCHER_ADD(Numista);
QStringList langs = QLocale().uiLanguages();
if(langs.first().contains(QLatin1Char('-'))) {
// I'm not sure QT always include two-letter locale codes
diff --git a/src/fetch/numistafetcher.cpp b/src/fetch/numistafetcher.cpp
new file mode 100644
index 00000000..af27fdb3
--- /dev/null
+++ b/src/fetch/numistafetcher.cpp
@@ -0,0 +1,466 @@
+/***************************************************************************
+ Copyright (C) 2020 Robby Stephenson <robby at 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 "numistafetcher.h"
+#include "../collections/coincollection.h"
+#include "../entry.h"
+#include "../images/imagefactory.h"
+#include "../gui/combobox.h"
+#include "../utils/guiproxy.h"
+#include "../utils/string_utils.h"
+#include "../tellico_debug.h"
+
+#include <KLocalizedString>
+#include <KIO/Job>
+#include <KJobUiDelegate>
+#include <KJobWidgets/KJobWidgets>
+#include <KConfigGroup>
+
+#include <QLabel>
+#include <QLineEdit>
+#include <QFile>
+#include <QTextStream>
+#include <QGridLayout>
+#include <QUrlQuery>
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QJsonObject>
+#include <QStandardPaths>
+
+namespace {
+ static const int NUMISTA_MAX_RETURNS_TOTAL = 20;
+ static const char* NUMISTA_API_URL = "https://api.numista.com/api/v1";
+ static const char* NUMISTA_MAGIC_TOKEN = "2e19b8f32c5e8fbd96aeb2c0590d70458ef81d5b0657b1f6741685e1f9cf7a0983d7d0e0a2c69bcca7cfb4c08fde1c5a562e083e2d44a492a5e4b9c3d2a42a7c536a99f8511bfdbca9fb6d29f587fbbf";
+}
+
+using namespace Tellico;
+using Tellico::Fetch::NumistaFetcher;
+
+NumistaFetcher::NumistaFetcher(QObject* parent_)
+ : Fetcher(parent_)
+ , m_limit(NUMISTA_MAX_RETURNS_TOTAL)
+ , m_total(-1)
+ , m_page(1)
+ , m_job(nullptr)
+ , m_locale(QStringLiteral("en"))
+ , m_started(false) {
+}
+
+NumistaFetcher::~NumistaFetcher() {
+}
+
+QString NumistaFetcher::source() const {
+ return m_name.isEmpty() ? defaultName() : m_name;
+}
+
+bool NumistaFetcher::canFetch(int type) const {
+ return type == Data::Collection::Coin;
+}
+
+void NumistaFetcher::readConfigHook(const KConfigGroup& config_) {
+ QString k = config_.readEntry("API Key");
+ if(!k.isEmpty()) {
+ m_apiKey = k;
+ }
+ k = config_.readEntry("Locale", "en");
+ if(!k.isEmpty()) {
+ m_locale = k.toLower();
+ }
+}
+
+void NumistaFetcher::setLimit(int limit_) {
+ m_limit = qBound(1, limit_, NUMISTA_MAX_RETURNS_TOTAL);
+}
+
+void NumistaFetcher::search() {
+ m_started = true;
+ m_total = -1;
+ m_page = 1;
+ m_year.clear();
+ doSearch();
+}
+
+void NumistaFetcher::continueSearch() {
+ m_started = true;
+ doSearch();
+}
+
+void NumistaFetcher::doSearch() {
+ QUrl u(QString::fromLatin1(NUMISTA_API_URL));
+ // all searches are for coins
+ u.setPath(u.path() + QStringLiteral("/coins"));
+
+ if(m_apiKey.isEmpty()) {
+ m_apiKey = Tellico::reverseObfuscate(NUMISTA_MAGIC_TOKEN);
+ }
+
+ // pull out year, keep the regexp a little loose
+ QRegularExpression yearRX(QStringLiteral("[0-9]{4}"));
+ QRegularExpressionMatch match = yearRX.match(request().value);
+ if(match.hasMatch()) {
+ m_year = match.captured(0);
+ }
+
+ QString queryString;
+ switch(request().key) {
+ case Keyword:
+ queryString = request().value;
+ break;
+
+ default:
+ myWarning() << "key not recognized: " << request().key;
+ stop();
+ return;
+ }
+ QUrlQuery q;
+ q.addQueryItem(QStringLiteral("q"), queryString);
+ q.addQueryItem(QStringLiteral("count"), QString::number(m_limit));
+ q.addQueryItem(QStringLiteral("page"), QString::number(m_page));
+ q.addQueryItem(QStringLiteral("lang"), m_locale);
+ u.setQuery(q);
+// myDebug() << "url: " << u.url();
+
+ m_job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
+ m_job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Numista-API-Key: ") + m_apiKey);
+ KJobWidgets::setWindow(m_job, GUI::Proxy::widget());
+ connect(m_job.data(), &KJob::result,
+ this, &NumistaFetcher::slotComplete);
+}
+
+void NumistaFetcher::stop() {
+ if(!m_started) {
+ return;
+ }
+ if(m_job) {
+ m_job->kill();
+ m_job = nullptr;
+ }
+ m_started = false;
+ emit signalDone(this);
+}
+
+void NumistaFetcher::slotComplete(KJob* ) {
+ if(m_job->error()) {
+ m_job->uiDelegate()->showErrorMessage();
+ stop();
+ return;
+ }
+
+ QByteArray data = m_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 numistafetcher.cpp";
+ QFile f(QStringLiteral("/tmp/test.json"));
+ if(f.open(QIODevice::WriteOnly)) {
+ QTextStream t(&f);
+ t.setCodec("UTF-8");
+ t << data;
+ }
+ f.close();
+#endif
+
+ QJsonParseError jsonError;
+ QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
+ if(doc.isNull()) {
+ myDebug() << "null JSON document:" << jsonError.errorString();
+ message(jsonError.errorString(), MessageHandler::Error);
+ stop();
+ return;
+ }
+ QJsonObject obj = doc.object();
+
+ // check for error
+ if(obj.contains(QStringLiteral("error"))) {
+ const QString msg = obj.value(QStringLiteral("error")).toString();
+ message(msg, MessageHandler::Error);
+ myDebug() << "NunmistaFetcher -" << msg;
+ stop();
+ return;
+ }
+
+ m_total = obj.value(QLatin1String("count")).toString().toInt();
+
+ int count = 0;
+ QJsonArray results = obj.value(QLatin1String("coins")).toArray();
+ for(QJsonArray::const_iterator i = results.constBegin(); i != results.constEnd(); ++i) {
+ if(count >= m_limit) {
+ break;
+ }
+ QJsonObject result = (*i).toObject();
+
+ QString desc = result.value(QLatin1String("issuer")).toObject()
+ .value(QLatin1String("name")).toString();
+ desc += QLatin1Char('/');
+ const QString minYear = result.value(QLatin1String("minYear")).toString();
+ if(!minYear.isEmpty()) {
+ desc += minYear + QLatin1Char('-') + result.value(QLatin1String("maxYear")).toString();
+ }
+ FetchResult* r = new FetchResult(Fetcher::Ptr(this),
+ result.value(QLatin1String("title")).toString(),
+ desc);
+ m_matches.insert(r->uid, result.value(QLatin1String("id")).toInt());
+ emit signalResultFound(r);
+ ++count;
+ }
+
+ stop(); // required
+}
+
+Tellico::Data::EntryPtr NumistaFetcher::fetchEntryHook(uint uid_) {
+ Data::EntryPtr entry = m_entries.value(uid_);
+ if(entry) {
+ return entry;
+ }
+
+ if(!m_matches.contains(uid_)) {
+ myWarning() << "no matching coin id";
+ return Data::EntryPtr();
+ }
+
+ QUrl url(QString::fromLatin1(NUMISTA_API_URL));
+ url.setPath(url.path() + QLatin1String("/coins/") + QString::number(m_matches[uid_]));
+// myDebug() << url.url();
+ QPointer<KIO::StoredTransferJob> job = KIO::storedGet(url, KIO::NoReload, KIO::HideProgressInfo);
+ job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Numista-API-Key: ") + m_apiKey);
+ KJobWidgets::setWindow(job, GUI::Proxy::widget());
+ if(!job->exec()) {
+ myDebug() << job->errorString() << url;
+ return Data::EntryPtr();
+ }
+ const QByteArray data = job->data();
+ if(data.isEmpty()) {
+ myDebug() << "no data for" << url;
+ return Data::EntryPtr();
+ }
+#if 0
+ myWarning() << "Remove debug2 from numistafetcher.cpp";
+ QFile f(QStringLiteral("/tmp/test2-numista.json"));
+ if(f.open(QIODevice::WriteOnly)) {
+ QTextStream t(&f);
+ t.setCodec("UTF-8");
+ t << data;
+ }
+ f.close();
+#endif
+
+ entry = parseEntry(data);
+ if(!entry) {
+ myDebug() << "No discernible entry data";
+ return Data::EntryPtr();
+ }
+
+ QString image = entry->field(QStringLiteral("obverse"));
+ if(!image.isEmpty() && optionalFields().contains(QStringLiteral("obverse"))) {
+ const QString id = ImageFactory::addImage(QUrl::fromUserInput(image), true /* quiet */);
+ if(id.isEmpty()) {
+ message(i18n("The cover image could not be loaded."), MessageHandler::Warning);
+ }
+ entry->setField(QStringLiteral("obverse"), id);
+ }
+ image = entry->field(QStringLiteral("reverse"));
+ if(!image.isEmpty() && optionalFields().contains(QStringLiteral("reverse"))) {
+ const QString id = ImageFactory::addImage(QUrl::fromUserInput(image), true /* quiet */);
+ if(id.isEmpty()) {
+ message(i18n("The cover image could not be loaded."), MessageHandler::Warning);
+ }
+ entry->setField(QStringLiteral("reverse"), id);
+ }
+
+ return entry;
+}
+
+Tellico::Data::EntryPtr NumistaFetcher::parseEntry(const QByteArray& data_) {
+ QJsonParseError parseError;
+ QJsonDocument doc = QJsonDocument::fromJson(data_, &parseError);
+ if(doc.isNull()) {
+ myDebug() << "Bad json data:" << parseError.errorString();
+ return Data::EntryPtr();
+ }
+
+ Data::CollPtr coll(new Data::CoinCollection(true));
+ Data::EntryPtr entry(new Data::Entry(coll));
+ coll->addEntries(entry);
+
+ QVariantMap objectMap = doc.object().toVariantMap();
+ // for type, try to tease out from title
+ // use ruler name as a possible fallback
+ QRegularExpression titleQuote(QStringLiteral(""(.+)""));
+ QRegularExpressionMatch quoteMatch = titleQuote.match(mapValue(objectMap, "title"));
+ if(quoteMatch.hasMatch()) {
+ entry->setField(QStringLiteral("type"), quoteMatch.captured(1));
+ } else {
+ entry->setField(QStringLiteral("type"), mapValue(objectMap, "ruler", "name"));
+ }
+
+ entry->setField(QStringLiteral("denomination"), mapValue(objectMap, "value", "text"));
+ entry->setField(QStringLiteral("currency"), mapValue(objectMap, "value", "currency", "name"));
+ entry->setField(QStringLiteral("country"), mapValue(objectMap, "issuer", "name"));
+ entry->setField(QStringLiteral("mintmark"), mapValue(objectMap, "mintLetter"));
+
+ // if minyear = maxyear, then set the year of the coin
+ auto year = objectMap.value(QLatin1String("minYear"));
+ if(year == objectMap.value(QLatin1String("maxYear"))) {
+ entry->setField(QStringLiteral("year"), year.toString());
+ } else if(!m_year.isEmpty()) {
+ entry->setField(QStringLiteral("year"), m_year);
+ }
+
+ entry->setField(QStringLiteral("obverse"), mapValue(objectMap, "obverse", "picture"));
+ entry->setField(QStringLiteral("reverse"), mapValue(objectMap, "reverse", "picture"));
+
+ const QString numista(QStringLiteral("numista"));
+ if(optionalFields().contains(numista)) {
+ Data::FieldPtr field(new Data::Field(numista, i18n("Numista Link"), Data::Field::URL));
+ field->setCategory(i18n("General"));
+ coll->addField(field);
+ entry->setField(numista, mapValue(objectMap, "url"));
+ }
+
+ const QString desc(QStringLiteral("description"));
+ if(!coll->hasField(desc) && optionalFields().contains(desc)) {
+ Data::FieldPtr field(new Data::Field(desc, i18n("Description"), Data::Field::Para));
+ coll->addField(field);
+ entry->setField(QStringLiteral("description"), mapValue(objectMap, "comments"));
+ }
+
+ QVariantList refs = objectMap.value(QStringLiteral("references")).toList();
+ const QString krause(QStringLiteral("km"));
+ if(!coll->hasField(krause) && optionalFields().contains(krause)) {
+ Data::FieldPtr field(new Data::Field(krause, allOptionalFields().value(krause)));
+ field->setCategory(i18n("General"));
+ coll->addField(field);
+ foreach(const QVariant& ref, refs) {
+ QVariantMap refMap = ref.toMap();
+ if(mapValue(refMap, "catalogue", "code") == QLatin1String("KM")) {
+ entry->setField(krause, mapValue(refMap, "number"));
+ // don't break out, there could be multiple KM values and we want the last one
+ }
+ }
+ }
+
+ return entry;
+}
+
+Tellico::Fetch::FetchRequest NumistaFetcher::updateRequest(Data::EntryPtr entry_) {
+ QString t = entry_->field(QStringLiteral("type"));
+ QString c = entry_->field(QStringLiteral("country"));
+ if(!t.isEmpty()) {
+ return FetchRequest(Fetch::Keyword, t + QLatin1Char(' ') + c);
+ }
+
+ return FetchRequest();
+}
+
+Tellico::Fetch::ConfigWidget* NumistaFetcher::configWidget(QWidget* parent_) const {
+ return new NumistaFetcher::ConfigWidget(parent_, this);
+}
+
+QString NumistaFetcher::defaultName() {
+ return QStringLiteral("Numista"); // no translation
+}
+
+QString NumistaFetcher::defaultIcon() {
+ return favIcon("https://en.numista.com");
+}
+
+Tellico::StringHash NumistaFetcher::allOptionalFields() {
+ StringHash hash;
+ hash[QStringLiteral("numista")] = i18n("Numista Link");
+ hash[QStringLiteral("description")] = i18n("Description");
+ // treat images as optional since Numista doesn't break out different images for each year
+ hash[QStringLiteral("obverse")] = i18n("Obverse");
+ hash[QStringLiteral("reverse")] = i18n("Reverse");
+ hash[QStringLiteral("km")] = i18nc("Standard catalog of world coins number", "Krause-Mishler");
+ return hash;
+}
+
+NumistaFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const NumistaFetcher* fetcher_)
+ : Fetch::ConfigWidget(parent_) {
+ QGridLayout* l = new QGridLayout(optionsWidget());
+ l->setSpacing(4);
+ l->setColumnStretch(1, 10);
+
+ int row = -1;
+
+ QLabel* label = new QLabel(i18n("Access key: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+
+ m_apiKeyEdit = new QLineEdit(optionsWidget());
+ connect(m_apiKeyEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified);
+ l->addWidget(m_apiKeyEdit, row, 1);
+ label->setBuddy(m_apiKeyEdit);
+
+ label = new QLabel(i18n("Language: "), optionsWidget());
+ l->addWidget(label, ++row, 0);
+ m_langCombo = new GUI::ComboBox(optionsWidget());
+ QIcon iconUS(QStandardPaths::locate(QStandardPaths::GenericDataLocation,
+ QStringLiteral("kf5/locale/countries/us/flag.png")));
+ m_langCombo->addItem(iconUS, i18nc("Language", "English"), QLatin1String("en"));
+ QIcon iconFR(QStandardPaths::locate(QStandardPaths::GenericDataLocation,
+ QStringLiteral("kf5/locale/countries/fr/flag.png")));
+ m_langCombo->addItem(iconFR, i18nc("Language", "French"), QLatin1String("fr"));
+ void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated;
+ connect(m_langCombo, activatedInt, this, &ConfigWidget::slotSetModified);
+ connect(m_langCombo, activatedInt, this, &ConfigWidget::slotLangChanged);
+ l->addWidget(m_langCombo, row, 1);
+ label->setBuddy(m_langCombo);
+
+ l->setRowStretch(++row, 10);
+
+ // now add additional fields widget
+ addFieldsWidget(NumistaFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList());
+
+ // don't show the default API key
+ if(fetcher_) {
+ if(fetcher_->m_apiKey != Tellico::reverseObfuscate(NUMISTA_MAGIC_TOKEN)) {
+ m_apiKeyEdit->setText(fetcher_->m_apiKey);
+ }
+ m_langCombo->setCurrentData(fetcher_->m_locale);
+ }
+}
+
+void NumistaFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) {
+ const QString apiKey = m_apiKeyEdit->text().trimmed();
+ if(!apiKey.isEmpty()) {
+ config_.writeEntry("API Key", apiKey);
+ }
+ const QString lang = m_langCombo->currentData().toString();
+ config_.writeEntry("Locale", lang);
+}
+
+QString NumistaFetcher::ConfigWidget::preferredName() const {
+ return i18n("Numista (%1)", m_langCombo->currentText());
+}
+
+void NumistaFetcher::ConfigWidget::slotLangChanged() {
+ emit signalName(preferredName());
+}
diff --git a/src/fetch/numistafetcher.h b/src/fetch/numistafetcher.h
new file mode 100644
index 00000000..27634842
--- /dev/null
+++ b/src/fetch/numistafetcher.h
@@ -0,0 +1,131 @@
+/***************************************************************************
+ Copyright (C) 2020 Robby Stephenson <robby at 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_NUMISTAFETCHER_H
+#define TELLICO_NUMISTAFETCHER_H
+
+#include "fetcher.h"
+#include "configwidget.h"
+#include "../datavectors.h"
+
+#include <QPointer>
+
+class QLineEdit;
+
+class KJob;
+namespace KIO {
+ class StoredTransferJob;
+}
+
+namespace Tellico {
+ namespace GUI {
+ class ComboBox;
+ }
+
+ namespace Fetch {
+
+/**
+ * A fetcher for numista.com
+ *
+ * @author Robby Stephenson
+ */
+class NumistaFetcher : public Fetcher {
+Q_OBJECT
+
+public:
+ /**
+ */
+ NumistaFetcher(QObject* parent);
+ /**
+ */
+ virtual ~NumistaFetcher();
+
+ /**
+ */
+ virtual QString source() const Q_DECL_OVERRIDE;
+ virtual bool isSearching() const Q_DECL_OVERRIDE { return m_started; }
+ virtual void continueSearch() Q_DECL_OVERRIDE;
+ // amazon can search title or person
+ virtual bool canSearch(FetchKey k) const Q_DECL_OVERRIDE { return k == Keyword; }
+ virtual void stop() Q_DECL_OVERRIDE;
+ virtual Data::EntryPtr fetchEntryHook(uint uid) Q_DECL_OVERRIDE;
+ virtual Type type() const Q_DECL_OVERRIDE { return Numista; }
+ virtual bool canFetch(int type) const Q_DECL_OVERRIDE;
+ virtual void readConfigHook(const KConfigGroup& config) Q_DECL_OVERRIDE;
+ void setLimit(int limit);
+
+ /**
+ * 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 doSearch();
+ Data::EntryPtr parseEntry(const QByteArray& data);
+
+ int m_limit;
+ int m_total;
+ int m_page;
+
+ QHash<uint, int> m_matches; // search result id to coin id
+ QHash<uint, Data::EntryPtr> m_entries;
+ QPointer<KIO::StoredTransferJob> m_job;
+ QString m_apiKey;
+ QString m_locale;
+ QString m_year;
+
+ bool m_started;
+};
+
+class NumistaFetcher::ConfigWidget : public Fetch::ConfigWidget {
+Q_OBJECT
+
+public:
+ explicit ConfigWidget(QWidget* parent_, const NumistaFetcher* fetcher = nullptr);
+ virtual void saveConfigHook(KConfigGroup&) Q_DECL_OVERRIDE;
+ virtual QString preferredName() const Q_DECL_OVERRIDE;
+
+private Q_SLOTS:
+ void slotLangChanged();
+
+private:
+ QLineEdit* m_apiKeyEdit;
+ GUI::ComboBox* m_langCombo;
+};
+
+ } // end namespace
+} // end namespace
+#endif
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index b3e2a82e..c5fccb74 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -802,6 +802,14 @@ add_test(musicbrainzfetchertest musicbrainzfetchertest)
ecm_mark_as_test(musicbrainzfetchertest)
TARGET_LINK_LIBRARIES(musicbrainzfetchertest fetcherstest ${TELLICO_TEST_LIBS})
+add_executable(numistafetchertest numistafetchertest.cpp abstractfetchertest.cpp
+ ../fetch/numistafetcher.cpp
+)
+ecm_mark_nongui_executable(numistafetchertest)
+add_test(numistafetchertest numistafetchertest)
+ecm_mark_as_test(numistafetchertest)
+TARGET_LINK_LIBRARIES(numistafetchertest fetcherstest ${TELLICO_TEST_LIBS})
+
add_executable(openlibraryfetchertest openlibraryfetchertest.cpp abstractfetchertest.cpp
../fetch/openlibraryfetcher.cpp
)
diff --git a/src/tests/numistafetchertest.cpp b/src/tests/numistafetchertest.cpp
new file mode 100644
index 00000000..8cab6781
--- /dev/null
+++ b/src/tests/numistafetchertest.cpp
@@ -0,0 +1,98 @@
+/***************************************************************************
+ Copyright (C) 2020 Robby Stephenson <robby at 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 "numistafetchertest.h"
+
+#include "../fetch/numistafetcher.h"
+#include "../collections/coincollection.h"
+#include "../entry.h"
+#include "../images/imagefactory.h"
+
+#include <KConfig>
+#include <KConfigGroup>
+
+#include <QTest>
+
+QTEST_GUILESS_MAIN( NumistaFetcherTest )
+
+NumistaFetcherTest::NumistaFetcherTest() : AbstractFetcherTest() {
+}
+
+void NumistaFetcherTest::initTestCase() {
+ Tellico::ImageFactory::init();
+}
+
+void NumistaFetcherTest::testSacagawea() {
+ KConfig config(QFINDTESTDATA("tellicotest.config"), KConfig::SimpleConfig);
+ QString groupName = QStringLiteral("numista");
+ if(!config.hasGroup(groupName)) {
+ QSKIP("This test requires a config file.", SkipAll);
+ }
+ KConfigGroup cg(&config, groupName);
+
+ Tellico::Fetch::FetchRequest request(Tellico::Data::Collection::Coin,
+ Tellico::Fetch::Keyword,
+ QStringLiteral("2019 Sacagawea"));
+ Tellico::Fetch::Fetcher::Ptr fetcher(new Tellico::Fetch::NumistaFetcher(this));
+ fetcher->readConfig(cg, cg.name());
+
+ static_cast<Tellico::Fetch::NumistaFetcher*>(fetcher.data())->setLimit(1);
+ Tellico::Data::EntryList results = DO_FETCH1(fetcher, request, 1);
+
+ QCOMPARE(results.size(), 1);
+ Tellico::Data::EntryPtr entry = results.at(0);
+
+ QCOMPARE(entry->field(QStringLiteral("type")), QStringLiteral("Native American Dollar"));
+ QCOMPARE(entry->field(QStringLiteral("year")), QStringLiteral("2019"));
+ QCOMPARE(entry->field(QStringLiteral("country")), QStringLiteral("United States"));
+ QCOMPARE(entry->field(QStringLiteral("denomination")), QStringLiteral("1 Dollar"));
+ QCOMPARE(entry->field(QStringLiteral("currency")), QStringLiteral("Dollar"));
+ QCOMPARE(entry->field(QStringLiteral("numista")), QStringLiteral("https://en.numista.com/catalogue/pieces155679.html"));
+ QVERIFY(!entry->field(QStringLiteral("description")).isEmpty());
+ QVERIFY(!entry->field(QStringLiteral("obverse")).isEmpty());
+ QVERIFY(!entry->field(QStringLiteral("obverse")).contains(QLatin1Char('/')));
+ QVERIFY(!entry->field(QStringLiteral("reverse")).isEmpty());
+ QVERIFY(!entry->field(QStringLiteral("reverse")).contains(QLatin1Char('/')));
+}
+
+void NumistaFetcherTest::testJefferson() {
+ Tellico::Fetch::FetchRequest request(Tellico::Data::Collection::Coin,
+ Tellico::Fetch::Keyword,
+ QStringLiteral("1974 jefferson nickel"));
+ Tellico::Fetch::Fetcher::Ptr fetcher(new Tellico::Fetch::NumistaFetcher(this));
+
+ Tellico::Data::EntryList results = DO_FETCH(fetcher, request);
+
+ QVERIFY(!results.isEmpty());
+ Tellico::Data::EntryPtr entry = results.at(0);
+ QVERIFY(entry);
+
+ QCOMPARE(entry->field(QStringLiteral("type")), QStringLiteral("Jefferson Nickel"));
+ QCOMPARE(entry->field(QStringLiteral("year")), QStringLiteral("1974"));
+ QCOMPARE(entry->field(QStringLiteral("country")), QStringLiteral("United States"));
+ QCOMPARE(entry->field(QStringLiteral("denomination")), QStringLiteral("5 Cents"));
+ QCOMPARE(entry->field(QStringLiteral("currency")), QStringLiteral("Dollar"));
+}
diff --git a/src/fetch/fetch.h b/src/tests/numistafetchertest.h
similarity index 57%
copy from src/fetch/fetch.h
copy to src/tests/numistafetchertest.h
index 22d0878b..5614dd2b 100644
--- a/src/fetch/fetch.h
+++ b/src/tests/numistafetchertest.h
@@ -1,5 +1,5 @@
/***************************************************************************
- Copyright (C) 2003-2009 Robby Stephenson <robby at periapsis.org>
+ Copyright (C) 2020 Robby Stephenson <robby at periapsis.org>
***************************************************************************/
/***************************************************************************
@@ -22,92 +22,20 @@
* *
***************************************************************************/
-#ifndef TELLICO_FETCH_H
-#define TELLICO_FETCH_H
+#ifndef NUMISTAFETCHERTEST_H
+#define NUMISTAFETCHERTEST_H
-namespace Tellico {
- namespace Fetch {
+#include "abstractfetchertest.h"
-/**
- * FetchFirst must be first, and the rest must follow consecutively in value.
- * FetchLast must be last!
- */
-enum FetchKey {
- FetchFirst = 0,
- Title,
- Person,
- ISBN,
- UPC,
- Keyword,
- DOI,
- ArxivID,
- PubmedID,
- LCCN,
- Raw,
- ExecUpdate,
- FetchLast
-};
+class NumistaFetcherTest : public AbstractFetcherTest {
+Q_OBJECT
+public:
+ NumistaFetcherTest();
-// real ones must start at 0!
-enum Type {
- Unknown = -1,
- Amazon = 0,
- IMDB,
- Z3950,
- SRU,
- Entrez,
- ExecExternal,
- Yahoo, // Removed
- AnimeNfo,
- IBS,
- ISBNdb,
- GCstarPlugin,
- CrossRef,
- Citebase, // Removed
- Arxiv,
- Bibsonomy,
- GoogleScholar,
- Discogs,
- WineCom,
- TheMovieDB,
- MusicBrainz,
- GiantBomb,
- OpenLibrary,
- Multiple,
- Freebase, // Removed
- DVDFr,
- Filmaster,
- Douban,
- BiblioShare,
- MovieMeter,
- GoogleBook,
- MAS, // Removed
- Springer,
- Allocine,
- ScreenRush, // Removed
- FilmStarts, // Removed
- SensaCine, // Removed
- Beyazperde, // Removed
- HathiTrust,
- TheGamesDB,
- DBLP,
- VNDB,
- MRLookup,
- BoardGameGeek,
- Bedetheque,
- OMDB,
- KinoPoisk,
- VideoGameGeek,
- DBC,
- IGDB,
- Kino,
- MobyGames,
- ComicVine,
- KinoTeatr,
- Colnect
+private Q_SLOTS:
+ void initTestCase();
+ void testSacagawea();
+ void testJefferson();
};
- }
-}
-
#endif
diff --git a/src/tests/tellicotest.config b/src/tests/tellicotest.config
index cf4d5f87..bde4a544 100644
--- a/src/tests/tellicotest.config
+++ b/src/tests/tellicotest.config
@@ -81,3 +81,6 @@ Custom Fields=obverse,reverse,series,mintage,description
[colnect stamps]
Custom Fields=image,series,description,stanley-gibbons,michel
+
+[numista]
+Custom Fields=numista,description,obverse,reverse,km
More information about the kde-doc-english
mailing list