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 }