/*************************************************************************** copyright : (C) 2006 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 "isbndbfetcher.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 "../tellico_debug.h" #include #include #include #include #include #include #include namespace { static const int ISBNDB_RETURNS_PER_REQUEST = 10; static const int ISBNDB_MAX_RETURNS_TOTAL = 25; static const char* ISBNDB_BASE_URL = "http://isbndb.com/api/books.xml"; static const char* ISBNDB_APP_ID = "3B9S3BQS"; } using Tellico::Fetch::ISBNdbFetcher; ISBNdbFetcher::ISBNdbFetcher(TQObject* parent_, const char* name_) : Fetcher(parent_, name_), m_xsltHandler(0), m_limit(ISBNDB_MAX_RETURNS_TOTAL), m_page(1), m_total(-1), m_countOffset(0), m_job(0), m_started(false) { } ISBNdbFetcher::~ISBNdbFetcher() { delete m_xsltHandler; m_xsltHandler = 0; } TQString ISBNdbFetcher::defaultName() { return i18n("ISBNdb.com"); } TQString ISBNdbFetcher::source() const { return m_name.isEmpty() ? defaultName() : m_name; } bool ISBNdbFetcher::canFetch(int type) const { return type == Data::Collection::Book || type == Data::Collection::ComicBook || type == Data::Collection::Bibtex; } void ISBNdbFetcher::readConfigHook(const KConfigGroup& config_) { Q_UNUSED(config_); } void ISBNdbFetcher::search(FetchKey key_, const TQString& value_) { m_key = key_; m_value = value_.stripWhiteSpace(); m_started = true; m_page = 1; m_total = -1; m_numResults = 0; m_countOffset = 0; if(!canFetch(Kernel::self()->collectionType())) { message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning); stop(); return; } doSearch(); } void ISBNdbFetcher::continueSearch() { m_started = true; m_limit += ISBNDB_MAX_RETURNS_TOTAL; doSearch(); } void ISBNdbFetcher::doSearch() { m_data.truncate(0); // myDebug() << "ISBNdbFetcher::search() - value = " << value_ << endl; KURL u(TQString::fromLatin1(ISBNDB_BASE_URL)); u.addQueryItem(TQString::fromLatin1("access_key"), TQString::fromLatin1(ISBNDB_APP_ID)); u.addQueryItem(TQString::fromLatin1("results"), TQString::fromLatin1("details,authors,subjects,texts")); u.addQueryItem(TQString::fromLatin1("page_number"), TQString::number(m_page)); switch(m_key) { case Title: u.addQueryItem(TQString::fromLatin1("index1"), TQString::fromLatin1("title")); u.addQueryItem(TQString::fromLatin1("value1"), m_value); break; case Person: // yes, this also queries titles, too, it's a limitation of the isbndb api service u.addQueryItem(TQString::fromLatin1("index1"), TQString::fromLatin1("combined")); u.addQueryItem(TQString::fromLatin1("value1"), m_value); break; case Keyword: u.addQueryItem(TQString::fromLatin1("index1"), TQString::fromLatin1("full")); u.addQueryItem(TQString::fromLatin1("value1"), m_value); break; case ISBN: u.addQueryItem(TQString::fromLatin1("index1"), TQString::fromLatin1("isbn")); { // only grab first value TQString v = m_value.section(TQChar(';'), 0); v.remove('-'); u.addQueryItem(TQString::fromLatin1("value1"), v); } break; default: kdWarning() << "ISBNdbFetcher::search() - key not recognized: " << m_key << endl; stop(); return; } // myDebug() << "ISBNdbFetcher::search() - url: " << u.url() << endl; 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 ISBNdbFetcher::stop() { if(!m_started) { return; } // myDebug() << "ISBNdbFetcher::stop()" << endl; if(m_job) { m_job->kill(); m_job = 0; } m_data.truncate(0); m_started = false; emit signalDone(this); } void ISBNdbFetcher::slotData(KIO::Job*, const TQByteArray& data_) { TQDataStream stream(m_data, IO_WriteOnly | IO_Append); stream.writeRawBytes(data_.data(), data_.size()); } void ISBNdbFetcher::slotComplete(KIO::Job* job_) { // myDebug() << "ISBNdbFetcher::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() << "ISBNdbFetcher::slotComplete() - no data" << endl; stop(); return; } #if 0 kdWarning() << "Remove debug from isbndbfetcher.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 TQDomDocument dom; if(!dom.setContent(m_data, false)) { kdWarning() << "ISBNdbFetcher::slotComplete() - server did not return valid XML." << endl; return; } if(m_total == -1) { TQDomNode n = dom.documentElement().namedItem(TQString::fromLatin1("BookList")); TQDomElement e = n.toElement(); if(!e.isNull()) { m_total = e.attribute(TQString::fromLatin1("total_results"), TQString::number(-1)).toInt(); } } 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(); int count = 0; Data::EntryVec entries = coll->entries(); for(Data::EntryVec::Iterator entry = entries.begin(); m_numResults < m_limit && entry != entries.end(); ++entry, ++count) { if(count < m_countOffset) { continue; } 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("cr_year")).isEmpty()) { desc += TQChar('/') + entry->field(TQString::fromLatin1("cr_year")); } else if(!entry->field(TQString::fromLatin1("pub_year")).isEmpty()){ desc += TQChar('/') + entry->field(TQString::fromLatin1("pub_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); ++m_numResults; } // are there any additional results to get? m_hasMoreResults = m_page * ISBNDB_RETURNS_PER_REQUEST < m_total; const int currentTotal = TQMIN(m_total, m_limit); if(m_page * ISBNDB_RETURNS_PER_REQUEST < currentTotal) { int foundCount = (m_page-1) * ISBNDB_RETURNS_PER_REQUEST + coll->entryCount(); message(i18n("Results from %1: %2/%3").arg(source()).arg(foundCount).arg(m_total), MessageHandler::Status); ++m_page; m_countOffset = 0; doSearch(); } else { m_countOffset = m_entries.count() % ISBNDB_RETURNS_PER_REQUEST; if(m_countOffset == 0) { ++m_page; // need to go to next page } stop(); // required } } Tellico::Data::EntryPtr ISBNdbFetcher::fetchEntry(uint uid_) { Data::EntryPtr entry = m_entries[uid_]; if(!entry) { kdWarning() << "ISBNdbFetcher::fetchEntry() - no entry in dict" << endl; return 0; } // if the publisher id is set, then we need to grab the real publisher name const TQString id = entry->field(TQString::fromLatin1("pub_id")); if(!id.isEmpty()) { KURL u(TQString::fromLatin1(ISBNDB_BASE_URL)); u.setFileName(TQString::fromLatin1("publishers.xml")); u.addQueryItem(TQString::fromLatin1("access_key"), TQString::fromLatin1(ISBNDB_APP_ID)); u.addQueryItem(TQString::fromLatin1("index1"), TQString::fromLatin1("publisher_id")); u.addQueryItem(TQString::fromLatin1("value1"), id); TQDomDocument dom = FileHandler::readXMLFile(u, true); if(!dom.isNull()) { TQString pub = dom.documentElement().namedItem(TQString::fromLatin1("PublisherList")) .namedItem(TQString::fromLatin1("PublisherData")) .namedItem(TQString::fromLatin1("Name")) .toElement().text(); if(!pub.isEmpty()) { entry->setField(TQString::fromLatin1("publisher"), pub); } } entry->setField(TQString::fromLatin1("pub_id"), TQString()); } return entry; } void ISBNdbFetcher::initXSLTHandler() { TQString xsltfile = locate("appdata", TQString::fromLatin1("isbndb2tellico.xsl")); if(xsltfile.isEmpty()) { kdWarning() << "ISBNdbFetcher::initXSLTHandler() - can not locate isbndb2tellico.xsl." << endl; return; } KURL u; u.setPath(xsltfile); delete m_xsltHandler; m_xsltHandler = new XSLTHandler(u); if(!m_xsltHandler->isValid()) { kdWarning() << "ISBNdbFetcher::initXSLTHandler() - error in isbndb2tellico.xsl." << endl; delete m_xsltHandler; m_xsltHandler = 0; return; } } void ISBNdbFetcher::updateEntry(Data::EntryPtr entry_) { // myDebug() << "ISBNdbFetcher::updateEntry()" << endl; // limit to top 5 results m_limit = 5; TQString isbn = entry_->field(TQString::fromLatin1("isbn")); if(!isbn.isEmpty()) { search(Fetch::ISBN, isbn); return; } // 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; } myDebug() << "ISBNdbFetcher::updateEntry() - insufficient info to search" << endl; emit signalDone(this); // always need to emit this if not continuing with the search } Tellico::Fetch::ConfigWidget* ISBNdbFetcher::configWidget(TQWidget* parent_) const { return new ISBNdbFetcher::ConfigWidget(parent_, this); } ISBNdbFetcher::ConfigWidget::ConfigWidget(TQWidget* parent_, const ISBNdbFetcher*/*=0*/) : Fetch::ConfigWidget(parent_) { TQVBoxLayout* l = new TQVBoxLayout(optionsWidget()); l->addWidget(new TQLabel(i18n("This source has no options."), optionsWidget())); l->addStretch(); } TQString ISBNdbFetcher::ConfigWidget::preferredName() const { return ISBNdbFetcher::defaultName(); } #include "isbndbfetcher.moc"