Git commit 1a8521490343716351e43ff939048ddfa8c885fc by Robby Stephenson. Committed on 07/10/2019 at 00:37. Pushed by rstephenson into branch '3.2'.
Add an importer for LibraryThing.com collections exported in JSON. There is no public API for pulling a user's collection directly, so the collection must first be exported in JSON format. No images are included. Publication data, such as publisher and location, are not parsed. User tags are imported as keywords. FEATURE: 411095 FIXED-IN: 3.2.2 M +8 -0 doc/importing-exporting.docbook M +2 -1 icons/CMakeLists.txt M +1 -0 icons/icons.qrc A +- -- icons/librarything.png M +8 -0 src/importdialog.cpp M +3 -0 src/mainwindow.cpp M +2 -1 src/tellicoui.rc M +1 -0 src/translators/CMakeLists.txt A +161 -0 src/translators/librarythingimporter.cpp [License: GPL (v2/3)] C +30 -60 src/translators/librarythingimporter.h [from: src/translators/translators.h - 065% similarity] M +2 -1 src/translators/translators.h https://commits.kde.org/tellico/1a8521490343716351e43ff939048ddfa8c885fc diff --git a/doc/importing-exporting.docbook b/doc/importing-exporting.docbook index a7ca2b68..9137d64e 100644 --- a/doc/importing-exporting.docbook +++ b/doc/importing-exporting.docbook @@ -184,6 +184,14 @@ The <ulink url="https://en.wikipedia.org/wiki/RIS_(file_format)"><acronym>RIS</a <ulink url="https://www.goodreads.com">Goodreads</ulink> is an online social network for readers to track book collections. &appname; can import the list of books in a user's collection, given either the user name or user ID, as long as the collection is set to be publicly accessible. </para> </sect3> + +<sect3 id="importing-librarything"> +<title>Importing LibraryThing Collection</title> +<para> +<ulink url="https://www.librarything.com">LibraryThing</ulink> is an online service to help people catalog their books easily. &appname; can import the list of books in a user's collection, <ulink url="https://www.librarything.com/export.php?export_type=json">exported in JSON format</ulink>. +</para> +</sect3> + </sect2> <sect2 id="importing-file-listing"> diff --git a/icons/CMakeLists.txt b/icons/CMakeLists.txt index 7016edab..b9d7f85a 100644 --- a/icons/CMakeLists.txt +++ b/icons/CMakeLists.txt @@ -17,8 +17,9 @@ set(PIC_FILES file.png game.png gcstar.png - griffith.png goodreads.png + griffith.png + librarything.png nocover_album.png nocover_bibtex.png nocover_boardgame.png diff --git a/icons/icons.qrc b/icons/icons.qrc index 6928a582..bcee7435 100644 --- a/icons/icons.qrc +++ b/icons/icons.qrc @@ -19,6 +19,7 @@ <file alias="gcstar.png">gcstar.png</file> <file alias="goodreads.png">goodreads.png</file> <file alias="griffith.png">griffith.png</file> + <file alias="librarything.png">librarything.png</file> <file alias="nocover_album.png">nocover_album.png</file> <file alias="nocover_bibtex.png">nocover_bibtex.png</file> <file alias="nocover_boardgame.png">nocover_boardgame.png</file> diff --git a/icons/librarything.png b/icons/librarything.png new file mode 100644 index 00000000..babe0c95 Binary files /dev/null and b/icons/librarything.png differ diff --git a/src/importdialog.cpp b/src/importdialog.cpp index b989a35c..2c4d2798 100644 --- a/src/importdialog.cpp +++ b/src/importdialog.cpp @@ -50,6 +50,7 @@ #include "translators/ciwimporter.h" #include "translators/vinoxmlimporter.h" #include "translators/boardgamegeekimporter.h" +#include "translators/librarythingimporter.h" #include "utils/datafileregistry.h" #include <KLocalizedString> @@ -295,6 +296,11 @@ Tellico::Import::Importer* ImportDialog::importer(Tellico::Import::Format format CHECK_SIZE; importer = new Import::BoardGameGeekImporter(); break; + + case Import::LibraryThing: + CHECK_SIZE; + importer = new Import::LibraryThingImporter(); + break; } if(!importer) { myWarning() << "importer not created!"; @@ -386,6 +392,7 @@ QString ImportDialog::fileFilter(Tellico::Import::Format format_) { case Import::GRS1: case Import::Goodreads: case Import::BoardGameGeek: + case Import::LibraryThing: break; } @@ -404,6 +411,7 @@ Tellico::Import::Target ImportDialog::importTarget(Tellico::Import::Format forma case Import::FreeDB: case Import::Goodreads: case Import::BoardGameGeek: + case Import::LibraryThing: return Import::None; default: return Import::File; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8250c562..051afc9a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -390,6 +390,9 @@ void MainWindow::initActions() { IMPORT_ACTION(Import::Goodreads, "file_import_goodreads", i18n("Import Goodreads Collection..."), i18n("Import a collection from Goodreads.com"), QIcon::fromTheme(QStringLiteral(":/icons/goodreads"))); + IMPORT_ACTION(Import::LibraryThing, "file_import_librarything", i18n("Import LibraryThing Collection..."), + i18n("Import a collection from LibraryThing.com"), QIcon::fromTheme(QStringLiteral(":/icons/librarything"))); + IMPORT_ACTION(Import::PDF, "file_import_pdf", i18n("Import PDF File..."), i18n("Import a PDF file"), mimeIcon("application/pdf")); diff --git a/src/tellicoui.rc b/src/tellicoui.rc index d2fd2320..2792add9 100644 --- a/src/tellicoui.rc +++ b/src/tellicoui.rc @@ -1,6 +1,6 @@ <?xml version = '1.0'?> <!DOCTYPE kpartgui SYSTEM "kpartgui.dtd"> -<kpartgui version="37" name="tellico"> +<kpartgui version="38" name="tellico"> <MenuBar> <Menu name="file"> <text>&File</text> @@ -35,6 +35,7 @@ <Action name="file_import_referencer"/> <Action name="file_import_ris"/> <Action name="file_import_goodreads"/> + <Action name="file_import_librarything"/> <Separator/> <Action name="file_import_freedb"/> <Action name="file_import_audiofile"/> diff --git a/src/translators/CMakeLists.txt b/src/translators/CMakeLists.txt index e93e9691..7d18cfe7 100644 --- a/src/translators/CMakeLists.txt +++ b/src/translators/CMakeLists.txt @@ -28,6 +28,7 @@ SET(translators_STAT_SRCS grs1importer.cpp htmlexporter.cpp importer.cpp + librarythingimporter.cpp onixexporter.cpp pdfimporter.cpp referencerimporter.cpp diff --git a/src/translators/librarythingimporter.cpp b/src/translators/librarythingimporter.cpp new file mode 100644 index 00000000..9ca38674 --- /dev/null +++ b/src/translators/librarythingimporter.cpp @@ -0,0 +1,161 @@ +/*************************************************************************** + Copyright (C) 2019 Robby Stephenson <[email protected]> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "librarythingimporter.h" +#include "../collections/bookcollection.h" +#include "../core/filehandler.h" +#include "../utils/string_utils.h" +#include "../utils/isbnvalidator.h" +#include "../tellico_debug.h" + +#include <KLocalizedString> +#include <KUrlRequester> + +#include <QVBoxLayout> +#include <QFormLayout> +#include <QGroupBox> +#include <QLabel> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonParseError> + +using Tellico::Import::LibraryThingImporter; + +LibraryThingImporter::LibraryThingImporter() : Import::Importer(), m_widget(nullptr), m_URLRequester(nullptr) { +} + +bool LibraryThingImporter::canImport(int type) const { + return type == Data::Collection::Book; +} + +Tellico::Data::CollPtr LibraryThingImporter::collection() { + if(m_coll) { + return m_coll; + } + + if(!m_widget) { + myWarning() << "no widget!"; + return Data::CollPtr(); + } + + QUrl jsonUrl = m_URLRequester->url(); + + if(jsonUrl.isEmpty() || !jsonUrl.isValid()) { + myDebug() << "Bad jsonUrl:" << jsonUrl; + return Data::CollPtr(); + } + + + QByteArray data = Tellico::FileHandler::readDataFile(jsonUrl, false /* quiet */); + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(data, &parseError); + if(doc.isNull()) { + myDebug() << "Bad json data:" << parseError.errorString(); + return Data::CollPtr(); + } + + m_coll = new Data::BookCollection(true); + Data::EntryList entries; + QVariantMap map = doc.object().toVariantMap(); + QMapIterator<QString, QVariant> i(map); + while (i.hasNext()) { + i.next(); + QVariantMap valueMap = i.value().toMap(); + Data::EntryPtr entry(new Data::Entry(m_coll)); + entry->setField(QStringLiteral("title"), mapValue(valueMap, "title")); + entry->setField(QStringLiteral("pub_year"), mapValue(valueMap, "date")); + entry->setField(QStringLiteral("keyword"), mapValue(valueMap, "tags")); + entry->setField(QStringLiteral("genre"), mapValue(valueMap, "genre")); + entry->setField(QStringLiteral("series"), mapValue(valueMap, "series")); + entry->setField(QStringLiteral("language"), mapValue(valueMap, "language")); + + QJsonArray authorArray = valueMap.value(QStringLiteral("authors")).toJsonArray(); + QStringList authors; + for(int i = 0; i < authorArray.size(); ++i) { + QVariantMap m = authorArray.at(i).toObject().toVariantMap(); + // TODO: read config option for author formatting? + // use first-lastname for now + authors += mapValue(m, "fl"); + } + entry->setField(QStringLiteral("author"), authors.join(FieldFormat::delimiterString())); + + QJsonArray formatArray = valueMap.value(QStringLiteral("format")).toJsonArray(); + for(int i = 0; i < formatArray.size(); ++i) { + QVariantMap m = formatArray.at(i).toObject().toVariantMap(); + const QString format = mapValue(m, "text"); + if(format == QLatin1String("Paperback")) { + entry->setField(QStringLiteral("binding"), i18n("Paperback")); + } else if(format == QLatin1String("Hardcover")) { + entry->setField(QStringLiteral("binding"), i18n("Hardback")); + } else { + // just in case there's a value there + entry->setField(QStringLiteral("binding"), format); + } + break; + } + + QString isbn = mapValue(valueMap, "originalisbn"); + ISBNValidator::staticFixup(isbn); + entry->setField(QStringLiteral("isbn"), isbn); + + // grab first set of digits + QRegularExpression digits(QStringLiteral("\\d+")); + QRegularExpressionMatch match = digits.match(mapValue(valueMap, "pages")); + if(match.hasMatch()) { + entry->setField(QStringLiteral("pages"), match.captured(0)); + } + entries += entry; + } + m_coll->addEntries(entries); + return m_coll; +} + +QWidget* LibraryThingImporter::widget(QWidget* parent_) { + if(m_widget) { + return m_widget; + } + m_widget = new QWidget(parent_); + QVBoxLayout* l = new QVBoxLayout(m_widget); + + QGroupBox* gbox = new QGroupBox(i18n("LibraryThing Options"), m_widget); + QFormLayout* lay = new QFormLayout(gbox); + + lay->addRow(new QLabel(i18n("Export your LibraryThing collection in " + "<a href=\"https://www.librarything.com/export.php?export_type=json\">JSON format</a>."), gbox)); + + m_URLRequester = new KUrlRequester(gbox); + // these are in the old KDE4 filter format, not the Qt5 format + QString filter = QLatin1String("*.json|") + i18n("JSON Files") + + QLatin1Char('\n') + + QLatin1String("*|") + i18n("All Files"); + m_URLRequester->setFilter(filter); + + lay->addRow(i18n("LibraryThing file:"), m_URLRequester); + + l->addWidget(gbox); + l->addStretch(1); + + return m_widget; +} diff --git a/src/translators/translators.h b/src/translators/librarythingimporter.h similarity index 65% copy from src/translators/translators.h copy to src/translators/librarythingimporter.h index 57353472..ea2f33d9 100644 --- a/src/translators/translators.h +++ b/src/translators/librarythingimporter.h @@ -1,5 +1,5 @@ /*************************************************************************** - Copyright (C) 2003-2009 Robby Stephenson <[email protected]> + Copyright (C) 2019 Robby Stephenson <[email protected]> ***************************************************************************/ /*************************************************************************** @@ -22,71 +22,41 @@ * * ***************************************************************************/ -#ifndef TRANSLATORS_H -#define TRANSLATORS_H +#ifndef TELLICO_IMPORT_LIBRARYTHINGIMPORTER_H +#define TELLICO_IMPORT_LIBRARYTHINGIMPORTER_H + +#include "importer.h" + +class KUrlRequester; namespace Tellico { namespace Import { - enum Format { - TellicoXML = 0, - Bibtex, - Bibtexml, - CSV, - XSLT, - AudioFile, - MODS, - Alexandria, - FreeDB, - RIS, - GCstar, - FileListing, - GRS1, - AMC, - Griffith, - PDF, - Referencer, - Delicious, - Goodreads, - CIW, - VinoXML, - BoardGameGeek - }; - enum Action { - Replace, - Append, - Merge - }; +/** + * @author Robby Stephenson +*/ +class LibraryThingImporter : public Importer { +Q_OBJECT + +public: + /** + */ + LibraryThingImporter(); + + virtual Data::CollPtr collection() Q_DECL_OVERRIDE; + virtual bool canImport(int type) const Q_DECL_OVERRIDE; - enum Target { - None, - File, - Dir - }; - } + virtual QWidget* widget(QWidget* parent) Q_DECL_OVERRIDE; - namespace Export { - enum Format { - TellicoXML = 0, - TellicoZip, - Bibtex, - Bibtexml, - HTML, - CSV, - XSLT, - Text, - PilotDB, // Deprecated - Alexandria, - ONIX, - GCstar - }; +public Q_SLOTS: + void slotCancel() Q_DECL_OVERRIDE {} - enum Target { - None, - File, - Dir - }; - } -} +private: + Data::CollPtr m_coll; + QWidget* m_widget; + KUrlRequester* m_URLRequester; +}; + } // end namespace +} // end namespace #endif diff --git a/src/translators/translators.h b/src/translators/translators.h index 57353472..76210c81 100644 --- a/src/translators/translators.h +++ b/src/translators/translators.h @@ -49,7 +49,8 @@ namespace Tellico { Goodreads, CIW, VinoXML, - BoardGameGeek + BoardGameGeek, + LibraryThing }; enum Action {
