File indexing completed on 2024-04-28 16:32:01

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 "utils/datafileregistry.h"
0057 
0058 #include <KLocalizedString>
0059 #include <KStandardGuiItem>
0060 
0061 #include <QGroupBox>
0062 #include <QButtonGroup>
0063 #include <QRadioButton>
0064 #include <QCheckBox>
0065 #include <QTimer>
0066 #include <QVBoxLayout>
0067 #include <QDialogButtonBox>
0068 #include <QPushButton>
0069 
0070 using Tellico::ImportDialog;
0071 
0072 ImportDialog::ImportDialog(Tellico::Import::Format format_, const QList<QUrl>& urls_, QWidget* parent_)
0073     : QDialog(parent_),
0074       m_importer(importer(format_, urls_)) {
0075   setModal(true);
0076   setWindowTitle(i18n("Import Options"));
0077 
0078   QVBoxLayout* mainLayout = new QVBoxLayout();
0079   setLayout(mainLayout);
0080 
0081   QWidget* widget = new QWidget(this);
0082   mainLayout->addWidget(widget);
0083   QVBoxLayout* topLayout = new QVBoxLayout(widget);
0084 
0085   QGroupBox* groupBox = new QGroupBox(i18n("Import Options"), widget);
0086   QVBoxLayout* vlay = new QVBoxLayout(groupBox);
0087   topLayout->addWidget(groupBox, 0);
0088 
0089   m_radioReplace = new QRadioButton(i18n("&Replace current collection"), groupBox);
0090   m_radioReplace->setWhatsThis(i18n("Replace the current collection with the contents "
0091                                      "of the imported file."));
0092   m_radioAppend = new QRadioButton(i18n("A&ppend to current collection"), groupBox);
0093   m_radioAppend->setWhatsThis(i18n("Append the contents of the imported file to the "
0094                                    "current collection. This is only possible when the "
0095                                    "collection types match."));
0096   m_radioMerge = new QRadioButton(i18n("&Merge with current collection"), groupBox);
0097   m_radioMerge->setWhatsThis(i18n("Merge the contents of the imported file to the "
0098                                   "current collection. This is only possible when the "
0099                                   "collection types match. Entries must match exactly "
0100                                   "in order to be merged."));
0101   if(m_importer->canImport(Data::Document::self()->collection()->type())) {
0102     // append by default?
0103     m_radioAppend->setChecked(true);
0104   } else {
0105     m_radioReplace->setChecked(true);
0106     m_radioAppend->setEnabled(false);
0107     m_radioMerge->setEnabled(false);
0108   }
0109 
0110   vlay->addWidget(m_radioReplace);
0111   vlay->addWidget(m_radioAppend);
0112   vlay->addWidget(m_radioMerge);
0113 
0114   m_buttonGroup = new QButtonGroup(widget);
0115   m_buttonGroup->addButton(m_radioReplace, Import::Replace);
0116   m_buttonGroup->addButton(m_radioAppend, Import::Append);
0117   m_buttonGroup->addButton(m_radioMerge, Import::Merge);
0118 
0119   QWidget* w = m_importer->widget(widget);
0120 //  m_importer->readOptions(KSharedConfig::openConfig());
0121   if(w) {
0122     w->layout()->setMargin(0);
0123     topLayout->addWidget(w, 0);
0124   }
0125 
0126 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
0127   connect(m_buttonGroup, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked),
0128           m_importer, &Tellico::Import::Importer::slotActionChanged);
0129 #else
0130   connect(m_buttonGroup, &QButtonGroup::idClicked,
0131           m_importer, &Tellico::Import::Importer::slotActionChanged);
0132 #endif
0133 
0134   topLayout->addStretch();
0135 
0136   QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
0137   mainLayout->addWidget(buttonBox);
0138   QPushButton* okButton = buttonBox->button(QDialogButtonBox::Ok);
0139   okButton->setDefault(true);
0140   okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0141   connect(okButton, &QPushButton::clicked, this, &ImportDialog::slotOk);
0142   connect(buttonBox, &QDialogButtonBox::accepted, this, &ImportDialog::accept);
0143   connect(buttonBox, &QDialogButtonBox::rejected, this, &ImportDialog::reject);
0144 
0145   KGuiItem ok = KStandardGuiItem::ok();
0146   ok.setText(i18n("&Import"));
0147   KGuiItem::assign(okButton, ok);
0148 
0149   // want to grab default button action, too
0150   // since the importer might do something with widgets, don't just call it, do it after layout is done
0151   QTimer::singleShot(0, this, &ImportDialog::slotUpdateAction);
0152 }
0153 
0154 ImportDialog::~ImportDialog() {
0155   delete m_importer;
0156   m_importer = nullptr;
0157 }
0158 
0159 Tellico::Data::CollPtr ImportDialog::collection() {
0160   if(m_importer && !m_coll) {
0161     ProgressItem& item = ProgressManager::self()->newProgressItem(m_importer, m_importer->progressLabel(), true);
0162     connect(m_importer, &Import::Importer::signalTotalSteps,
0163             ProgressManager::self(), &ProgressManager::setTotalSteps);
0164     connect(m_importer, &Import::Importer::signalProgress,
0165             ProgressManager::self(), &ProgressManager::setProgress);
0166     connect(&item, &ProgressItem::signalCancelled, m_importer, &Import::Importer::slotCancel);
0167     ProgressItem::Done done(m_importer);
0168     m_coll = m_importer->collection();
0169   }
0170   return m_coll;
0171 }
0172 
0173 QString ImportDialog::statusMessage() const {
0174   return m_importer ? m_importer->statusMessage() : QString();
0175 }
0176 
0177 Tellico::Import::Action ImportDialog::action() const {
0178   if(m_radioReplace->isChecked()) {
0179     return Import::Replace;
0180   } else if(m_radioAppend->isChecked()) {
0181     return Import::Append;
0182   } else {
0183     return Import::Merge;
0184   }
0185 }
0186 
0187 // static
0188 Tellico::Import::Importer* ImportDialog::importer(Tellico::Import::Format format_, const QList<QUrl>& urls_) {
0189 #define CHECK_SIZE if(urls_.size() > 1) myWarning() << "only importing first URL"
0190   QUrl firstURL = urls_.isEmpty() ? QUrl() : urls_[0];
0191   Import::Importer* importer = nullptr;
0192   switch(format_) {
0193     case Import::TellicoXML:
0194       CHECK_SIZE;
0195       importer = new Import::TellicoImporter(firstURL);
0196       break;
0197 
0198     case Import::Bibtex:
0199       importer = new Import::BibtexImporter(urls_);
0200 #ifndef ENABLE_BTPARSE
0201       myLog() << "Bibtex importing is not available due to lack of btparse library";
0202 #endif
0203       break;
0204 
0205     case Import::Bibtexml:
0206       CHECK_SIZE;
0207       importer = new Import::BibtexmlImporter(firstURL);
0208       break;
0209 
0210     case Import::CSV:
0211       CHECK_SIZE;
0212       importer = new Import::CSVImporter(firstURL);
0213       break;
0214 
0215     case Import::XSLT:
0216       CHECK_SIZE;
0217       importer = new Import::XSLTImporter(firstURL);
0218       break;
0219 
0220     case Import::MODS:
0221       CHECK_SIZE;
0222       importer = new Import::XSLTImporter(firstURL);
0223       {
0224         QString xsltFile = DataFileRegistry::self()->locate(QStringLiteral("mods2tellico.xsl"));
0225         if(!xsltFile.isEmpty()) {
0226           QUrl u = QUrl::fromLocalFile(xsltFile);
0227           static_cast<Import::XSLTImporter*>(importer)->setXSLTURL(u);
0228         } else {
0229           myWarning() << "unable to find mods2tellico.xml!";
0230         }
0231       }
0232       break;
0233 
0234     case Import::AudioFile:
0235       CHECK_SIZE;
0236       importer = new Import::AudioFileImporter(firstURL);
0237       break;
0238 
0239     case Import::Alexandria:
0240       CHECK_SIZE;
0241       importer = new Import::AlexandriaImporter();
0242       break;
0243 
0244     case Import::FreeDB:
0245       CHECK_SIZE;
0246       importer = new Import::FreeDBImporter();
0247       break;
0248 
0249     case Import::RIS:
0250       importer = new Import::RISImporter(urls_);
0251       break;
0252 
0253     case Import::GCstar:
0254       CHECK_SIZE;
0255       importer = new Import::GCstarImporter(firstURL);
0256       break;
0257 
0258     case Import::FileListing:
0259       CHECK_SIZE;
0260       importer = new Import::FileListingImporter(firstURL);
0261       break;
0262 
0263     case Import::AMC:
0264       CHECK_SIZE;
0265       importer = new Import::AMCImporter(firstURL);
0266       break;
0267 
0268     case Import::Griffith:
0269       CHECK_SIZE;
0270       importer = new Import::GriffithImporter(firstURL);
0271       break;
0272 
0273     case Import::PDF:
0274       importer = new Import::PDFImporter(urls_);
0275       break;
0276 
0277     case Import::Referencer:
0278       CHECK_SIZE;
0279       importer = new Import::ReferencerImporter(firstURL);
0280       break;
0281 
0282     case Import::Delicious:
0283       CHECK_SIZE;
0284       importer = new Import::DeliciousImporter(firstURL);
0285       break;
0286 
0287     case Import::Goodreads:
0288       CHECK_SIZE;
0289       importer = new Import::GoodreadsImporter();
0290       break;
0291 
0292     case Import::GRS1:
0293       myDebug() << "GRS1 not implemented";
0294       break;
0295 
0296     case Import::CIW:
0297       importer = new Import::CIWImporter(urls_);
0298       break;
0299 
0300     case Import::VinoXML:
0301       CHECK_SIZE;
0302       importer = new Import::VinoXMLImporter(firstURL);
0303       break;
0304 
0305     case Import::BoardGameGeek:
0306       CHECK_SIZE;
0307       importer = new Import::BoardGameGeekImporter();
0308       break;
0309 
0310     case Import::LibraryThing:
0311       CHECK_SIZE;
0312       importer = new Import::LibraryThingImporter();
0313       break;
0314 
0315     case Import::Collectorz:
0316       CHECK_SIZE;
0317       importer = new Import::CollectorzImporter(firstURL);
0318       break;
0319   }
0320   if(!importer) {
0321     myWarning() << "importer not created!";
0322     return nullptr;
0323   }
0324   importer->setCurrentCollection(Data::Document::self()->collection());
0325   return importer;
0326 #undef CHECK_SIZE
0327 }
0328 
0329 //static
0330 Tellico::Import::Importer* ImportDialog::importerForText(Tellico::Import::Format format_, const QString& text_) {
0331   Import::Importer* importer = nullptr;
0332   switch(format_) {
0333     case Import::Bibtex:
0334       importer = new Import::BibtexImporter(text_);
0335       break;
0336 
0337     default:
0338       break;
0339   }
0340 
0341   if(!importer) {
0342     myWarning() << "importer not created!";
0343     return nullptr;
0344   }
0345   importer->setCurrentCollection(Data::Document::self()->collection());
0346   return importer;
0347 }
0348 
0349 // static
0350 QString ImportDialog::fileFilter(Tellico::Import::Format format_) {
0351   QString text;
0352   switch(format_) {
0353     case Import::TellicoXML:
0354       text = i18n("Tellico Files") + QLatin1String(" (*.tc *.bc)") + QLatin1String(";;");
0355       text += i18n("XML Files") + QLatin1String(" (*.xml)") + QLatin1String(";;");
0356       break;
0357 
0358     case Import::Bibtex:
0359       text = i18n("Bibtex Files") + QLatin1String(" (*.bib)") + QLatin1String(";;");
0360       break;
0361 
0362     case Import::CSV:
0363       text = i18n("CSV Files") + QLatin1String(" (*.csv)") + QLatin1String(";;");
0364       break;
0365 
0366     case Import::Bibtexml:
0367     case Import::XSLT:
0368     case Import::MODS:
0369     case Import::Delicious:
0370     case Import::Griffith:
0371     case Import::Collectorz:
0372       text = i18n("XML Files") + QLatin1String(" (*.xml)") + QLatin1String(";;");
0373       break;
0374 
0375     case Import::RIS:
0376       text = i18n("RIS Files") + QLatin1String(" (*.ris)") + QLatin1String(";;");
0377       break;
0378 
0379     case Import::GCstar:
0380       text = i18n("GCstar Data Files") + QLatin1String(" (*.gcs *.gcf)") + QLatin1String(";;");
0381       break;
0382 
0383     case Import::AMC:
0384       text = i18n("AMC Data Files") + QLatin1String(" (*.amc)") + QLatin1String(";;");
0385       break;
0386 
0387     case Import::PDF:
0388       text = i18n("PDF Files") + QLatin1String(" (*.pdf)") + QLatin1String(";;");
0389       break;
0390 
0391     case Import::Referencer:
0392       text = i18n("Referencer Files") + QLatin1String(" (*.reflib)") + QLatin1String(";;");
0393       break;
0394 
0395     case Import::CIW:
0396       text = i18n("CIW Files") + QLatin1String(" (*.ciw)") + QLatin1String(";;");
0397       break;
0398 
0399     case Import::VinoXML:
0400       text = i18n("VinoXML Data Files") + QLatin1String(" (*.vinoxml)") + QLatin1String(";;");
0401       text += i18n("XML Files") + QLatin1String(" (*.xml)") + QLatin1String(";;");
0402       break;
0403 
0404     case Import::AudioFile:
0405     case Import::Alexandria:
0406     case Import::FreeDB:
0407     case Import::FileListing:
0408     case Import::GRS1:
0409     case Import::Goodreads:
0410     case Import::BoardGameGeek:
0411     case Import::LibraryThing:
0412       break;
0413   }
0414 
0415   return text + i18n("All Files") + QLatin1String(" (*)");
0416 }
0417 
0418 // audio files are imported by directory
0419 // alexandria is a defined location, as is freedb
0420 // all others are files
0421 Tellico::Import::Target ImportDialog::importTarget(Tellico::Import::Format format_) {
0422   switch(format_) {
0423     case Import::AudioFile:
0424     case Import::FileListing:
0425       return Import::Dir;
0426     case Import::Alexandria:
0427     case Import::FreeDB:
0428     case Import::Goodreads:
0429     case Import::BoardGameGeek:
0430     case Import::LibraryThing:
0431       return Import::None;
0432     default:
0433       return Import::File;
0434   }
0435 }
0436 
0437 QString ImportDialog::startDir(Tellico::Import::Format format_) {
0438   if(format_ == Import::GCstar) {
0439     QDir dir = QDir::home();
0440     // able to cd if exists and readable
0441     if(dir.cd(QStringLiteral(".local/share/gcstar/"))) {
0442       return dir.absolutePath();
0443     }
0444   }
0445   return QString();
0446 }
0447 
0448 void ImportDialog::slotOk() {
0449   // some importers, like the CSV importer, can validate their settings
0450   if(!m_importer || m_importer->validImport()) {
0451     accept();
0452   } else {
0453     myLog() << "not a valid import";
0454   }
0455 }
0456 
0457 void ImportDialog::slotUpdateAction() {
0458   m_importer->slotActionChanged(m_buttonGroup->checkedId());
0459 }
0460 
0461 // static
0462 Tellico::Data::CollPtr ImportDialog::importURL(Tellico::Import::Format format_, const QUrl& url_) {
0463   Import::Importer* imp = importer(format_, QList<QUrl>() << url_);
0464   if(!imp) {
0465     return Data::CollPtr();
0466   }
0467 
0468   ProgressItem& item = ProgressManager::self()->newProgressItem(imp, imp->progressLabel(), true);
0469   connect(imp, &Import::Importer::signalTotalSteps,
0470           ProgressManager::self(), &ProgressManager::setTotalSteps);
0471   connect(imp, &Import::Importer::signalProgress,
0472           ProgressManager::self(), &ProgressManager::setProgress);
0473   connect(&item, &ProgressItem::signalCancelled, imp, &Import::Importer::slotCancel);
0474   ProgressItem::Done done(imp);
0475 
0476   Data::CollPtr c = imp->collection();
0477   if(!c && !imp->statusMessage().isEmpty()) {
0478     GUI::Proxy::sorry(imp->statusMessage());
0479   }
0480   delete imp;
0481   return c;
0482 }
0483 
0484 Tellico::Data::CollPtr ImportDialog::importText(Tellico::Import::Format format_, const QString& text_) {
0485   Import::Importer* imp = importerForText(format_, text_);
0486   if(!imp) {
0487     return Data::CollPtr();
0488   }
0489 
0490   // the Done() constructor crashes for some reason, so just don't use it
0491   // 5/18/19 -> uncomment the progress Done again
0492   ProgressItem& item = ProgressManager::self()->newProgressItem(imp, imp->progressLabel(), true);
0493   connect(imp, &Import::Importer::signalTotalSteps,
0494           ProgressManager::self(), &ProgressManager::setTotalSteps);
0495   connect(imp, &Import::Importer::signalProgress,
0496           ProgressManager::self(), &ProgressManager::setProgress);
0497   connect(&item, &ProgressItem::signalCancelled, imp, &Import::Importer::slotCancel);
0498   ProgressItem::Done done(imp);
0499 
0500   Data::CollPtr c = imp->collection();
0501   if(!c && !imp->statusMessage().isEmpty()) {
0502     GUI::Proxy::sorry(imp->statusMessage());
0503   }
0504   delete imp;
0505   return c;
0506 }