File indexing completed on 2024-05-12 05:09:29

0001 /***************************************************************************
0002     Copyright (C) 2007-2009 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 "crossreffetcher.h"
0026 #include "../translators/xslthandler.h"
0027 #include "../translators/tellicoimporter.h"
0028 #include "../utils/guiproxy.h"
0029 #include "../collection.h"
0030 #include "../entry.h"
0031 #include "../core/netaccess.h"
0032 #include "../images/imagefactory.h"
0033 #include "../utils/datafileregistry.h"
0034 #include "../tellico_debug.h"
0035 
0036 #include <KLocalizedString>
0037 #include <KIO/Job>
0038 #include <KIO/JobUiDelegate>
0039 #include <KConfigGroup>
0040 #include <KJobWidgets/KJobWidgets>
0041 
0042 #include <QLineEdit>
0043 #include <QLabel>
0044 #include <QFile>
0045 #include <QTextStream>
0046 #include <QGridLayout>
0047 #include <QPixmap>
0048 #include <QTextCodec>
0049 #include <QUrlQuery>
0050 
0051 #define CROSSREF_USE_UNIXREF
0052 
0053 namespace {
0054   static const char* CROSSREF_BASE_URL = "http://www.crossref.org/openurl/";
0055 }
0056 
0057 using namespace Tellico;
0058 using namespace Tellico::Fetch;
0059 using Tellico::Fetch::CrossRefFetcher;
0060 
0061 CrossRefFetcher::CrossRefFetcher(QObject* parent_)
0062     : Fetcher(parent_), m_xsltHandler(nullptr), m_job(nullptr), m_started(false) {
0063 }
0064 
0065 CrossRefFetcher::~CrossRefFetcher() {
0066   delete m_xsltHandler;
0067   m_xsltHandler = nullptr;
0068 }
0069 
0070 QString CrossRefFetcher::source() const {
0071   return m_name.isEmpty() ? defaultName() : m_name;
0072 }
0073 
0074 bool CrossRefFetcher::canFetch(int type) const {
0075   return type == Data::Collection::Bibtex;
0076 }
0077 
0078 void CrossRefFetcher::readConfigHook(const KConfigGroup& config_) {
0079   m_user = config_.readEntry("User");
0080   m_password = config_.readEntry("Password");
0081   m_email = config_.readEntry("Email");
0082 }
0083 
0084 void CrossRefFetcher::search() {
0085   m_started = true;
0086 
0087 //  myDebug() << "value = " << value_;
0088 
0089   QUrl u = searchURL(request().key(), request().value());
0090   if(u.isEmpty()) {
0091     stop();
0092     return;
0093   }
0094 
0095   if(m_email.isEmpty() && (m_user.isEmpty() || m_password.isEmpty())) {
0096     myDebug() << i18n("%1 requires a username and password.", source());
0097     message(i18n("%1 requires a username and password.", source()), MessageHandler::Error);
0098     stop();
0099     return;
0100   }
0101 
0102   m_job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
0103   KJobWidgets::setWindow(m_job, GUI::Proxy::widget());
0104   connect(m_job.data(), &KJob::result,
0105           this, &CrossRefFetcher::slotComplete);
0106 }
0107 
0108 void CrossRefFetcher::stop() {
0109   if(!m_started) {
0110     return;
0111   }
0112 //  myDebug();
0113   if(m_job) {
0114     m_job->kill();
0115     m_job = nullptr;
0116   }
0117   m_started = false;
0118   emit signalDone(this);
0119 }
0120 
0121 void CrossRefFetcher::slotComplete(KJob*) {
0122 //  myDebug();
0123 
0124   if(m_job->error()) {
0125     m_job->uiDelegate()->showErrorMessage();
0126     stop();
0127     return;
0128   }
0129 
0130   QByteArray data = m_job->data();
0131   if(data.isEmpty()) {
0132     myDebug() << "no data";
0133     stop();
0134     return;
0135   }
0136 
0137   // since the fetch is done, don't worry about holding the job pointer
0138   m_job = nullptr;
0139 #if 0
0140   myWarning() << "Remove debug from crossreffetcher.cpp";
0141   QFile f(QLatin1String("/tmp/test.xml"));
0142   if(f.open(QIODevice::WriteOnly)) {
0143     QTextStream t(&f);
0144     t.setCodec("UTF-8");
0145     t << data;
0146   }
0147   f.close();
0148 #endif
0149 
0150   if(!m_xsltHandler) {
0151     initXSLTHandler();
0152     if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading
0153       stop();
0154       return;
0155     }
0156   }
0157 
0158   // assume result is always utf-8
0159   QString str = m_xsltHandler->applyStylesheet(QString::fromUtf8(data.constData(), data.size()));
0160   Import::TellicoImporter imp(str);
0161   Data::CollPtr coll = imp.collection();
0162 
0163   if(!coll) {
0164     myDebug() << "no valid result";
0165     stop();
0166     return;
0167   }
0168 
0169   Data::EntryList entries = coll->entries();
0170   foreach(Data::EntryPtr entry, entries) {
0171     if(!m_started) {
0172       // might get aborted
0173       break;
0174     }
0175 
0176     FetchResult* r = new FetchResult(this, entry);
0177     m_entries.insert(r->uid, Data::EntryPtr(entry));
0178     emit signalResultFound(r);
0179   }
0180 
0181   stop(); // required
0182 }
0183 
0184 Tellico::Data::EntryPtr CrossRefFetcher::fetchEntryHook(uint uid_) {
0185   Data::EntryPtr entry = m_entries[uid_];
0186   // if URL but no cover image, fetch it
0187   if(!entry->field(QStringLiteral("url")).isEmpty()) {
0188     Data::CollPtr coll = entry->collection();
0189     Data::FieldPtr field = coll->fieldByName(QStringLiteral("cover"));
0190     if(!field && !coll->imageFields().isEmpty()) {
0191       field = coll->imageFields().front();
0192     } else if(!field) {
0193       field = Data::Field::createDefaultField(Data::Field::FrontCoverField);
0194       coll->addField(field);
0195     }
0196     if(entry->field(field).isEmpty()) {
0197       QPixmap pix = NetAccess::filePreview(QUrl::fromUserInput(entry->field(QStringLiteral("url"))));
0198       if(!pix.isNull()) {
0199         QString id = ImageFactory::addImage(pix, QStringLiteral("PNG"));
0200         if(!id.isEmpty()) {
0201           entry->setField(field, id);
0202         }
0203       }
0204     }
0205   }
0206   return entry;
0207 }
0208 
0209 void CrossRefFetcher::initXSLTHandler() {
0210 #ifdef CROSSREF_USE_UNIXREF
0211   QString xsltfile = DataFileRegistry::self()->locate(QStringLiteral("unixref2tellico.xsl"));
0212 #else
0213   QString xsltfile = DataFileRegistry::self()->locate(QLatin1String("crossref2tellico.xsl"));
0214 #endif
0215   if(xsltfile.isEmpty()) {
0216 #ifdef CROSSREF_USE_UNIXREF
0217     myWarning() << "can not locate xslt file: unixref2tellico.xsl";
0218 #else
0219     myWarning() << "can not locate xslt file: crossref2tellico.xsl";
0220 #endif
0221     return;
0222   }
0223 
0224   QUrl u = QUrl::fromLocalFile(xsltfile);
0225 
0226   delete m_xsltHandler;
0227   m_xsltHandler = new XSLTHandler(u);
0228   if(!m_xsltHandler->isValid()) {
0229     myWarning() << "error in crossref2tellico.xsl.";
0230     delete m_xsltHandler;
0231     m_xsltHandler = nullptr;
0232     return;
0233   }
0234 }
0235 
0236 QUrl CrossRefFetcher::searchURL(FetchKey key_, const QString& value_) const {
0237   QUrl u(QString::fromLatin1(CROSSREF_BASE_URL));
0238   QUrlQuery q;
0239   q.addQueryItem(QStringLiteral("noredirect"), QStringLiteral("true"));
0240   q.addQueryItem(QStringLiteral("multihit"), QStringLiteral("true"));
0241 #ifdef CROSSREF_USE_UNIXREF
0242   q.addQueryItem(QStringLiteral("format"), QStringLiteral("unixref"));
0243 #endif
0244   if(m_email.isEmpty()) {
0245     q.addQueryItem(QStringLiteral("pid"), QStringLiteral("%1:%2").arg(m_user, m_password));
0246   } else {
0247     q.addQueryItem(QStringLiteral("pid"), m_email);
0248   }
0249 
0250   switch(key_) {
0251     case DOI:
0252       q.addQueryItem(QStringLiteral("rft_id"), QStringLiteral("info:doi/%1").arg(value_));
0253       break;
0254 
0255     default:
0256       myWarning() << source() << "- key not recognized:" << key_;
0257       return QUrl();
0258   }
0259   u.setQuery(q);
0260 //  myDebug() << "url: " << u.url();
0261   return u;
0262 }
0263 
0264 Tellico::Fetch::FetchRequest CrossRefFetcher::updateRequest(Data::EntryPtr entry_) {
0265   QString doi = entry_->field(QStringLiteral("doi"));
0266   if(!doi.isEmpty()) {
0267     return FetchRequest(Fetch::DOI, doi);
0268   }
0269 
0270 #if 0
0271   // optimistically try searching for title and rely on Collection::sameEntry() to figure things out
0272   QString t = entry_->field(QLatin1String("title"));
0273   if(!t.isEmpty()) {
0274     return FetchRequest(Fetch::Title, t);
0275   }
0276 #endif
0277   return FetchRequest();
0278 }
0279 
0280 Tellico::Fetch::ConfigWidget* CrossRefFetcher::configWidget(QWidget* parent_) const {
0281   return new CrossRefFetcher::ConfigWidget(parent_, this);
0282 }
0283 
0284 QString CrossRefFetcher::defaultName() {
0285   return QStringLiteral("CrossRef"); // no translation
0286 }
0287 
0288 QString CrossRefFetcher::defaultIcon() {
0289   return favIcon("https://www.crossref.org");
0290 }
0291 
0292 CrossRefFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const CrossRefFetcher* fetcher_)
0293     : Fetch::ConfigWidget(parent_) {
0294   QGridLayout* l = new QGridLayout(optionsWidget());
0295   l->setSpacing(4);
0296   l->setColumnStretch(1, 10);
0297 
0298   int row = 0;
0299 
0300   QLabel* al = new QLabel(i18n("Registration is required for accessing this data source. "
0301                                "If you agree to the terms and conditions, <a href='%1'>sign "
0302                                "up for an account</a>, and enter your information below.",
0303                                 QLatin1String("http://www.crossref.org/requestaccount/")),
0304                           optionsWidget());
0305   al->setOpenExternalLinks(true);
0306   al->setWordWrap(true);
0307   l->addWidget(al, row, 0, 1, 2);
0308   // richtext gets weird with size
0309   al->setMinimumWidth(al->sizeHint().width());
0310 
0311   QLabel* label = new QLabel(i18n("&Username: "), optionsWidget());
0312   l->addWidget(label, ++row, 0);
0313   m_userEdit = new QLineEdit(optionsWidget());
0314   connect(m_userEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified);
0315   l->addWidget(m_userEdit, row, 1);
0316   QString w = i18n("A username and password is required to access the CrossRef service.");
0317   label->setWhatsThis(w);
0318   m_userEdit->setWhatsThis(w);
0319   label->setBuddy(m_userEdit);
0320 
0321   label = new QLabel(i18n("&Password: "), optionsWidget());
0322   l->addWidget(label, ++row, 0);
0323   m_passEdit = new QLineEdit(optionsWidget());
0324 //  m_passEdit->setEchoMode(QLineEdit::PasswordEchoOnEdit);
0325   connect(m_passEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified);
0326   l->addWidget(m_passEdit, row, 1);
0327   label->setWhatsThis(w);
0328   m_passEdit->setWhatsThis(w);
0329   label->setBuddy(m_passEdit);
0330 
0331   label = new QLabel(i18n("For some accounts, only an email address is required."), optionsWidget());
0332   l->addWidget(label, ++row, 0, 1, 2);
0333 
0334   label = new QLabel(i18n("Email: "), optionsWidget());
0335   l->addWidget(label, ++row, 0);
0336   m_emailEdit = new QLineEdit(optionsWidget());
0337   connect(m_emailEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified);
0338   l->addWidget(m_emailEdit, row, 1);
0339   label->setBuddy(m_emailEdit);
0340 
0341   if(fetcher_) {
0342     m_userEdit->setText(fetcher_->m_user);
0343     m_passEdit->setText(fetcher_->m_password);
0344     m_emailEdit->setText(fetcher_->m_email);
0345   }
0346 }
0347 
0348 void CrossRefFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) {
0349   QString s = m_userEdit->text().trimmed();
0350   if(!s.isEmpty()) {
0351     config_.writeEntry("User", s);
0352   }
0353   s = m_passEdit->text().trimmed();
0354   if(!s.isEmpty()) {
0355     config_.writeEntry("Password", s);
0356   }
0357   s = m_emailEdit->text().trimmed();
0358   if(!s.isEmpty()) {
0359     config_.writeEntry("Email", s);
0360   }
0361 }
0362 
0363 QString CrossRefFetcher::ConfigWidget::preferredName() const {
0364   return CrossRefFetcher::defaultName();
0365 }