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 }