Git commit c7e7eb08c431163733cfac23be594d9ee53c7524 by Robby Stephenson. Committed on 10/03/2023 at 02:41. Pushed by rstephenson into branch 'master'.
Import metadata from EPub, FB2, and Mobi ebook formats Importing is supported for drag-and-drop, subject to support from KFileMetadata. Combining with PDF import isn't supported yet. BUG: 450192 FIXED-IN: 3.5 M +4 -0 ChangeLog M +1 -1 doc/importing-exporting.docbook M +10 -3 src/gui/drophandler.cpp M +10 -0 src/importdialog.cpp M +1 -0 src/translators/CMakeLists.txt A +136 -0 src/translators/ebookimporter.cpp [License: GPL (v2/3)] C +34 -68 src/translators/ebookimporter.h [from: src/translators/translators.h - 063% similarity] M +2 -1 src/translators/translators.h https://invent.kde.org/office/tellico/commit/c7e7eb08c431163733cfac23be594d9ee53c7524 diff --git a/ChangeLog b/ChangeLog index 167838cc..c5866af1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2023-03-09 Robby Stephenson <[email protected]> + + * Added support for importing EPub data on drag/drop (Bug 450192). + 2023-02-20 Robby Stephenson <[email protected]> * Updated AMS MR Lookup data source. diff --git a/doc/importing-exporting.docbook b/doc/importing-exporting.docbook index 753815da..249470af 100644 --- a/doc/importing-exporting.docbook +++ b/doc/importing-exporting.docbook @@ -222,7 +222,7 @@ Any &XML; file may be imported into &appname; provided an &XSL; stylesheet is av <sect1 id="drag-n-drop"> <title>Drag and Drop</title> -<para>Dragging data files to the main &appname; window and dropping them will import the files, just as if the <link linkend="importing">import command</link> was made from the menus. Drag and drop works for the following file formats: Tellico, Bibtex, RIS, and &PDF;. Importing multiple files at once is also supported.</para> +<para>Dragging data files to the main &appname; window and dropping them will import the files, just as if the <link linkend="importing">import command</link> was made from the menus. Drag and drop works for the following file formats: Tellico, Bibtex, RIS, &PDF;, and EPub. Importing multiple files at once is also supported.</para> <para>So, for example, if you want to catalog several <link linkend="importing-pdf">&PDF; files</link>, select them in the file manager and drag them to the &appname; window. &appname; will import as much metadata from the files as it can, and then fetch additional information from various configured Internet sources.</para> diff --git a/src/gui/drophandler.cpp b/src/gui/drophandler.cpp index 8bac7b03..3bf5cc77 100644 --- a/src/gui/drophandler.cpp +++ b/src/gui/drophandler.cpp @@ -84,19 +84,18 @@ bool DropHandler::drop(QDropEvent* event_) { bool DropHandler::handleURL(const QList<QUrl>& urls_) { bool hasUnknown = false; - QList<QUrl> tc, pdf, bib, ris, ciw; + QMimeDatabase db; + QList<QUrl> tc, pdf, bib, ris, ciw, ebook; foreach(const QUrl& url, urls_) { QMimeType ptr; // findByURL doesn't work for http, so actually query // the url itself if(url.scheme() != QLatin1String("http")) { - QMimeDatabase db; ptr = db.mimeTypeForUrl(url); } else { KIO::MimetypeJob* job = KIO::mimetype(url, KIO::HideProgressInfo); KJobWidgets::setWindow(job, GUI::Proxy::widget()); job->exec(); - QMimeDatabase db; ptr = db.mimeTypeForName(job->mimetype()); } if(ptr.inherits(QStringLiteral("application/x-tellico"))) { @@ -109,6 +108,11 @@ bool DropHandler::handleURL(const QList<QUrl>& urls_) { bib << url; } else if(ptr.inherits(QStringLiteral("application/x-research-info-systems"))) { ris << url; + } else if(ptr.inherits(QStringLiteral("application/epub+zip")) || + ptr.inherits(QStringLiteral("application/x-mobipocket-ebook")) || + ptr.inherits(QStringLiteral("application/x-fictionbook+xml")) || + ptr.inherits(QStringLiteral("application/x-zip-compressed-fb2"))) { + ebook << url; } else if(url.fileName().endsWith(QLatin1String(".bib"))) { bib << url; } else if(url.fileName().endsWith(QLatin1String(".ris"))) { @@ -146,6 +150,9 @@ bool DropHandler::handleURL(const QList<QUrl>& urls_) { if(!ciw.isEmpty()) { mainWindow->importFile(Import::CIW, ciw); } + if(!ebook.isEmpty()) { + mainWindow->importFile(Import::EBook, ebook); + } // any unknown urls get passed return !hasUnknown; } diff --git a/src/importdialog.cpp b/src/importdialog.cpp index 408d2dda..c4b22525 100644 --- a/src/importdialog.cpp +++ b/src/importdialog.cpp @@ -55,6 +55,7 @@ #include "translators/collectorzimporter.h" #include "translators/datacrowimporter.h" #include "translators/marcimporter.h" +#include "translators/ebookimporter.h" #include "utils/datafileregistry.h" #include <KLocalizedString> @@ -328,6 +329,10 @@ Tellico::Import::Importer* ImportDialog::importer(Tellico::Import::Format format CHECK_SIZE; importer = new Import::MarcImporter(firstURL); break; + + case Import::EBook: + importer = new Import::EBookImporter(urls_); + break; } if(!importer) { myWarning() << "importer not created!"; @@ -414,6 +419,11 @@ QString ImportDialog::fileFilter(Tellico::Import::Format format_) { text += i18n("XML Files") + QLatin1String(" (*.xml)") + QLatin1String(";;"); break; + case Import::EBook: + // KFileMetaData has extractors that support mimetypes with these typical extensions + text = i18n("eBook Files") + QLatin1String(" (*.epub *.fb2 *.fb2zip *.mobi)") + QLatin1String(";;"); + break; + case Import::AudioFile: case Import::Alexandria: case Import::FreeDB: diff --git a/src/translators/CMakeLists.txt b/src/translators/CMakeLists.txt index 602b28f4..1d92c123 100644 --- a/src/translators/CMakeLists.txt +++ b/src/translators/CMakeLists.txt @@ -19,6 +19,7 @@ SET(translators_STAT_SRCS datacrowimporter.cpp dataimporter.cpp deliciousimporter.cpp + ebookimporter.cpp exporter.cpp filelistingimporter.cpp freedb_util.cpp diff --git a/src/translators/ebookimporter.cpp b/src/translators/ebookimporter.cpp new file mode 100644 index 00000000..b67a5d6b --- /dev/null +++ b/src/translators/ebookimporter.cpp @@ -0,0 +1,136 @@ +/*************************************************************************** + Copyright (C) 2023 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 <config.h> + +#include "ebookimporter.h" +#include "../collections/bookcollection.h" +#include "../core/netaccess.h" +#include "../images/imagefactory.h" +#include "../utils/datafileregistry.h" +#include "../tellico_debug.h" + +#include <KLocalizedString> +#include <KFileItem> +#ifdef HAVE_KFILEMETADATA +#include <KFileMetaData/Extractor> +#include <KFileMetaData/ExtractorCollection> +#include <KFileMetaData/SimpleExtractionResult> +#include <KFileMetaData/PropertyInfo> +#endif + +#include <QPixmap> + +using Tellico::Import::EBookImporter; + +EBookImporter::EBookImporter(const QUrl& url_) : Importer(url_) { +} + +EBookImporter::EBookImporter(const QList<QUrl>& urls_) : Importer(urls_) { +} + +bool EBookImporter::canImport(int type) const { + return type == Data::Collection::Book; +} + +Tellico::Data::CollPtr EBookImporter::collection() { + Data::CollPtr coll(new Data::BookCollection(true)); +#ifdef HAVE_KFILEMETADATA + KFileMetaData::ExtractorCollection extractors; + foreach(const QUrl& url, urls()) { + KFileItem item(url); + myDebug() << "Reading" << url.url() << item.mimetype(); + KFileMetaData::SimpleExtractionResult result(url.toLocalFile(), + item.mimetype(), + KFileMetaData::ExtractionResult::ExtractMetaData); + auto exList = extractors.fetchExtractors(item.mimetype()); + if(exList.isEmpty()) continue; + foreach(auto ex, exList) { + ex->extract(&result); + } + bool isEmpty = true; + Data::EntryPtr entry(new Data::Entry(coll)); + entry->setField(QStringLiteral("comments"), url.toLocalFile()); + QStringList authors, publishers, genres; + KFileMetaData::PropertyMap properties = result.properties(); + KFileMetaData::PropertyMap::const_iterator it = properties.constBegin(); + for( ; it != properties.constEnd(); ++it) { + const QString value = it.value().toString(); + if(value.isEmpty()) continue; + switch(it.key()) { + case KFileMetaData::Property::Title: + isEmpty = false; // require a title or author + entry->setField(QStringLiteral("title"), value); + break; + + case KFileMetaData::Property::Author: + isEmpty = false; // require a title or author + authors += value; + break; + + case KFileMetaData::Property::Publisher: + publishers += value; + break; + + case KFileMetaData::Property::Subject: + case KFileMetaData::Property::Genre: + genres += value; + break; + + case KFileMetaData::Property::ReleaseYear: + entry->setField(QStringLiteral("pub_year"), value); + break; + + // is description usually the plot or just comments? + case KFileMetaData::Property::Description: + entry->setField(QStringLiteral("plot"), value); + break; + + default: + if(!value.isEmpty()) { + myDebug() << "skipping" << it.key() << it.value(); + } + break; + } + } + if(!authors.isEmpty()) { + entry->setField(QStringLiteral("author"), authors.join(FieldFormat::delimiterString())); + } + if(!publishers.isEmpty()) { + entry->setField(QStringLiteral("publisher"), publishers.join(FieldFormat::delimiterString())); + } + if(!genres.isEmpty()) { + entry->setField(QStringLiteral("genre"), genres.join(FieldFormat::delimiterString())); + } + if(!isEmpty) { + coll->addEntries(entry); + } + } +#endif + return coll; +} + +void EBookImporter::slotCancel() { + myDebug() << "EBookImporter::slotCancel() - unimplemented"; +} diff --git a/src/translators/translators.h b/src/translators/ebookimporter.h similarity index 63% copy from src/translators/translators.h copy to src/translators/ebookimporter.h index 45c384d3..9df575f9 100644 --- a/src/translators/translators.h +++ b/src/translators/ebookimporter.h @@ -1,5 +1,5 @@ /*************************************************************************** - Copyright (C) 2003-2009 Robby Stephenson <[email protected]> + Copyright (C) 2023 Robby Stephenson <[email protected]> ***************************************************************************/ /*************************************************************************** @@ -22,75 +22,41 @@ * * ***************************************************************************/ -#ifndef TRANSLATORS_H -#define TRANSLATORS_H +#ifndef TELLICO_IMPORT_EBOOKIMPORTER_H +#define TELLICO_IMPORT_EBOOKIMPORTER_H + +#include "importer.h" +#include "../datavectors.h" 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, - LibraryThing, - Collectorz, - DataCrow, - MARC - }; - - enum Action { - Replace, - Append, - Merge - }; - - enum Target { - None, - File, - Dir - }; - } - - namespace Export { - enum Format { - TellicoXML = 0, - TellicoZip, - Bibtex, - Bibtexml, - HTML, - CSV, - XSLT, - Text, - PilotDB, // Deprecated - Alexandria, - ONIX, - GCstar - }; - - enum Target { - None, - File, - Dir - }; - } -} +/** + * @author Robby Stephenson +*/ +class EBookImporter : public Importer { +Q_OBJECT + +public: + /** + */ + EBookImporter(const QUrl& url); + EBookImporter(const QList<QUrl>& urls); + + /** + */ + virtual Data::CollPtr collection() Q_DECL_OVERRIDE; + /** + */ + virtual QWidget* widget(QWidget*) Q_DECL_OVERRIDE { return nullptr; } + virtual bool canImport(int type) const Q_DECL_OVERRIDE; + +public Q_SLOTS: + void slotCancel() Q_DECL_OVERRIDE; + +private: +}; + + } // end namespace +} // end namespace #endif diff --git a/src/translators/translators.h b/src/translators/translators.h index 45c384d3..074bf6bd 100644 --- a/src/translators/translators.h +++ b/src/translators/translators.h @@ -53,7 +53,8 @@ namespace Tellico { LibraryThing, Collectorz, DataCrow, - MARC + MARC, + EBook }; enum Action {
