/*************************************************************************** copyright : (C) 2007 by Robby Stephenson email : robby@periapsis.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of version 2 of the GNU General Public License as * * published by the Free Software Foundation; * * * ***************************************************************************/ #include "crossreffetcher.h" #include "messagehandler.h" #include "../translators/xslthandler.h" #include "../translators/tellicoimporter.h" #include "../tellico_kernel.h" #include "../tellico_utils.h" #include "../collection.h" #include "../entry.h" #include "../core/netaccess.h" #include "../imagefactory.h" #include "../tellico_debug.h" #include #include #include #include #include #include #include #include #include // #define CROSSREF_TEST #define CROSSREF_USE_UNIXREF namespace { static const char* CROSSREF_BASE_URL = "http://www.crossref.org/openurl/?url_ver=Z39.88-2004&noredirect=true"; } using Tellico::Fetch::CrossRefFetcher; CrossRefFetcher::CrossRefFetcher(TQObject* parent_) : Fetcher(parent_), m_xsltHandler(0), m_job(0), m_started(false) { } CrossRefFetcher::~CrossRefFetcher() { delete m_xsltHandler; m_xsltHandler = 0; } TQString CrossRefFetcher::defaultName() { return TQString::fromLatin1("CrossRef"); } TQString CrossRefFetcher::source() const { return m_name.isEmpty() ? defaultName() : m_name; } bool CrossRefFetcher::canFetch(int type) const { return type == Data::Collection::Bibtex; } void CrossRefFetcher::readConfigHook(const KConfigGroup& config_) { TQString s = config_.readEntry("User"); if(!s.isEmpty()) { m_user = s; } s = config_.readEntry("Password"); if(!s.isEmpty()) { m_password = s; } } void CrossRefFetcher::search(FetchKey key_, const TQString& value_) { m_key = key_; m_value = value_.stripWhiteSpace(); m_started = true; if(m_user.isEmpty() || m_password.isEmpty()) { message(i18n("%1 requires a username and password.").tqarg(source()), MessageHandler::Warning); stop(); return; } if(!canFetch(Kernel::self()->collectionType())) { message(i18n("%1 does not allow searching for this collection type.").tqarg(source()), MessageHandler::Warning); stop(); return; } m_data.truncate(0); // myDebug() << "CrossRefFetcher::search() - value = " << value_ << endl; KURL u = searchURL(m_key, m_value); if(u.isEmpty()) { stop(); return; } m_job = KIO::get(u, false, false); connect(m_job, TQT_SIGNAL(data(KIO::Job*, const TQByteArray&)), TQT_SLOT(slotData(KIO::Job*, const TQByteArray&))); connect(m_job, TQT_SIGNAL(result(KIO::Job*)), TQT_SLOT(slotComplete(KIO::Job*))); } void CrossRefFetcher::stop() { if(!m_started) { return; } // myDebug() << "CrossRefFetcher::stop()" << endl; if(m_job) { m_job->kill(); m_job = 0; } m_data.truncate(0); m_started = false; emit signalDone(this); } void CrossRefFetcher::slotData(KIO::Job*, const TQByteArray& data_) { TQDataStream stream(m_data, IO_WriteOnly | IO_Append); stream.writeRawBytes(data_.data(), data_.size()); } void CrossRefFetcher::slotComplete(KIO::Job* job_) { // myDebug() << "CrossRefFetcher::slotComplete()" << endl; // since the fetch is done, don't worry about holding the job pointer m_job = 0; if(job_->error()) { job_->showErrorDialog(Kernel::self()->widget()); stop(); return; } if(m_data.isEmpty()) { myDebug() << "CrossRefFetcher::slotComplete() - no data" << endl; stop(); return; } #if 0 kdWarning() << "Remove debug from crossreffetcher.cpp" << endl; TQFile f(TQString::fromLatin1("/tmp/test.xml")); if(f.open(IO_WriteOnly)) { TQTextStream t(&f); t.setEncoding(TQTextStream::UnicodeUTF8); t << TQCString(m_data, m_data.size()+1); } f.close(); #endif if(!m_xsltHandler) { initXSLTHandler(); if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading stop(); return; } } // assume result is always utf-8 TQString str = m_xsltHandler->applyStylesheet(TQString::fromUtf8(m_data, m_data.size())); Import::TellicoImporter imp(str); Data::CollPtr coll = imp.collection(); if(!coll) { myDebug() << "CrossRefFetcher::slotComplete() - no valid result" << endl; stop(); return; } Data::EntryVec entries = coll->entries(); for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) { if(!m_started) { // might get aborted break; } TQString desc = entry->field(TQString::fromLatin1("author")) + TQChar('/') + entry->field(TQString::fromLatin1("publisher")); if(!entry->field(TQString::fromLatin1("year")).isEmpty()) { desc += TQChar('/') + entry->field(TQString::fromLatin1("year")); } SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(TQString::fromLatin1("isbn"))); m_entries.insert(r->uid, Data::EntryPtr(entry)); emit signalResultFound(r); } stop(); // required } Tellico::Data::EntryPtr CrossRefFetcher::fetchEntry(uint uid_) { Data::EntryPtr entry = m_entries[uid_]; // if URL but no cover image, fetch it if(!entry->field(TQString::fromLatin1("url")).isEmpty()) { Data::CollPtr coll = entry->collection(); Data::FieldPtr field = coll->fieldByName(TQString::fromLatin1("cover")); if(!field && !coll->imageFields().isEmpty()) { field = coll->imageFields().front(); } else if(!field) { field = new Data::Field(TQString::fromLatin1("cover"), i18n("Front Cover"), Data::Field::Image); coll->addField(field); } if(entry->field(field).isEmpty()) { TQPixmap pix = NetAccess::filePreview(entry->field(TQString::fromLatin1("url"))); if(!pix.isNull()) { TQString id = ImageFactory::addImage(pix, TQString::fromLatin1("PNG")); if(!id.isEmpty()) { entry->setField(field, id); } } } } return entry; } void CrossRefFetcher::initXSLTHandler() { #ifdef CROSSREF_USE_UNIXREF TQString xsltfile = locate("appdata", TQString::fromLatin1("unixref2tellico.xsl")); #else TQString xsltfile = locate("appdata", TQString::fromLatin1("crossref2tellico.xsl")); #endif if(xsltfile.isEmpty()) { kdWarning() << "CrossRefFetcher::initXSLTHandler() - can not locate xslt file." << endl; return; } KURL u; u.setPath(xsltfile); delete m_xsltHandler; m_xsltHandler = new XSLTHandler(u); if(!m_xsltHandler->isValid()) { kdWarning() << "CrossRefFetcher::initXSLTHandler() - error in crossref2tellico.xsl." << endl; delete m_xsltHandler; m_xsltHandler = 0; return; } } KURL CrossRefFetcher::searchURL(FetchKey key_, const TQString& value_) const { KURL u(TQString::fromLatin1(CROSSREF_BASE_URL)); #ifdef CROSSREF_USE_UNIXREF u.addQueryItem(TQString::fromLatin1("format"), TQString::fromLatin1("unixref")); #endif u.addQueryItem(TQString::fromLatin1("req_dat"), TQString::fromLatin1("ourl_%1:%2").tqarg(m_user, m_password)); switch(key_) { case DOI: u.addQueryItem(TQString::fromLatin1("rft_id"), TQString::fromLatin1("info:doi/%1").tqarg(value_)); break; default: kdWarning() << "CrossRefFetcher::search() - key not recognized: " << m_key << endl; return KURL(); } #ifdef CROSSREF_TEST u = KURL::fromPathOrURL(TQString::fromLatin1("/home/robby/crossref.xml")); #endif myDebug() << "CrossRefFetcher::search() - url: " << u.url() << endl; return u; } void CrossRefFetcher::updateEntry(Data::EntryPtr entry_) { TQString doi = entry_->field(TQString::fromLatin1("doi")); if(!doi.isEmpty()) { search(Fetch::DOI, doi); return; } #if 0 // optimistically try searching for title and rely on Collection::sameEntry() to figure things out TQString t = entry_->field(TQString::fromLatin1("title")); if(!t.isEmpty()) { m_limit = 10; // raise limit so more possibility of match search(Fetch::Title, t); return; } #endif myDebug() << "CrossRefFetcher::updateEntry() - insufficient info to search" << endl; emit signalDone(this); // always need to emit this if not continuing with the search } void CrossRefFetcher::updateEntrySynchronous(Data::EntryPtr entry) { if(!entry) { return; } if(m_user.isEmpty() || m_password.isEmpty()) { myDebug() << "CrossRefFetcher::updateEntrySynchronous() - username and password is required" << endl; return; } TQString doi = entry->field(TQString::fromLatin1("doi")); if(doi.isEmpty()) { return; } KURL u = searchURL(DOI, doi); TQString xml = FileHandler::readTextFile(u, true, true); if(xml.isEmpty()) { return; } if(!m_xsltHandler) { initXSLTHandler(); if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading return; } } // assume result is always utf-8 TQString str = m_xsltHandler->applyStylesheet(xml); Import::TellicoImporter imp(str); Data::CollPtr coll = imp.collection(); if(coll && coll->entryCount() > 0) { myLog() << "CrossRefFetcher::updateEntrySynchronous() - found DOI result, merging" << endl; Data::Collection::mergeEntry(entry, coll->entries().front(), false /*overwrite*/); } } Tellico::Fetch::ConfigWidget* CrossRefFetcher::configWidget(TQWidget* parent_) const { return new CrossRefFetcher::ConfigWidget(parent_, this); } CrossRefFetcher::ConfigWidget::ConfigWidget(TQWidget* parent_, const CrossRefFetcher* fetcher_) : Fetch::ConfigWidget(parent_) { TQGridLayout* l = new TQGridLayout(optionsWidget(), 4, 2); l->setSpacing(4); l->setColStretch(1, 10); int row = 0; KActiveLabel* al = new KActiveLabel(i18n("CrossRef requires an account for access. " "Please read the terms and conditions and " "" "request an account. Enter your OpenURL " "account information below."), optionsWidget()); ++row; l->addMultiCellWidget(al, row, row, 0, 1); // richtext gets weird with size al->setMinimumWidth(al->sizeHint().width()); TQLabel* label = new TQLabel(i18n("&Username: "), optionsWidget()); l->addWidget(label, ++row, 0); m_userEdit = new KLineEdit(optionsWidget()); connect(m_userEdit, TQT_SIGNAL(textChanged(const TQString&)), TQT_SLOT(slotSetModified())); l->addWidget(m_userEdit, row, 1); TQString w = i18n("A username and password is required to access the CrossRef service. The password is " "stored as plain text in the Tellico configuration file."); TQWhatsThis::add(label, w); TQWhatsThis::add(m_userEdit, w); label->setBuddy(m_userEdit); label = new TQLabel(i18n("&Password: "), optionsWidget()); l->addWidget(label, ++row, 0); m_passEdit = new KLineEdit(optionsWidget()); connect(m_passEdit, TQT_SIGNAL(textChanged(const TQString&)), TQT_SLOT(slotSetModified())); l->addWidget(m_passEdit, row, 1); TQWhatsThis::add(label, w); TQWhatsThis::add(m_passEdit, w); label->setBuddy(m_passEdit); if(fetcher_) { m_userEdit->setText(fetcher_->m_user); m_passEdit->setText(fetcher_->m_password); } } void CrossRefFetcher::ConfigWidget::saveConfig(KConfigGroup& config_) { TQString s = m_userEdit->text(); config_.writeEntry("User", s); s = m_passEdit->text(); config_.writeEntry("Password", s); slotSetModified(false); } TQString CrossRefFetcher::ConfigWidget::preferredName() const { return CrossRefFetcher::defaultName(); } #include "crossreffetcher.moc"