File indexing completed on 2024-04-28 05:08:23

0001 /***************************************************************************
0002     Copyright (C) 2003-2014 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include <config.h>
0026 #include "importdialog.h"
0027 #include "document.h"
0028 #include "tellico_debug.h"
0029 #include "collection.h"
0030 #include "progressmanager.h"
0031 #include "utils/guiproxy.h"
0032 
0033 #include "translators/importer.h"
0034 #include "translators/tellicoimporter.h"
0035 #include "translators/bibteximporter.h"
0036 #include "translators/bibtexmlimporter.h"
0037 #include "translators/csvimporter.h"
0038 #include "translators/xsltimporter.h"
0039 #include "translators/audiofileimporter.h"
0040 #include "translators/alexandriaimporter.h"
0041 #include "translators/freedbimporter.h"
0042 #include "translators/risimporter.h"
0043 #include "translators/gcstarimporter.h"
0044 #include "translators/filelistingimporter.h"
0045 #include "translators/amcimporter.h"
0046 #include "translators/griffithimporter.h"
0047 #include "translators/pdfimporter.h"
0048 #include "translators/referencerimporter.h"
0049 #include "translators/deliciousimporter.h"
0050 #include "translators/goodreadsimporter.h"
0051 #include "translators/ciwimporter.h"
0052 #include "translators/vinoxmlimporter.h"
0053 #include "translators/boardgamegeekimporter.h"
0054 #include "translators/librarythingimporter.h"
0055 #include "translators/collectorzimporter.h"
0056 #include "translators/datacrowimporter.h"
0057 #include "translators/marcimporter.h"
0058 #include "translators/ebookimporter.h"
0059 #include "translators/discogsimporter.h"
0060 #include "utils/datafileregistry.h"
0061 
0062 #include <KLocalizedString>
0063 #include <KStandardGuiItem>
0064 
0065 #include <QGroupBox>
0066 #include <QButtonGroup>
0067 #include <QRadioButton>
0068 #include <QCheckBox>
0069 #include <QTimer>
0070 #include <QVBoxLayout>
0071 #include <QDialogButtonBox>
0072 #include <QPushButton>
0073 
0074 using Tellico::ImportDialog;
0075 
0076 ImportDialog::ImportDialog(Tellico::Import::Format format_, const QList<QUrl>& urls_, QWidget* parent_)
0077     : QDialog(parent_),
0078       m_importer(importer(format_, urls_)) {
0079   setModal(true);
0080   setWindowTitle(i18n("Import Options"));
0081 
0082   QVBoxLayout* mainLayout = new QVBoxLayout();
0083   setLayout(mainLayout);
0084 
0085   QWidget* widget = new QWidget(this);
0086   mainLayout->addWidget(widget);
0087   QVBoxLayout* topLayout = new QVBoxLayout(widget);
0088 
0089   QGroupBox* groupBox = new QGroupBox(i18n("Import Options"), widget);
0090   QVBoxLayout* vlay = new QVBoxLayout(groupBox);
0091   topLayout->addWidget(groupBox, 0);
0092 
0093   m_radioReplace = new QRadioButton(i18n("&Replace current collection"), groupBox);
0094   m_radioReplace->setWhatsThis(i18n("Replace the current collection with the contents "
0095                                      "of the imported file."));
0096   m_radioAppend = new QRadioButton(i18n("A&ppend to current collection"), groupBox);
0097   m_radioAppend->setWhatsThis(i18n("Append the contents of the imported file to the "
0098                                    "current collection. This is only possible when the "
0099                                    "collection types match."));
0100   m_radioMerge = new QRadioButton(i18n("&Merge with current collection"), groupBox);
0101   m_radioMerge->setWhatsThis(i18n("Merge the contents of the imported file to the "
0102                                   "current collection. This is only possible when the "
0103                                   "collection types match. Entries must match exactly "
0104                                   "in order to be merged."));
0105   if(m_importer->canImport(Data::Document::self()->collection()->type())) {
0106     // append by default?
0107     m_radioAppend->setChecked(true);
0108   } else {
0109     m_radioReplace->setChecked(true);
0110     m_radioAppend->setEnabled(false);
0111     m_radioMerge->setEnabled(false);
0112   }
0113 
0114   vlay->addWidget(m_radioReplace);
0115   vlay->addWidget(m_radioAppend);
0116   vlay->addWidget(m_radioMerge);
0117 
0118   m_buttonGroup = new QButtonGroup(widget);
0119   m_buttonGroup->addButton(m_radioReplace, Import::Replace);
0120   m_buttonGroup->addButton(m_radioAppend, Import::Append);
0121   m_buttonGroup->addButton(m_radioMerge, Import::Merge);
0122 
0123   QWidget* w = m_importer->widget(widget);
0124 //  m_importer->readOptions(KSharedConfig::openConfig());
0125   if(w) {
0126     w->layout()->setMargin(0);
0127     topLayout->addWidget(w, 0);
0128   }
0129 
0130 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
0131   connect(m_buttonGroup, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked),
0132           m_importer, &Tellico::Import::Importer::slotActionChanged);
0133 #else
0134   connect(m_buttonGroup, &QButtonGroup::idClicked,
0135           m_importer, &Tellico::Import::Importer::slotActionChanged);
0136 #endif
0137 
0138   topLayout->addStretch();
0139 
0140   QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
0141   mainLayout->addWidget(buttonBox);
0142   QPushButton* okButton = buttonBox->button(QDialogButtonBox::Ok);
0143   okButton->setDefault(true);
0144   okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0145   connect(okButton, &QPushButton::clicked, this, &ImportDialog::slotOk);
0146   connect(buttonBox, &QDialogButtonBox::accepted, this, &ImportDialog::accept);
0147   connect(buttonBox, &QDialogButtonBox::rejected, this, &ImportDialog::reject);
0148 
0149   KGuiItem ok = KStandardGuiItem::ok();
0150   ok.setText(i18n("&Import"));
0151   KGuiItem::assign(okButton, ok);
0152 
0153   // want to grab default button action, too
0154   // since the importer might do something with widgets, don't just call it, do it after layout is done
0155   QTimer::singleShot(0, this, &ImportDialog::slotUpdateAction);
0156 }
0157 
0158 ImportDialog::~ImportDialog() {
0159   delete m_importer;
0160   m_importer = nullptr;
0161 }
0162 
0163 Tellico::Data::CollPtr ImportDialog::collection() {
0164   if(m_importer && !m_coll) {
0165     ProgressItem& item = ProgressManager::self()->newProgressItem(m_importer, m_importer->progressLabel(), true);
0166     connect(m_importer, &Import::Importer::signalTotalSteps,
0167             ProgressManager::self(), &ProgressManager::setTotalSteps);
0168     connect(m_importer, &Import::Importer::signalProgress,
0169             ProgressManager::self(), &ProgressManager::setProgress);
0170     connect(&item, &ProgressItem::signalCancelled, m_importer, &Import::Importer::slotCancel);
0171     ProgressItem::Done done(m_importer);
0172     m_coll = m_importer->collection();
0173   }
0174   return m_coll;
0175 }
0176 
0177 QString ImportDialog::statusMessage() const {
0178   return m_importer ? m_importer->statusMessage() : QString();
0179 }
0180 
0181 Tellico::Import::Action ImportDialog::action() const {
0182   if(m_radioReplace->isChecked()) {
0183     return Import::Replace;
0184   } else if(m_radioAppend->isChecked()) {
0185     return Import::Append;
0186   } else {
0187     return Import::Merge;
0188   }
0189 }
0190 
0191 // static
0192 Tellico::Import::Importer* ImportDialog::importer(Tellico::Import::Format format_, const QList<QUrl>& urls_) {
0193 #define CHECK_SIZE if(urls_.size() > 1) myWarning() << "only importing first URL"
0194   QUrl firstURL = urls_.isEmpty() ? QUrl() : urls_[0];
0195   Import::Importer* importer = nullptr;
0196   switch(format_) {
0197     case Import::TellicoXML:
0198       CHECK_SIZE;
0199       importer = new Import::TellicoImporter(firstURL);
0200       break;
0201 
0202     case Import::Bibtex:
0203       importer = new Import::BibtexImporter(urls_);
0204 #ifndef ENABLE_BTPARSE
0205       myLog() << "Bibtex importing is not available due to lack of btparse library";
0206 #endif
0207       break;
0208 
0209     case Import::Bibtexml:
0210       CHECK_SIZE;
0211       importer = new Import::BibtexmlImporter(firstURL);
0212       break;
0213 
0214     case Import::CSV:
0215       CHECK_SIZE;
0216       importer = new Import::CSVImporter(firstURL);
0217       break;
0218 
0219     case Import::XSLT:
0220       CHECK_SIZE;
0221       importer = new Import::XSLTImporter(firstURL);
0222       break;
0223 
0224     case Import::MODS:
0225       CHECK_SIZE;
0226       importer = new Import::XSLTImporter(firstURL);
0227       {
0228         QString xsltFile = DataFileRegistry::self()->locate(QStringLiteral("mods2tellico.xsl"));
0229         if(!xsltFile.isEmpty()) {
0230           QUrl u = QUrl::fromLocalFile(xsltFile);
0231           static_cast<Import::XSLTImporter*>(importer)->setXSLTURL(u);
0232         } else {
0233           myWarning() << "unable to find mods2tellico.xml!";
0234         }
0235       }
0236       break;
0237 
0238     case Import::AudioFile:
0239       CHECK_SIZE;
0240       importer = new Import::AudioFileImporter(firstURL);
0241       break;
0242 
0243     case Import::Alexandria:
0244       CHECK_SIZE;
0245       importer = new Import::AlexandriaImporter();
0246       break;
0247 
0248     case Import::FreeDB:
0249       CHECK_SIZE;
0250       importer = new Import::FreeDBImporter();
0251       break;
0252 
0253     case Import::RIS:
0254       importer = new Import::RISImporter(urls_);
0255       break;
0256 
0257     case Import::GCstar:
0258       CHECK_SIZE;
0259       importer = new Import::GCstarImporter(firstURL);
0260       break;
0261 
0262     case Import::FileListing:
0263       CHECK_SIZE;
0264       importer = new Import::FileListingImporter(firstURL);
0265       break;
0266 
0267     case Import::AMC:
0268       CHECK_SIZE;
0269       importer = new Import::AMCImporter(firstURL);
0270       break;
0271 
0272     case Import::Griffith:
0273       CHECK_SIZE;
0274       importer = new Import::GriffithImporter(firstURL);
0275       break;
0276 
0277     case Import::PDF:
0278       importer = new Import::PDFImporter(urls_);
0279       break;
0280 
0281     case Import::Referencer:
0282       CHECK_SIZE;
0283       importer = new Import::ReferencerImporter(firstURL);
0284       break;
0285 
0286     case Import::Delicious:
0287       CHECK_SIZE;
0288       importer = new Import::DeliciousImporter(firstURL);
0289       break;
0290 
0291     case Import::Goodreads:
0292       CHECK_SIZE;
0293       importer = new Import::GoodreadsImporter();
0294       break;
0295 
0296     case Import::GRS1:
0297       myDebug() << "GRS1 not implemented";
0298       break;
0299 
0300     case Import::CIW:
0301       importer = new Import::CIWImporter(urls_);
0302       break;
0303 
0304     case Import::VinoXML:
0305       CHECK_SIZE;
0306       importer = new Import::VinoXMLImporter(firstURL);
0307       break;
0308 
0309     case Import::BoardGameGeek:
0310       CHECK_SIZE;
0311       importer = new Import::BoardGameGeekImporter();
0312       break;
0313 
0314     case Import::LibraryThing:
0315       CHECK_SIZE;
0316       importer = new Import::LibraryThingImporter();
0317       break;
0318 
0319     case Import::Collectorz:
0320       CHECK_SIZE;
0321       importer = new Import::CollectorzImporter(firstURL);
0322       break;
0323 
0324     case Import::DataCrow:
0325       CHECK_SIZE;
0326       importer = new Import::DataCrowImporter(firstURL);
0327       break;
0328 
0329     case Import::MARC:
0330       CHECK_SIZE;
0331       importer = new Import::MarcImporter(firstURL);
0332       break;
0333 
0334     case Import::EBook:
0335       importer = new Import::EBookImporter(urls_);
0336       break;
0337 
0338     case Import::Discogs:
0339       CHECK_SIZE;
0340       importer = new Import::DiscogsImporter();
0341       break;
0342   }
0343   if(!importer) {
0344     myWarning() << "importer not created!";
0345     return nullptr;
0346   }
0347   importer->setCurrentCollection(Data::Document::self()->collection());
0348   return importer;
0349 #undef CHECK_SIZE
0350 }
0351 
0352 //static
0353 Tellico::Import::Importer* ImportDialog::importerForText(Tellico::Import::Format format_, const QString& text_) {
0354   Import::Importer* importer = nullptr;
0355   switch(format_) {
0356     case Import::Bibtex:
0357       importer = new Import::BibtexImporter(text_);
0358       break;
0359 
0360     default:
0361       break;
0362   }
0363 
0364   if(!importer) {
0365     myWarning() << "importer not created!";
0366     return nullptr;
0367   }
0368   importer->setCurrentCollection(Data::Document::self()->collection());
0369   return importer;
0370 }
0371 
0372 // static
0373 QString ImportDialog::fileFilter(Tellico::Import::Format format_) {
0374   QString text;
0375   switch(format_) {
0376     case Import::TellicoXML:
0377       text = i18n("Tellico Files") + QLatin1String(" (*.tc *.bc)") + QLatin1String(";;");
0378       text += i18n("XML Files") + QLatin1String(" (*.xml)") + QLatin1String(";;");
0379       break;
0380 
0381     case Import::Bibtex:
0382       text = i18n("Bibtex Files") + QLatin1String(" (*.bib)") + QLatin1String(";;");
0383       break;
0384 
0385     case Import::CSV:
0386       text = i18n("CSV Files") + QLatin1String(" (*.csv)") + QLatin1String(";;");
0387       break;
0388 
0389     case Import::Bibtexml:
0390     case Import::XSLT:
0391     case Import::MODS:
0392     case Import::Delicious:
0393     case Import::Griffith:
0394     case Import::Collectorz:
0395     case Import::DataCrow:
0396       text = i18n("XML Files") + QLatin1String(" (*.xml)") + QLatin1String(";;");
0397       break;
0398 
0399     case Import::RIS:
0400       text = i18n("RIS Files") + QLatin1String(" (*.ris)") + QLatin1String(";;");
0401       break;
0402 
0403     case Import::GCstar:
0404       text = i18n("GCstar Data Files") + QLatin1String(" (*.gcs *.gcf)") + QLatin1String(";;");
0405       break;
0406 
0407     case Import::AMC:
0408       text = i18n("AMC Data Files") + QLatin1String(" (*.amc)") + QLatin1String(";;");
0409       break;
0410 
0411     case Import::PDF:
0412       text = i18n("PDF Files") + QLatin1String(" (*.pdf)") + QLatin1String(";;");
0413       break;
0414 
0415     case Import::Referencer:
0416       text = i18n("Referencer Files") + QLatin1String(" (*.reflib)") + QLatin1String(";;");
0417       break;
0418 
0419     case Import::CIW:
0420       text = i18n("CIW Files") + QLatin1String(" (*.ciw)") + QLatin1String(";;");
0421       break;
0422 
0423     case Import::VinoXML:
0424       text = i18n("VinoXML Data Files") + QLatin1String(" (*.vinoxml)") + QLatin1String(";;");
0425       text += i18n("XML Files") + QLatin1String(" (*.xml)") + QLatin1String(";;");
0426       break;
0427 
0428     case Import::EBook:
0429       // KFileMetaData has extractors that support mimetypes with these typical extensions
0430       text = i18n("eBook Files") + QLatin1String(" (*.epub *.fb2 *.fb2zip *.mobi)") + QLatin1String(";;");
0431       break;
0432 
0433     case Import::AudioFile:
0434     case Import::Alexandria:
0435     case Import::FreeDB:
0436     case Import::FileListing:
0437     case Import::GRS1:
0438     case Import::Goodreads:
0439     case Import::BoardGameGeek:
0440     case Import::LibraryThing:
0441     case Import::MARC:
0442     case Import::Discogs:
0443       break;
0444   }
0445 
0446   return text + i18n("All Files") + QLatin1String(" (*)");
0447 }
0448 
0449 // audio files are imported by directory
0450 // alexandria is a defined location, as is freedb
0451 // all others are files
0452 Tellico::Import::Target ImportDialog::importTarget(Tellico::Import::Format format_) {
0453   switch(format_) {
0454     case Import::AudioFile:
0455     case Import::FileListing:
0456       return Import::Dir;
0457     case Import::Alexandria:
0458     case Import::FreeDB:
0459     case Import::Goodreads:
0460     case Import::BoardGameGeek:
0461     case Import::LibraryThing:
0462     case Import::Discogs:
0463       return Import::None;
0464     default:
0465       return Import::File;
0466   }
0467 }
0468 
0469 QString ImportDialog::startDir(Tellico::Import::Format format_) {
0470   if(format_ == Import::GCstar) {
0471     QDir dir = QDir::home();
0472     // able to cd if exists and readable
0473     if(dir.cd(QStringLiteral(".local/share/gcstar/"))) {
0474       return dir.absolutePath();
0475     }
0476   }
0477   return QString();
0478 }
0479 
0480 void ImportDialog::slotOk() {
0481   // some importers, like the CSV importer, can validate their settings
0482   if(!m_importer || m_importer->validImport()) {
0483     accept();
0484   } else {
0485     myLog() << "not a valid import";
0486   }
0487 }
0488 
0489 void ImportDialog::slotUpdateAction() {
0490   m_importer->slotActionChanged(m_buttonGroup->checkedId());
0491 }
0492 
0493 // static
0494 Tellico::Data::CollPtr ImportDialog::importURL(Tellico::Import::Format format_, const QUrl& url_) {
0495   QScopedPointer<Import::Importer> imp(importer(format_, QList<QUrl>() << url_));
0496   if(!imp) {
0497     return Data::CollPtr();
0498   }
0499 
0500   Data::CollPtr c;
0501   {
0502     // do this in a block to ensure the progress item is deleted before the importer
0503     ProgressItem& item = ProgressManager::self()->newProgressItem(imp.data(), imp->progressLabel(), true);
0504     connect(imp.data(), &Import::Importer::signalTotalSteps,
0505             ProgressManager::self(), &ProgressManager::setTotalSteps);
0506     connect(imp.data(), &Import::Importer::signalProgress,
0507             ProgressManager::self(), &ProgressManager::setProgress);
0508     connect(&item, &ProgressItem::signalCancelled, imp.data(), &Import::Importer::slotCancel);
0509     ProgressItem::Done done(imp.data());
0510 
0511     c = imp->collection();
0512   }
0513   if(!c && !imp->statusMessage().isEmpty()) {
0514     GUI::Proxy::sorry(imp->statusMessage());
0515   }
0516   return c;
0517 }
0518 
0519 Tellico::Data::CollPtr ImportDialog::importText(Tellico::Import::Format format_, const QString& text_) {
0520   Import::Importer* imp = importerForText(format_, text_);
0521   if(!imp) {
0522     return Data::CollPtr();
0523   }
0524 
0525   // the Done() constructor crashes for some reason, so just don't use it
0526   // 5/18/19 -> uncomment the progress Done again
0527   ProgressItem& item = ProgressManager::self()->newProgressItem(imp, imp->progressLabel(), true);
0528   connect(imp, &Import::Importer::signalTotalSteps,
0529           ProgressManager::self(), &ProgressManager::setTotalSteps);
0530   connect(imp, &Import::Importer::signalProgress,
0531           ProgressManager::self(), &ProgressManager::setProgress);
0532   connect(&item, &ProgressItem::signalCancelled, imp, &Import::Importer::slotCancel);
0533   ProgressItem::Done done(imp);
0534 
0535   Data::CollPtr c = imp->collection();
0536   if(!c && !imp->statusMessage().isEmpty()) {
0537     GUI::Proxy::sorry(imp->statusMessage());
0538   }
0539   delete imp;
0540   return c;
0541 }