/*************************************************************************** copyright : (C) 2008 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 "discogsfetcher.h" #include "messagehandler.h" #include "../translators/xslthandler.h" #include "../translators/tellicoimporter.h" #include "../imagefactory.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 #include //#define DISCOGS_TEST namespace { static const int DISCOGS_MAX_RETURNS_TOTAL = 20; static const char* DISCOGS_API_URL = "http://www.discogs.com"; static const char* DISCOGS_API_KEY = "de6cb96534"; } using Tellico::Fetch::DiscogsFetcher; DiscogsFetcher::DiscogsFetcher(TQObject* parent_, const char* name_) : Fetcher(parent_, name_), m_xsltHandler(0), m_limit(DISCOGS_MAX_RETURNS_TOTAL), m_job(0), m_started(false), m_apiKey(TQString::tqfromLatin1(DISCOGS_API_KEY)) { } DiscogsFetcher::~DiscogsFetcher() { delete m_xsltHandler; m_xsltHandler = 0; } TQString DiscogsFetcher::defaultName() { return i18n("Discogs Audio Search"); } TQString DiscogsFetcher::source() const { return m_name.isEmpty() ? defaultName() : m_name; } bool DiscogsFetcher::canFetch(int type) const { return type == Data::Collection::Album; } void DiscogsFetcher::readConfigHook(const KConfigGroup& config_) { TQString k = config_.readEntry("API Key"); if(!k.isEmpty()) { m_apiKey = k; } m_fetchImages = config_.readBoolEntry("Fetch Images", true); m_fields = config_.readListEntry("Custom Fields"); } void DiscogsFetcher::search(FetchKey key_, const TQString& value_) { m_key = key_; m_value = value_; m_started = true; m_start = 1; m_total = -1; doSearch(); } void DiscogsFetcher::continueSearch() { m_started = true; doSearch(); } void DiscogsFetcher::doSearch() { KURL u(TQString::tqfromLatin1(DISCOGS_API_URL)); u.addQueryItem(TQString::tqfromLatin1("f"), TQString::tqfromLatin1("xml")); u.addQueryItem(TQString::tqfromLatin1("api_key"), m_apiKey); if(!canFetch(Kernel::self()->collectionType())) { message(i18n("%1 does not allow searching for this collection type.").tqarg(source()), MessageHandler::Warning); stop(); return; } switch(m_key) { case Title: u.setPath(TQString::tqfromLatin1("/search")); u.addQueryItem(TQString::tqfromLatin1("q"), m_value); u.addQueryItem(TQString::tqfromLatin1("type"), TQString::tqfromLatin1("release")); break; case Person: u.setPath(TQString::tqfromLatin1("/artist/%1").tqarg(m_value)); break; case Keyword: u.setPath(TQString::tqfromLatin1("/search")); u.addQueryItem(TQString::tqfromLatin1("q"), m_value); u.addQueryItem(TQString::tqfromLatin1("type"), TQString::tqfromLatin1("all")); break; default: kdWarning() << "DiscogsFetcher::search() - key not recognized: " << m_key << endl; stop(); return; } #ifdef DISCOGS_TEST u = KURL(TQString::tqfromLatin1("/home/robby/discogs-results.xml")); #endif // myDebug() << "DiscogsFetcher::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 DiscogsFetcher::stop() { if(!m_started) { return; } if(m_job) { m_job->kill(); m_job = 0; } m_data.truncate(0); m_started = false; emit signalDone(this); } void DiscogsFetcher::slotData(KIO::Job*, const TQByteArray& data_) { TQDataStream stream(m_data, IO_WriteOnly | IO_Append); stream.writeRawBytes(data_.data(), data_.size()); } void DiscogsFetcher::slotComplete(KIO::Job* job_) { // myDebug() << "DiscogsFetcher::slotComplete()" << endl; if(job_->error()) { job_->showErrorDialog(Kernel::self()->widget()); stop(); return; } if(m_data.isEmpty()) { myDebug() << "DiscogsFetcher::slotComplete() - no data" << endl; stop(); return; } #if 0 kdWarning() << "Remove debug from discogsfetcher.cpp" << endl; TQFile f(TQString::tqfromLatin1("/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; } } if(m_total == -1) { TQDomDocument dom; if(!dom.setContent(m_data, false)) { kdWarning() << "DiscogsFetcher::slotComplete() - server did not return valid XML." << endl; return; } // total is /resp/searchresults/@numResults TQDomNode n = dom.documentElement().namedItem(TQString::tqfromLatin1("resp")) .namedItem(TQString::tqfromLatin1("searchresults")); TQDomElement e = n.toElement(); if(!e.isNull()) { m_total = e.attribute(TQString::tqfromLatin1("numResults")).toInt(); myDebug() << "total = " << m_total; } } // assume discogs 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() << "DiscogsFetcher::slotComplete() - no collection pointer" << endl; stop(); return; } int count = 0; Data::EntryVec entries = coll->entries(); for(Data::EntryVec::Iterator entry = entries.begin(); count < m_limit && entry != entries.end(); ++entry, ++count) { if(!m_started) { // might get aborted break; } TQString desc = entry->field(TQString::tqfromLatin1("artist")) + TQChar('/') + entry->field(TQString::tqfromLatin1("label")); SearchResult* r = new SearchResult(this, entry->title(), desc, TQString()); m_entries.insert(r->uid, Data::EntryPtr(entry)); emit signalResultFound(r); } m_start = m_entries.count() + 1; // not sure how tospecify start in the REST url // m_hasMoreResults = m_start <= m_total; stop(); // required } Tellico::Data::EntryPtr DiscogsFetcher::fetchEntry(uint uid_) { Data::EntryPtr entry = m_entries[uid_]; if(!entry) { kdWarning() << "DiscogsFetcher::fetchEntry() - no entry in dict" << endl; return 0; } // one way we tell if this entry has been fully initialized is to // check for a cover image if(!entry->field(TQString::tqfromLatin1("cover")).isEmpty()) { myLog() << "DiscogsFetcher::fetchEntry() - already downloaded " << entry->title() << endl; return entry; } TQString release = entry->field(TQString::tqfromLatin1("discogs-id")); if(release.isEmpty()) { myDebug() << "DiscogsFetcher::fetchEntry() - no discogs release found" << endl; return entry; } #ifdef DISCOGS_TEST KURL u(TQString::tqfromLatin1("/home/robby/discogs-release.xml")); #else KURL u(TQString::tqfromLatin1(DISCOGS_API_URL)); u.setPath(TQString::tqfromLatin1("/release/%1").tqarg(release)); u.addQueryItem(TQString::tqfromLatin1("f"), TQString::tqfromLatin1("xml")); u.addQueryItem(TQString::tqfromLatin1("api_key"), m_apiKey); #endif // myDebug() << "DiscogsFetcher::fetchEntry() - url: " << u << endl; // quiet, utf8, allowCompressed TQString output = FileHandler::readTextFile(u, true, true, true); #if 0 kdWarning() << "Remove output debug from discogsfetcher.cpp" << endl; TQFile f(TQString::tqfromLatin1("/tmp/test.xml")); if(f.open(IO_WriteOnly)) { TQTextStream t(&f); t.setEncoding(TQTextStream::UnicodeUTF8); t << output; } f.close(); #endif Import::TellicoImporter imp(m_xsltHandler->applyStylesheet(output)); Data::CollPtr coll = imp.collection(); // getTracks(entry); if(!coll) { kdWarning() << "DiscogsFetcher::fetchEntry() - no collection pointer" << endl; return entry; } if(coll->entryCount() > 1) { myDebug() << "DiscogsFetcher::fetchEntry() - weird, more than one entry found" << endl; } const StringMap customFields = this->customFields(); for(StringMap::ConstIterator it = customFields.begin(); it != customFields.end(); ++it) { if(!m_fields.tqcontains(it.key())) { coll->removeField(it.key()); } } // don't want to include id coll->removeField(TQString::tqfromLatin1("discogs-id")); entry = coll->entries().front(); m_entries.tqreplace(uid_, entry); return entry; } void DiscogsFetcher::initXSLTHandler() { TQString xsltfile = locate("appdata", TQString::tqfromLatin1("discogs2tellico.xsl")); if(xsltfile.isEmpty()) { kdWarning() << "DiscogsFetcher::initXSLTHandler() - can not locate discogs2tellico.xsl." << endl; return; } KURL u; u.setPath(xsltfile); delete m_xsltHandler; m_xsltHandler = new XSLTHandler(u); if(!m_xsltHandler->isValid()) { kdWarning() << "DiscogsFetcher::initXSLTHandler() - error in discogs2tellico.xsl." << endl; delete m_xsltHandler; m_xsltHandler = 0; return; } } void DiscogsFetcher::updateEntry(Data::EntryPtr entry_) { // myDebug() << "DiscogsFetcher::updateEntry()" << endl; TQString value; TQString title = entry_->field(TQString::tqfromLatin1("title")); if(!title.isEmpty()) { search(Title, value); return; } TQString artist = entry_->field(TQString::tqfromLatin1("artist")); if(!artist.isEmpty()) { search(Person, artist); return; } myDebug() << "DiscogsFetcher::updateEntry() - insufficient info to search" << endl; emit signalDone(this); // always need to emit this if not continuing with the search } Tellico::Fetch::ConfigWidget* DiscogsFetcher::configWidget(TQWidget* parent_) const { return new DiscogsFetcher::ConfigWidget(parent_, this); } DiscogsFetcher::ConfigWidget::ConfigWidget(TQWidget* parent_, const DiscogsFetcher* fetcher_) : Fetch::ConfigWidget(parent_) { TQGridLayout* l = new TQGridLayout(optionsWidget(), 2, 2); l->setSpacing(4); l->setColStretch(1, 10); int row = -1; TQLabel* label = new TQLabel(i18n("API &key: "), optionsWidget()); l->addWidget(label, ++row, 0); m_apiKeyEdit = new KLineEdit(optionsWidget()); connect(m_apiKeyEdit, TQT_SIGNAL(textChanged(const TQString&)), TQT_SLOT(slotSetModified())); l->addWidget(m_apiKeyEdit, row, 1); TQString w = i18n("With your discogs.com account you receive an API key for the usage of their XML-based interface " "(See http://www.discogs.com/help/api)."); TQWhatsThis::add(label, w); TQWhatsThis::add(m_apiKeyEdit, w); label->setBuddy(m_apiKeyEdit); m_fetchImageCheck = new TQCheckBox(i18n("Download cover &image"), optionsWidget()); connect(m_fetchImageCheck, TQT_SIGNAL(clicked()), TQT_SLOT(slotSetModified())); ++row; l->addMultiCellWidget(m_fetchImageCheck, row, row, 0, 1); w = i18n("The cover image may be downloaded as well. However, too many large images in the " "collection may degrade performance."); TQWhatsThis::add(m_fetchImageCheck, w); l->setRowStretch(++row, 10); // now add additional fields widget addFieldsWidget(DiscogsFetcher::customFields(), fetcher_ ? fetcher_->m_fields : TQStringList()); if(fetcher_) { m_apiKeyEdit->setText(fetcher_->m_apiKey); m_fetchImageCheck->setChecked(fetcher_->m_fetchImages); } else { m_apiKeyEdit->setText(TQString::tqfromLatin1(DISCOGS_API_KEY)); m_fetchImageCheck->setChecked(true); } } void DiscogsFetcher::ConfigWidget::saveConfig(KConfigGroup& config_) { TQString apiKey = m_apiKeyEdit->text().stripWhiteSpace(); if(!apiKey.isEmpty()) { config_.writeEntry("API Key", apiKey); } config_.writeEntry("Fetch Images", m_fetchImageCheck->isChecked()); saveFieldsConfig(config_); slotSetModified(false); } TQString DiscogsFetcher::ConfigWidget::preferredName() const { return DiscogsFetcher::defaultName(); } Tellico::StringMap DiscogsFetcher::customFields() { StringMap map; map[TQString::tqfromLatin1("producer")] = i18n("Producer"); map[TQString::tqfromLatin1("nationality")] = i18n("Nationality"); map[TQString::tqfromLatin1("discogs")] = i18n("Discogs Link"); return map; } #include "discogsfetcher.moc"