[tellico/3.2] /: Add an importer for LibraryThing.com collections exported in JSON.

Robby Stephenson null at kde.org
Mon Oct 7 01:40:14 BST 2019


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 <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 "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 <robby at periapsis.org>
+    Copyright (C) 2019 Robby Stephenson <robby at periapsis.org>
  ***************************************************************************/
 
 /***************************************************************************
@@ -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 {


More information about the kde-doc-english mailing list